Wallet proposal (Note: we decided to rename terms such that "wallet" is not used for on-chain components)
This is a Work In Progress.
REV Vault Composition
Vaults are used to hold/send/receive REV, and are composed as follows:
Term | Descriptoon |
---|---|
Purse | holds REV. Purses must only accept (in their methods) other purses produced by the (same) REV Mint |
Vault | Adds the guard around a Purse. The "guard" can be a simple public key or a more complicated multisig smart-contract. It also adds auditing to all REV transfers and enforces that a Purse is only sent to another REV Vault (to ensure all REV transfers are audited and conserved.)
|
REV Address | A REV Address is a human-readable representation of where REV can be sent. It is used by off-chain it exchanges and wallets as part of the user experience.
|
REV Mint | The REV Mint issues Purses and ultimately ensures the total supply of REV is conserved. (The Rev Mint is part of the REV Vault contract.) |
Lockbox | The Lockbox is required to transfer REV out of the Purse locked by the Vault. There are two types:
|
The REV Vault contract handles the following:
- Looking up Vaults using the REV Address
- Creating a new Vault using only a REV Address. This allows a new user to create a REV Address off-chain, and allow deposits into the Vault at this REV Address.
- The REV Vault contract contains the map used to lookup Vaults by REV Addresses. This map is called the REV Vault Registry.
Bootstrap
Problem statement:
Bob is a new user and wants to have REV.
- Bob asks Alice to fund her Vault. (For example, Alice is an exchange and Bob sends USD to Alice to convert to REV.)
- Alice gets a REV Address form Bob. (This REV Address is created by generating and EC key-pairs, and converting the public key into a REV Address.)
- Alice uses an app that generates the rholang code that will send REV from her Vault to a new Vault that is controlled using Bob's private key.
- Alice signs and deploys the rholang code
- DeployData should point to a publicKey, timestamp, "max gas limit".
- Alice proposes.
- SOMEHOW the system sets up code that reserves "max gas limit" from the sender's (Alice's) Vault (It will ultimately transfer from Alice's Vault to CasperPool Vault).
- Code in step 6 gets executed.
- After rholang code gets executed. Bob's REV Address is visible in the REV Vault Registry.
- ("max gas limit" - "actual gas used") is refunded to Alice's REV Vault
REVAddress
Requirements:
REV Address Map
A registry of REV Addresses with the following characteristics is needed.
- REV Addresses are the output of a hash algorithm.
H(x)
- We need to come to an agreement on which hash algorithm, but for the remainder of this description it is not important, assume it is
H(...)
. - Also, for off-chain representations of these addresses, we should assume that the output of
H(x)
is encoded using something like BTC's base58 with an additional bit of info in it to assure it is intended to be used as a REV Address, possibly even containing an additional small checksum.
- We need to come to an agreement on which hash algorithm, but for the remainder of this description it is not important, assume it is
- Anyone can create (add) a new REV Address to the map knowing only the address. (To support bootstrap)
In this scenario, the REV Address isH(public_key)
- A user should be able to add REV to a Vault that is in the map knowing only its REV Address.
In other words, if someone adds a (REV Address,Vault) to the map with an initial balance of 5 REV, a second user should be able to send that REV Address an additional 3 REV, and the Vault in the map should now have a total of 8 REV. The actual Vault owner in this scenario may still not have interacted with RChain yet to support this use case. - The Vault owner should be able to prove ownership of the Vault Key in order to spend the REV it contains.
Ownership of the Vault Key can be proven in one of two ways:- Signing a transaction using the
private_key
associated with thepublic_key
forming the REV Address, ie.H(public_key)
. - Proving possession of an unforgeable name that hashes to the REV Address. In this case, the REV Address is
H(unforgeable_name)
The purpose of using an unforgeable name is to gain access to the Vault to allow for more sophisticated forms of ownership over the Vault than just a simple public key pair.
For example, someone might create a multisig contract that requires N of M users to unlock a Vault prior to spending it. In this scenario, the multisig contract will create an unforgeable name, Z, that ends up forming the REV AddressH(z)
. How this unforgeable name is actually used to unlock the Vault involves the use of a new RChain primitive called a lockbox, and is described below.
- Signing a transaction using the
Unlock a Vaults using a Lockbox
To unlock a Vault, you must present a lockbox that matches the REV Address.
Conceptually, the lockbox serves as a witness that the code requesting a reference to the spendable Vault has either been signed using the private key for the given public key, or is in possession of the unforgeable name that hashes to the REV Address.
new lockbox(`rho:lockbox:pubkey`), rtn in { lockbox!(rtn) }
OR
new lockbox(`rho:lockbox:unforgeable`), rtn, uname in { lockbox!(uname, rtn) }
Within the REV Vault contract, the code verifies that access to the Vault was passed in a lockbox whose H(x)
matches the address of the Vault being requested.
Preventing Replay
Transactions can be identified by the (user, phloStamp) tuple. Replay attacks are prevented by ensuring the transaction's (user, phloStamp) doesn't conflict with another's. We reject any transactions that are less than X from the current block's phlo execution accumulated value.
val block = findBlockWithPhloAccumulated(currentBlock, X) if (deploy.phloAccumulatedStamp >= block.phloAccumulated && deploy.phloAccumulatedStamp <= currentBlock.phloAccumulated) { // Valid } def findBlockWithPhloAccumulated(currentBlock: BlockMessage, threshold: Int) { if (threshold <= 0) { currentBlock } else { findBlockWithPhloAccumulated( currentBlock.mainParent , threshold - currentBlock.phloTotal) } }
Alice pays Dave for ice cream
In this example, we look into the details of how Alice pays Dave the ice cream man for ice cream.
Dave is selling a cup of ice cream for 3 REV. At the ice cream counter, there is a QR code with Dave's REV wallet address. This wallet address is a hash of a public key encoded in Base58 and also contains a builtin checksum.
- Using a REV wallet app on her iPhone, Alice selects the function to transfer REV. She scans Dave's QR code and enters 3 REV into the app indicating she wants to transfer 3 REV to Dave.
The REV wallet app constructs a Rholang transaction that similar to this:
// rho:id:6wnujzcraztjfg941skrtbdkdgbko8nuaqihuhn15s66oz8ro5gwbb is the REV Wallet contract new rl(`rho:registry:lookup`), lockbox(`rho:lockbox:pubkey`), revVaultCh, lockboxCH, aliceCh, status in { rl!(`rho:id:6wnujzcraztjfg941skrtbdkdgbko8nuaqihuhn15s66oz8ro5gwbb`, *revVaultCh) | lockbox!(*lockboxCh) | for(@(_, RevVault) <- revVaultCh; mylockbox <- lockboxCh ) { // Get Alice's Vault with access to spend it. // The Rev Vault contract uses the mylockbox (an RChain builtin) to ensure "AlicePubRevAddr" matches // the pub key that signed this transaction. RevVault!("getmywallet", "AlicePubWalletAddr", *aliceCh) | for ( Alice <- aliceCh ) { // 1122334455667788 is the addr of Dave's destination wallet. // 2 is the amount of Rev to transfer Alice!("transfer", "1122334455667788", 3, mylockbox, *status) | for(@result <- status) { // Do something with the status } } } }
- It then signs a deploy over (Alice's Pubkey, phlo-based timestamp, rholang code), and deploys it to a Validator.
- Dave runs a read-only RChain node, and observes the deployed transaction, ensuring the transaction is finalized.
- Once confirmed, he gives Alice her ice Cream
REV Vault Contract
The Rev Vault Contract is where most of the logic lives for on-chain REV movement.
It must support:
Function | Params | Desription |
---|---|---|
findOrCreate | REV Address | Looks up the Vault via the REV Address. If it finds one, it sends it back on the return channel. If a Vault does not exist at this REV address. a new empty Vault is created with this address. The Vault sent back is suitable to be the recipient of a REV Purse via "deposit" lookup the public key in REV-Address-Registry => Some(Vault)
|
create | REV Address | Creates a new Vault with address REV Address, and registers it in the REV-Address-Registry. This will typically be called by a rholang contract the creates a Valut intended to be used by a multisig smart contract. Note: This approach could also be used to create a wallet that could be spent using an Ethereum address, and thus be used to |
transfer | DestVault REV Address, Amount, Lockbox | Transfer REV from this Vault to another Vault. Lockbox proves ownership over the Vault sending the REV. transfer means:
|
deposit | Purse | Deposits Purse into the Vault. (Combing it with existing Purse in Vault.) |
Ad 3.
The Create contract seems to be:
- verify contract signature => lockBox(Alice publicKey)
- REVWalletFactory findOrCreate(Bob publicKey) => lockBox(Bob REVWallet)
- REVWalletFactory lookup(lockBox(Alice publicKey)) => lockBox(Alice REVWallet)
- lockBox(Alice REVWallet) transfer(lockBox(Bob REVWallet), amount)
Ad 6.
It looks like there are two possibilities:
- generate rholang and run it with authority (it may be the case that the part that does 3. needs to set this up)
- run methods on validator level (in platform code, impure)
The basics of charging for transactions is:
- REVWalletFactory lookup(lockBox(Alice publicKey)) => lockBox(Alice REVWallet)
- lockBox(Alice REVWallet) transfer(Casper Pool REVWallet publicKey, amount)
Notes:
Clashes on publicKeys in REVWalletFactory findOrCreate can result in slow REVWallet creation.
Should purses of size 0 be not allowed? (Former user (Deleted))
Purses should not be a first class citizen (purses live inside the REVWallet).
Max gas limit can be lied about by a colluding set of validators.