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:

TermDescriptoon
Purseholds REV. Purses must only accept (in their methods) other purses produced by the (same) REV Mint
VaultAdds 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.)
  • Anyone can make a deposit to a Vault. The REV Address is used to lookup which vault should receive the deposit of REV, as well as to look up the Vault used to spend.
  • Only the holder of the Vault key can spend it.
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.


An off-chain REV Address is used to lookup the Vault on-chain.

REV MintThe 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:

  • Public-key proof of possession of the private key.
  • Possession of the unforgeable name used to create the Vault.

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.

  1. Bob asks Alice to fund her Vault. (For example, Alice is an exchange and Bob sends USD to Alice to convert to REV.)
  2. 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.)
  3. 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.
  4. Alice signs and deploys the rholang code
    1. DeployData should point to a publicKey, timestamp, "max gas limit".
  5. Alice proposes.
  6. 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).
  7. Code in step 6 gets executed.
  8. After rholang code gets executed. Bob's REV Address is visible in the REV Vault Registry.
  9.  ("max gas limit" - "actual gas used") is refunded to Alice's REV Vault

REVAddress

Requirements:

  1. versioned 
  2. magic number - to make sure it refers to REV and not other coins
  3. checksum - to make sure that the whole address can be verified 
  4. compact
  5. human-readable - Base58 is recommended for avoiding human copying errors

Algorithm

Python pseudocode for converting a Public key into REV Address:

public_key_bytes = codecs.decode(public_key, ‘hex’)
keccak_hash = keccak.new(digest_bits=256)
keccak_hash.update(public_key_bytes)
keccak_digest = keccak_hash.hexdigest()
# Take the last 20 bytes
wallet_len = 40

(TODO ADD the remain steps per our decision Polland 5/8/19)

rev-address = prefix + payload + checksum

payload = base58(hash(public-key))

checksum = base58(take(4, hash(prefix + payload)))

prefix = arbitrary ASCII prefix. We can choose here any string we want. Example: "rev", "revtest" etc

The hash algorithm used is Blake2b

REV Address Map

A registry of REV Addresses with the following characteristics is needed.

  1. REV Addresses are the output of a hash algorithm. H(x)
    1. 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(...).
    2. 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.
  2. Anyone can create (add) a new REV Address to the map knowing only the address. (To support bootstrap)
    In this scenario, the REV Address is H(public_key)
  3. 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.
  4. 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:
    1. Signing a transaction using the private_key associated with the public_key forming the REV Address, ie. H(public_key).
    2. 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 Address 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.

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.

  1. 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.
  2. 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
                }
            }
        }
    }
    
    
  3. It then signs a deploy over (Alice's Pubkey, phlo-based timestamp, rholang code), and deploys it to a Validator.
  4. Dave runs a read-only RChain node, and observes the deployed transaction, ensuring the transaction is finalized.
  5. 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:

FunctionParamsDesription
findOrCreateREV 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)

  1. case Some => return
  2. case None =>
    1. REV Mint create empty purse => Purse
    2. new Vault(Purse, defaultAuthCode)
    3. register REV Address => Vault in REV-Address-registry
    4. return
createREV AddressCreates 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 
transferDestVault REV Address, Amount, Lockbox

Transfer REV from this Vault to another Vault. Lockbox proves ownership over the Vault sending the REV.

transfer means:

  • lockbox.verify() => matches?
  • purse.split(amount) => p1
  • findOrCreat(DestVault REV Address) => v1
  • v1.deposit(p1)
depositPurseDeposits Purse into the Vault. (Combing it with existing Purse in Vault.)





Ad 3.

The Create contract seems to be:

  1. verify contract signature => lockBox(Alice publicKey)
  2. REVWalletFactory findOrCreate(Bob publicKey) => lockBox(Bob REVWallet)
  3. REVWalletFactory lookup(lockBox(Alice publicKey)) => lockBox(Alice REVWallet)
  4. 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? (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.