Protocash is a layer 1
cryptocurrency running on top of cometbft with a focus on privacy and speed. All transactions on protocash are done in
zero-knowledge using arkworks
.
In this repo you will find three crates.
protocash-node
: A binary used by nodes on the network to validate transactions.protocash-client
: A binary used by clients to transact to other clients. The transactions are checked by nodes runningprotocash-node
.protocash-util
: A library of shared utilities betweenprotocash-node
andprotocash-client
.
(you may want to check that rust is up to date.)
- Start a node
Inside of node
, run
cargo run --release
- Start a client
Inside of client
, run
cargo run --release
You may also want to run the test. util
contains various tests for the payment
proof. You can run
cargo test --release
from the root directory to see these results
You should see the client
make a connection to the node
over the ABCI.
We consider a model with one key simplification: there are no denominations to the currency. In other words, any and all transactions just send one single coin.
This is the shape of the data stored by all validators on the blockchain.
type CoinID = u64; /// A coin identifier, often called the `pre_serial_number`.
type PubKey = u64; /// The public key of some user on the network.
/// The state of our application
struct State {
/// This `MerkleTree` stores coin commitments as its leaves.
coins: MerkleTree<Commitment<Coin>>,
/// This is a set of all used `serial_number`s. This list is used to keep
/// track of spent coins in order to avoid double spends.
spent_ids: HashSet<CoinID>
}
/// A Coin
struct Coin {
/// The public key of the owner of this coin.
key: PubKey,
/// The unique, random identifier of the coin.
pre_serial_number: CoinID,
/// Noise used when generating the coin commitment
com_rnd: u64
}
/// A coin commitment. This is the data that our MerkleTree actually
/// stores. This is really just a hash of the coin which takes as input
/// - The `key`
/// - The `pre_serial_number`
/// - The `com_rnd`, for randomness
struct Commitment<T> {
/// This is really the only important piece of data stored by the
/// commitment.
pub hash: u64,
/// This is just to keep track of what this is a commitment to. In this
/// case, just a coin.
_tx: PhantomData<T>
}
When we make a transaction, we need to enforce two guarantees:
-
We can afford to make this transaction.
Because there are no denominations in our currency, this is equivalent to proving that there is a coin in the Merkle Tree which belongs to us.
-
We are not double spending transactions.
If there is a coin in the tree that is ours, we need to ensure that we can only use it once.
The process by which we make a transaction is the following. Suppose that A
wants to make a payment to B
.
Then, A
needs to provide a zero knowledge proof that
-
There exists a commitment
c
in the Merkle Tree which opens topk_A
,A
's public key.pre_serial_no
com_rnd
This is the coin that
A
is spending. -
The serial number
sn = prf(sk_A, pre_serial_no)
ANDpk_A = H(sk_A)
, wheresk_A
isA
's secret key, andprf
is just some pseudo-random function which, in this case, is just a hash function.At this point, there are a few things to note
- Notice here that only
A
can make this proof because onlyA
knowssk_A
. - It's also important to note that
sn
is not stored in the commitment here. In fact,sn
is not stored anywhere, it is just used in the proof thatA
provides when making a transaction.
- Notice here that only
If A
can make this proof, a new commitment c' = H(pk_B, pre_serial_no', com_rnd')
is added by the validators, to the Merkle Tree, where
pre_serial_no' = H(sn)
cmd_rnd'
is random.
Both pre_serial_no'
and cmr_rnd'
are yielded back to A
. Then, A
is
reponsible for messaging both cmd_rnd'
and pre_serial_no'
privately to B
,
in order for B
to use this transaction.
In order for B
to find this coin in the Merkle Tree, they can simply compute
the commitment from the information sent over by A
.
Public inputs are:
r
: The Merkle Tree rootc
: The coin commitment owned byA
s
: The serial number