1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use dep::std;
// tx circuit + publisher
fn main(
tx_in: pub [Field; 16], // Publicly posted txs for forced inclusion
amount_public_in: pub Field,
amount_public_out: pub Field,
commitment_out: pub [Field; 16], // tx[0] -- [...h(utxo)] --> ONLY CONTAINS FIRST TX COMMITMENTS
recipient: pub Field,
oracle: pub Field,
old_root: pub Field, // [Publisher] Feed in from contract
new_root: pub Field, // Calculate and Return
nullifier_hashes: pub [Field; 16],
secrets: [Field; 16],
utxo_in: [Field; 48], // 16 * [owner, amount, asset_type]
utxo_out: [Field; 48], // 16 * [owner, amount, asset_type]
roots: [Field; 64], // 16 * [utxo_root, tx_root, batch_root, historic_root]
leaves: [Field; 64], // 16 * [utxo_leaf, tx_leaf, batch_leaf, historic_leaf]
indexes: [Field; 64], // 16 * [utxo_index, tx_index, batch_index, historic_index]
hash_path: [Field; 288], // 16 * [utxo_path, tx_path, batch_path, historic_path]
) { // -> [Field; 16] {
let trees: Field = 4; // UTXO->Tx->Batch->HistoricalState->CurrentState
// Initialize input and output tallies as public amounts
let mut sum_in: Field = amount_public_in;
let mut sum_out: Field = amount_public_out;
// Iterate over inputs
for i in 0..16 {
if (utxo_in[i*3 + 1] != 0) {
// Assert h(secret) == owner - make sure user can spend this utxo
let owner = std::hash::pedersen([secrets[i]]);
assert(owner[0] == utxo_in[i*3 + 0]);
// Nullifier is h(secret, secret) to avoid leaking spender - maybe use h(comm, secret) or something else later
assert(nullifier_hashes[i] == std::hash::pedersen([secrets[i], secrets[i]])[0]);
let commitment_in = std::hash::pedersen([utxo_in[i*3 + 0], utxo_in[i*3 + 1], utxo_in[i*3 + 2]])[0];
// Initialize hash path arrays
let mut hash_path_utxo: [Field; 4] = [0; 4]; // UTXO->Tx Limit
let mut hash_path_tx: [Field; 4] = [0; 4]; // Tx->Batch Limit
let mut hash_path_batch: [Field; 5] = [0; 5]; // Batch->State Limit
let mut hash_path_historic: [Field; 5] = [0; 5]; // Total State Limit
for j in 0..4 { // 4 levels per hash path (for now)
hash_path_utxo[j] = hash_path[18*i + 0 + j];
hash_path_tx[j] = hash_path[18*i + 4 + j];
}
for l in 0..5 { // 5 levels for batch and historic trees (for now)
hash_path_batch[l] = hash_path[18*i + 8 + l];
hash_path_historic[l] = hash_path[18*i + 13 + l];
}
let leaf_tx = leaves[trees * i + 1];
let leaf_batch = leaves[trees * i + 2];
let leaf_historic = leaves[trees * i + 3];
let index_utxo = indexes[trees * i + 0];
let index_tx = indexes[trees * i + 1];
let index_batch = indexes[trees * i + 2];
let index_historic = indexes[trees * i + 3];
// h([...utxo]) == root_utxo / tx_id
// h([...tx]) == root_batch
// h([...root_batch]) == current_root
// h([...root_leaves]) == historic_root
// leaf_batch = h(root_tx, oracle)
// leaf_historic = h()
//let root_utxo = roots[trees * i + 0];
let root_tx = roots[trees * i + 1];
let root_batch = roots[trees * i + 2];
let root_historic = roots[trees * i + 3];
// fn compute_merkle_root(
// leaf : Field,
// index : Field,
// hash_path: [Field]
// ) -> Field
// utxo_root == tx_id
let utxo_root = std::merkle::compute_merkle_root(
commitment_in,
index_utxo,
hash_path_utxo
);
assert(utxo_root == leaf_tx);
let tx_root = std::merkle::compute_merkle_root(
leaf_tx,
index_tx,
hash_path_tx
);
assert(tx_root == root_tx);
let batch_root = std::merkle::compute_merkle_root(
leaf_batch,
index_batch,
hash_path_batch
);
assert(batch_root == root_batch);
let historic_root = std::merkle::compute_merkle_root(
leaf_historic,
index_historic,
hash_path_historic
);
assert(historic_root == root_historic);
// Add utxo value to input total
sum_in += utxo_in[i*3 + 1];
}
}
// Iterate over outputs
for k in 0..16 {
if (utxo_out[k*3 + 1] != 0) {
let commitment_out_calc = std::hash::pedersen([utxo_out[k*3 + 0], utxo_out[k*3 + 1], utxo_out[k*3 + 2]]);
// Constraint check here to prevent loss of funds
assert(commitment_out_calc[0] == commitment_out[k]);
sum_out += utxo_out[k*3 + 1];
}
else {
let zero_hash = 0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725 as Field;
assert(commitment_out[k] == zero_hash);
}
}
// [Publisher] Construct the new root
// - newRoot->h(oldRoot, h(h([...h([...utxo])]), h(oracleData)))
// - batchRoot->h(batch, h(oracleData))
// - batch->h([...tx]
// - tx->h([...utxo])
// Find Tx aka UTXORoot (UTXO->Tx)]
let utxo_root_calc: Field = pedersen_tree_four(commitment_out);
assert(tx_in[0] == utxo_root_calc); // TODO: Check all indices
let tx_root_calc: Field = pedersen_tree_four(tx_in); // Only works for MAX_BATCH_SIZE = 16
assert(oracle == std::hash::pedersen([0])[0]); // TODO: Assert the actual data matches the assets contract
let batch_root_calc: Field = std::hash::pedersen([tx_root_calc, oracle])[0];
let new_root_calc: Field = std::hash::pedersen([batch_root_calc, old_root])[0];
assert(new_root == new_root_calc); // Make sure we submitted the correct h(old_root, batch)
// root = pedersen(Batch, Oracle) -- Oracle is zero_values[0] for now
// newRoot = pedersen(root, oldRoot)
// Check the final sums
assert(sum_in == sum_out);
// Silence the warning. The compiler probably optimizes this out anyway
assert(recipient == recipient);
}
fn pedersen_tree_four(leaves: [Field; 16]) -> Field {
//let mut num_hashes: u8 = 8; // make this leaves.len() / 2 ?
let mut tx_tree: [Field; 16] = leaves; // make this length num_hashes ?
for l in 0..8 {
tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0];
}
for l in 0..4 {
tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0];
}
for l in 0..2 {
tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0];
}
std::hash::pedersen([tx_tree[0], tx_tree[1]])[0] // root
}