This is a Work In Progress.
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:
Problem statement:
Bob is a new user and wants to have REV.
A registry of REV Addresses with the following characteristics is needed.
H(x)
H(...)
.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.H(public_key)
private_key
associated with the public_key
forming the REV Address, ie. H(public_key)
.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.H(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.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.
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) } } |
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.
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 } } } } |
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:
Ad 6.
It looks like there are two possibilities:
The basics of charging for transactions is:
Notes:
Clashes on publicKeys in REVWalletFactory findOrCreate can result in slow REVWallet creation.
Should purses of size 0 be not allowed? (Joseph Denman)
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.