parazyd 4 лет назад
Родитель
Сommit
3fb78520f0
4 измененных файлов с 284 добавлено и 0 удалено
  1. 3 0
      book/src/SUMMARY.md
  2. 6 0
      book/src/zkas/examples.md
  3. 270 0
      book/src/zkas/examples/sapling.md
  4. 5 0
      book/src/zkas/zkas.md

+ 3 - 0
book/src/SUMMARY.md

@@ -1,3 +1,6 @@
 # Summary
 
 [DarkFi](README.md)
+- [zkas](zkas/zkas.md)
+  - [Examples](zkas/examples.md)
+    - [Sapling payment scheme](zkas/examples/sapling.md)

+ 6 - 0
book/src/zkas/examples.md

@@ -0,0 +1,6 @@
+# Examples
+
+This section holds practical and real-world examples of the use for
+zkas.
+
+- [Sapling payment scheme](examples/sapling.md)

+ 270 - 0
book/src/zkas/examples/sapling.md

@@ -0,0 +1,270 @@
+# Sapling payment scheme
+
+Generally, the Sapling payment scheme consists of two proofs -
+**mint** and **burn**. We use the mint proof to create a new _coin_
+$C$, and we use the burn proof to spend a previously minted _coin_.
+
+## Mint
+
+```python
+constant "Mint" {
+    EcFixedPoint VALUE_COMMIT_VALUE,
+    EcFixedPoint VALUE_COMMIT_RANDOM,
+};
+
+contract "Mint" {
+    Base pub_x,
+    Base pub_y,
+    Base value,
+    Base token,
+    Base serial,
+    Base coin_blind,
+    Scalar value_blind,
+    Scalar asset_blind,
+};
+
+circuit "Mint" {
+    # Poseidon hash of the coin
+    C = poseidon_hash(pub_x, pub_y, value, token, serial, coin_blind);
+    constrain_instance(C);
+
+    # Pedersen commitment for coin's value.
+    vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
+    vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
+    value_commit = ec_add(vcv, vcr);
+    # Since value_commit is a curve point, we fetch its coordinates
+    # and constrain them.
+    value_commit_x = ec_get_x(value_commit);
+    value_commit_y = ec_get_x(value_commit);
+    constrain_instance(value_commit_x);
+    constrain_instance(value_commit_y);
+
+    # Pedersen commitment for coin's token ID.
+    tcv = ec_mul_short(token, VALUE_COMMIT_VALUE);
+    tcr = ec_mul(token_blind, VALUE_COMMIT_RANDOM);
+    token_commit = ec_add(tcv, tcr);
+    # Since token_commit is also a curve point, we'll do the same
+    # coordinate dance.
+    token_commit_x = ec_get_x(token_commit);
+    token_commit_y = ec_get_y(token_commit);
+    constrain_instance(token_commit_x);
+    constrain_instance(token_commit_y);
+
+    # At this point we've enforced all of our public inputs.
+};
+```
+
+As you can see, the `Mint` contract/circuit basically consists of
+three operations. First one is hashing the _coin_ $C$, and after
+that, we create _Pedersen commitments_ for both the coin's **value**
+and the coin's **token ID**. On top of the zkas code, we've declared
+two constant values that we are going to use for multiplication in
+the commitments.
+
+The `constrain_instance` call can take any of our assigned variables
+and enforce a _public input_. Public inputs are an array (or vector)
+of values used as public inputs by verifiers to verify a zero knowledge
+proof. In the above case of the Mint contract, since we have five
+calls to `constrain_instance`, we would also have an array of five
+elements that represent these public inputs. The array's order **must**
+match the `constrain_instance` calls since they will be constrained
+by their index in the array (which is incremented for every call).
+
+In other words, the vector of public inputs could look like this:
+
+```rust
+let public_inputs = vec![
+    coin,
+    *value_commitment_coords.x(),
+    *value_commitment_coords.y(),
+    *token_commitment_coords.x(),
+    *token_commitment_coords.y(),
+];
+```
+
+And then the Verifier uses these public inputs to verify a given zero 
+knowledge proof.
+
+### Coin
+
+During the **Mint** phase we create a new coin $C$, which is bound
+to the public key $P$. The coin $C$ is publicly revealed on the
+blockchain and added to the Merkle tree.
+
+Let $v$ be the coin's value, $t$ be the token ID, $\rho$ be the unique
+serial number for the coin, and $r_C$ be a random blinding value. We
+create a commitment (hash) these elements and produce the coin $C$ in
+zero-knowledge:
+
+$$C = H(P, v, t, \rho, r_C)$$
+
+### Value and Token commitments
+
+To have some value $v$ for our coin, we can create a
+_Pedersen commitment_ $V$ where $r_V$ is the blinding factor for the
+commitment, and $G_1$ and $G_2$ are two predefined generators:
+
+$$ v > 0 $$
+$$ V = vG_1 + r_VG_2 $$
+
+The token ID can be thought of as an attribute we append to our _coin_
+so we can have a differentiation of assets we are working with. In
+practice for example, this allows us to work with different tokens,
+using the same zero-knowledge proof circuit. For this token ID, we can
+also build a _Pedersen commitment_ $T$, where $t$ is the token ID,
+$r_T$ is the blinding factor, and $G_1$ and $G_2$ are the generators:
+
+$$ T = tG_1 + r_TG_2 $$
+
+Knowing this, we can extend our code example and build the
+before-mentioned public inputs for the circuit:
+
+```rust
+let public_key = pallas::Point::random(&mut OsRng);
+let coords = public_key.to_affine().coordinates().unwrap();
+let pub_x = *coords.x();
+let pub_y = *coords.y();
+
+let value = pallas::Base::from(42);
+let token = pallas::Base::from(1);
+let serial = pallas::Base::random(&mut OsRng);
+let coin_blind = pallas::Base::random(&mut OsRng);
+
+let coin = poseidon::Hash(pub_x, pub_y, value, token, serial, coin_blind);
+
+let value_commit = pedersen_commitment_u64(value, value_blind);
+let value_coords = value_commit.to_affine().coordinates().unwrap();
+
+let token_commit = pedersen_commitment_u64(token, token_blind);
+let token_coords = token_commit.to_affine().coordinates().unwrap();
+
+let public_inputs = vec![
+    coin,
+    *value_commitment_coords.x(),
+    *value_commitment_coords.y(),
+    *token_commitment_coords.x(),
+    *token_commitment_coords.y(),
+];
+```
+
+## Burn
+
+```python
+constant "Burn" {
+    EcFixedPoint VALUE_COMMIT_VALUE,
+    EcFixedPoint VALUE_COMMIT_RANDOM,
+    EcFixedPoint NULLIFIER_K,
+};
+
+contract "Burn" {
+    Base secret,
+    Base serial,
+    MerklePath path,
+    Base leaf,
+    Base value,
+    Base token,
+    Scalar value_blind,
+    Scalar token_blind,
+    Base signature_secret,
+};
+
+circuit "Burn" {
+    # Poseidon hash of the Nullifier
+    nullifier = poseidon_hash(secret, serial);
+    constrain_instance(nullifier);
+
+    # Merkle root
+    root = calculate_merkle_root(path, leaf);
+    constrain_instance(root);
+
+    # Pedersen commitment for coin's value
+    vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
+    vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
+    value_commit = ec_add(vcv, vcr);
+    # Since value_commit is a curve point, we fetch its coordinates
+    # and constrain them.
+    value_commit_x = ec_get_x(value_commit);
+    value_commit_y = ec_get_y(value_commit);
+    constrain_instance(value_commit_x);
+    constrain_instance(value_commit_y);
+
+    # Pedersen commitment for coin's token ID
+    tcv = ec_mul_short(token, VALUE_COMMIT_VALUE);
+    tcr = ec_mul(token_blind, VALUE_COMMIT_RANDOM);
+    token_commit = ec_add(tcv, tcr);
+    # Since token_commit is also a curve point, we'll do the same
+    # coordinate dance.
+    token_commit_x = ec_get_x(token_commit);
+    token_commit_y = ec_get_y(token_commit);
+    constrain_instance(token_commit_x);
+    constrain_instance(token_commit_y);
+
+    # Finally, we derive a public key for the signature and
+    # constrain its coordinates.
+    signature_public = ec_mul(signature_secret, NULLIFIER_K);
+    signature_x = ec_get_x(signature_public);
+    signature_y = ec_get_y(signature_public);
+    constrain_instance(signature_x);
+    constrain_instance(signature_y);
+
+    # At this point we've enforced all of our public inputs.
+};
+```
+
+The `Burn` contract/circuit consists of operations similar to the
+`Mint` circuit, with the addition of _Merkle root_ calculation. In
+the same manner, we're doing a Poseidon hash instance, we're building
+Pedersen commitments for the value and token ID, and finally we're
+doing a public key derivation.
+
+In this case, our vector of public inputs could look like:
+
+```rust
+let public_inputs = vec![
+    nullifier,
+    *value_commitment_coords.x(),
+    *value_commitment_coords.y(),
+    *token_commitment_coords.x(),
+    *token_commitment_coords.y(),
+    merkle_root,
+    *signature_coords.x(),
+    *signature_coords.y(),
+];
+```
+
+### Nullifier
+
+When we spend the coin, we must ensure that the value of the coin
+cannot be double spent. We call this the _Burn_ phase. The process
+relies on a nullifier $N$, which we create using the secret key $x$
+for the public key $P$ and a unique random serial $\rho$. Nullifiers
+are unique per coin and prevent double spending:
+
+$$ N = H(x, \rho) $$
+
+
+### Value and Token commitments
+
+Just like we calculated these for the `Mint` contract, we do the same
+here:
+
+$$ v > 0 $$
+$$ V = vG_1 + r_VG_2 $$
+$$ T = tG_1 + r_TG_2 $$
+
+
+### Public key derivation
+
+We check that the secret key $x$ corresponds to a public key $P$.
+Usually, we do public key derivation by multiplying our secret key
+with a generator $G$, which results in a public key.
+
+$$ P = xG $$
+
+### Merkle root
+
+We check that the merkle root corresponds to a coin which is in the
+Merkle tree $R$:
+
+$$ C = H(P, v, t, \rho, r_C) $$
+$$ C \in R $$

+ 5 - 0
book/src/zkas/zkas.md

@@ -0,0 +1,5 @@
+zkas
+====
+
+zkas is a compiler for the Halo2 zkVM langage used in
+[DarkFi](https://github.com/darkrenaissance/darkfi).