/ doc / src / zkas / examples / sapling.md
sapling.md
  1  # Sapling payment scheme
  2  
  3  Sapling is a type of transaction which hides both the sender and
  4  receiver data, as well as the amount transacted. This means it allows
  5  a fully private transaction between two addresses.
  6  
  7  Generally, the Sapling payment scheme consists of two ZK proofs -
  8  **mint** and **burn**. We use the mint proof to create a new _coin_
  9  $C$, and we use the burn proof to spend a previously minted _coin_.
 10  
 11  ## Mint proof
 12  
 13  ```zkas
 14  {{#include ../../../../proof/mint.zk}}
 15  ```
 16  
 17  As you can see, the `Mint` proof basically consists of three
 18  operations.  First one is hashing the _coin_ $C$, and after that,
 19  we create _Pedersen commitments_[^1] for both the coin's **value**
 20  and the coin's **token ID**. On top of the zkas code, we've declared
 21  two constant values that we are going to use for multiplication in
 22  the commitments.
 23  
 24  The `constrain_instance` call can take any of our assigned variables
 25  and enforce a _public input_. Public inputs are an array (or vector)
 26  of revealed values used by verifiers to verify a zero knowledge
 27  proof. In the above case of the Mint proof, since we have five calls to
 28  `constrain_instance`, we would also have an array of five elements that
 29  represent these public inputs. The array's order **must match** the
 30  order of the `constrain_instance` calls since they will be constrained
 31  by their index in the array (which is incremented for every call).
 32  
 33  In other words, the vector of public inputs could look like this:
 34  
 35  ```
 36  let public_inputs = vec![
 37      coin,
 38      *value_coords.x(),
 39      *value_coords.y(),
 40      *token_coords.x(),
 41      *token_coords.y(),
 42  ];
 43  ```
 44  
 45  And then the Verifier uses these public inputs to verify a given zero
 46  knowledge proof.
 47  
 48  ### Coin
 49  
 50  During the **Mint** phase we create a new coin $C$, which is bound
 51  to the public key $P$. The coin $C$ is publicly revealed on the
 52  blockchain and added to the Merkle tree.
 53  
 54  Let $v$ be the coin's value, $t$ be the token ID, $\rho$ be the unique
 55  serial number for the coin, and $r_C$ be a random blinding value. We
 56  create a commitment (hash) of these elements and produce the coin $C$
 57  in zero-knowledge:
 58  
 59  $$ C = H(P, v, t, \rho, r_C)$$
 60  
 61  An interesting thing to keep in mind is that this commitment is
 62  extensible, so one could fit an arbitrary amount of different
 63  attributes inside it.
 64  
 65  ### Value and token commitments
 66  
 67  To have some value $v$ for our coin, we ensure it's greater than
 68  zero, and then we can create a Pedersen commitment $V$ where $r_V$
 69  is the blinding factor for the commitment, and $G_1$ and $G_2$ are
 70  two predefined generators:
 71  
 72  $$ v > 0 $$
 73  $$ V = vG_1 + r_VG_2 $$
 74  
 75  The token ID can be thought of as an attribute we append to our coin
 76  so we can have a differentiation of assets we are working with. In
 77  practice, this allows us to work with different tokens, using the
 78  same zero-knowledge proof circuit. For this token ID, we can also
 79  build a Pedersen commitment $T$ where $t$ is the token ID, $r_T$
 80  is the blinding factor, and $G_1$ and $G_2$ are predefined generators:
 81  
 82  $$ T = tG_1 + r_TG_2 $$
 83  
 84  ## Pseudo-code
 85  
 86  Knowing this we can extend our pseudo-code and build the
 87  before-mentioned public inputs for the circuit:
 88  
 89  ```rust
 90  let bincode = include_bytes!("../proof/mint.zk.bin");
 91  let zkbin = ZkBinary::decode(bincode)?;
 92  
 93  // ======
 94  // Prover
 95  // ======
 96  
 97  // Witness values
 98  let value = 42;
 99  let token_id = pallas::Base::random(&mut OsRng);
100  let value_blind = pallas::Scalar::random(&mut OsRng);
101  let token_blind = pallas::Scalar::random(&mut OsRng);
102  let serial = pallas::Base::random(&mut OsRng);
103  let public_key = PublicKey::from_secret(SecretKey::random(&mut OsRng));
104  let (pub_x, pub_y) = public_key.xy();
105  
106  let prover_witnesses = vec![
107      Witness::Base(Value::known(pub_x)),
108      Witness::Base(Value::known(pub_y)),
109      Witness::Base(Value::known(pallas::Base::from(value))),
110      Witness::Base(Value::known(token_id)),
111      Witness::Base(Value::known(serial)),
112      Witness::Scalar(Value::known(value_blind)),
113      Witness::Scalar(Value::known(token_blind)),
114  ];
115  
116  // Create the public inputs
117  let msgs = [pub_x, pub_y, pallas::Base::from(value), token_id, serial];
118  let coin = poseidon_hash(msgs);
119  
120  let value_commit = pedersen_commitment_u64(value, value_blind);
121  let value_coords = value_commit.to_affine().coordinates().unwrap();
122  
123  let token_commit = pedersen_commitment_base(token_id, token_blind);
124  let token_coords = token_commit.to_affine().coordinates().unwrap();
125  
126  let public_inputs = vec![
127      coin,
128      *value_coords.x(),
129      *value_coords.y(),
130      *token_coords.x(),
131      *token_coords.y(),
132  ];
133  
134  // Create the circuit
135  let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
136  
137  let proving_key = ProvingKey::build(13, &circuit);
138  let proof = Proof::create(&proving_key, &[circuit], &public_inputs, &mut OsRng)?;
139  
140  // ========
141  // Verifier
142  // ========
143  
144  // Construct empty witnesses
145  let verifier_witnesses = empty_witnesses(&zkbin);
146  
147  // Create the circuit
148  let circuit = ZkCircuit::new(verifier_witnesses, zkbin);
149  
150  let verifying_key = VerifyingKey::build(13, &circuit);
151  proof.verify(&verifying_key, &public_inputs)?;
152  ```
153  
154  ## Burn
155  
156  ```zkas
157  {{#include ../../../../proof/burn.zk}}
158  ```
159  
160  The `Burn` proof consists of operations similar to the `Mint` proof,
161  with the addition of a _Merkle root_[^2] calculation. In the same
162  manner, we are doing a Poseidon hash instance, we're building Pedersen
163  commitments for the value and token ID, and finally we're doing a
164  public key derivation.
165  
166  In this case, our vector of public inputs could look like:
167  
168  ```
169  let public_inputs = vec![
170      nullifier,
171      *value_coords.x(),
172      *value_coords.y(),
173      *token_coords.x(),
174      *token_coords.y(),
175      merkle_root,
176      *sig_coords.x(),
177      *sig_coords.y(),
178  ];
179  ```
180  
181  ### Nullifier
182  
183  When we spend the coin, we must ensure that the value of the coin
184  cannot be double spent. We call this the _Burn_ phase. The process
185  relies on a nullifier $N$, which we create using the secret key $x$
186  for the public key $P$ and a unique random serial $\rho$. Nullifiers
187  are unique per coin and prevent double spending:
188  
189  $$ N = H(x, \rho) $$
190  
191  
192  ### Merkle root
193  
194  We check that the merkle root corresponds to a coin which is in the
195  Merkle tree $R$
196  
197  $$ C = H(P, v, t, \rho, r_C) $$
198  $$ C \in R $$
199  
200  ### Value and token commitments
201  
202  Just like we calculated these for the `Mint` proof, we do the same
203  here:
204  
205  $$ v > 0 $$
206  $$ V = vG_1 + r_VG_2 $$
207  $$ T = tG_1 + r_TG_2 $$
208  
209  
210  ## Public key derivation
211  
212  We check that the secret key $x$ corresponds to a public key $P$.
213  Usually, we do public key derivation my multiplying our secret key
214  with a genera tor $G$, which results in a public key:
215  
216  $$ P = xG $$
217  
218  
219  ## Pseudo-code
220  
221  Knowing this we can extend our pseudo-code and build the
222  before-mentioned public inputs for the circuit:
223  
224  ```rust
225  let bincode = include_bytes!("../proof/burn.zk.bin");
226  let zkbin = ZkBinary::decode(bincode)?;
227  
228  // ======
229  // Prover
230  // ======
231  
232  // Witness values
233  let value = 42;
234  let token_id = pallas::Base::random(&mut OsRng);
235  let value_blind = pallas::Scalar::random(&mut OsRng);
236  let token_blind = pallas::Scalar::random(&mut OsRng);
237  let serial = pallas::Base::random(&mut OsRng);
238  let secret = SecretKey::random(&mut OsRng);
239  let sig_secret = SecretKey::random(&mut OsRng);
240  
241  // Build the coin
242  let coin2 = {
243      let (pub_x, pub_y) = PublicKey::from_secret(secret).xy();
244      let messages = [pub_x, pub_y, pallas::Base::from(value), token_id, serial];
245      poseidon_hash(messages)
246  };
247  
248  // Fill the merkle tree with some random coins that we want to witness,
249  // and also add the above coin.
250  let mut tree = BridgeTree::<MerkleNode, 32>::new(100);
251  let coin0 = pallas::Base::random(&mut OsRng);
252  let coin1 = pallas::Base::random(&mut OsRng);
253  let coin3 = pallas::Base::random(&mut OsRng);
254  
255  tree.append(&MerkleNode::from(coin0));
256  tree.witness();
257  tree.append(&MerkleNode::from(coin1));
258  tree.append(&MerkleNode::from(coin2));
259  let leaf_pos = tree.witness().unwrap();
260  tree.append(&MerkleNode::from(coin3));
261  tree.witness();
262  
263  let root = tree.root(0).unwrap();
264  let merkle_path = tree.authentication_path(leaf_pos, &root).unwrap();
265  let leaf_pos: u64 = leaf_pos.into();
266  
267  let prover_witnesses = vec![
268      Witness::Base(Value::known(secret.inner())),
269      Witness::Base(Value::known(serial)),
270      Witness::Base(Value::known(pallas::Base::from(value))),
271      Witness::Base(Value::known(token_id)),
272      Witness::Scalar(Value::known(value_blind)),
273      Witness::Scalar(Value::known(token_blind)),
274      Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
275      Witness::MerklePath(Value::known(merkle_path.try_into().unwrap())),
276      Witness::Base(Value::known(sig_secret.inner())),
277  ];
278  
279  // Create the public inputs
280  let nullifier = Nullifier::from(poseidon_hash::<2>([secret.inner(), serial]));
281  
282  let value_commit = pedersen_commitment_u64(value, value_blind);
283  let value_coords = value_commit.to_affine().coordinates().unwrap();
284  
285  let token_commit = pedersen_commitment_base(token_id, token_blind);
286  let token_coords = token_commit.to_affine().coordinates().unwrap();
287  
288  let sig_pubkey = PublicKey::from_secret(sig_secret);
289  let (sig_x, sig_y) = sig_pubkey.xy();
290  
291  let merkle_root = tree.root(0).unwrap();
292  
293  let public_inputs = vec![
294      nullifier.inner(),
295      *value_coords.x(),
296      *value_coords.y(),
297      *token_coords.x(),
298      *token_coords.y(),
299      merkle_root.inner(),
300      sig_x,
301      sig_y,
302  ];
303  
304  // Create the circuit
305  let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
306  
307  let proving_key = ProvingKey::build(13, &circuit);
308  let proof = Proof::create(&proving_key, &[circuit], &public_inputs, &mut OsRng)?;
309  
310  // ========
311  // Verifier
312  // ========
313  
314  // Construct empty witnesses
315  let verifier_witnesses = empty_witnesses(&zkbin);
316  
317  // Create the circuit
318  let circuit = ZkCircuit::new(verifier_witnesses, zkbin);
319  
320  let verifying_key = VerifyingKey::build(13, &circuit);
321  proof.verify(&verifying_key, &public_inputs)?;
322  ```
323  
324  [^1]: See section 3: _The Commitment Scheme_ of Torben Pryds Pedersen's
325      [paper on Non-Interactive and
326      Information-Theoretic Secure Verifiable Secret
327      Sharing](https://link.springer.com/content/pdf/10.1007%2F3-540-46766-1_9.pdf)
328  
329  [^2]: [Merkle tree on Wikipedia](https://en.wikipedia.org/wiki/Merkle_tree)