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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
// Copyright (c) 2019-2026 Provable Inc.
// This file is part of the snarkVM library.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::*;
impl<N: Network> Stack<N> {
/// Deploys the given program ID, if it does not exist.
#[inline]
pub fn deploy<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(&self, rng: &mut R) -> Result<Deployment<N>> {
let timer = timer!("Stack::deploy");
// Ensure the program contains functions.
ensure!(!self.program.functions().is_empty(), "Program '{}' has no functions", self.program.id());
// Initialize a vector for the verifying keys and certificates.
let mut verifying_keys = Vec::with_capacity(self.program.functions().len() + self.program.records().len());
// Synthesize function verifying keys.
for function_name in self.program.functions().keys() {
// Synthesize the proving and verifying key.
self.synthesize_key::<A, R>(function_name, rng)?;
lap!(timer, "Synthesize key for {function_name}");
// Retrieve the proving key.
let proving_key = self.get_proving_key(function_name)?;
// Retrieve the verifying key.
let verifying_key = self.get_verifying_key(function_name)?;
lap!(timer, "Retrieve the keys for {function_name}");
// Certify the circuit.
let certificate = Certificate::certify(&function_name.to_string(), &proving_key, &verifying_key)?;
lap!(timer, "Certify the circuit");
// Add the verifying key and certificate to the bundle.
verifying_keys.push((*function_name, (verifying_key, certificate)));
}
// Synthesize record (translation) verifying keys.
for record_name in self.program.records().keys() {
// Synthesize the proving and verifying key.
self.synthesize_translation_key::<A, R>(record_name, rng)?;
lap!(timer, "Synthesize key for translation circuit for {record_name}");
// Retrieve the proving key.
let proving_key = self.get_proving_key(record_name)?;
// Retrieve the verifying key.
let verifying_key = self.get_verifying_key(record_name)?;
lap!(timer, "Retrieve the keys for translation circuit for {record_name}");
// Certify the circuit.
let certificate = Certificate::certify(&record_name.to_string(), &proving_key, &verifying_key)?;
lap!(timer, "Certify the circuit");
// Add the verifying key and certificate to the bundle.
verifying_keys.push((*record_name, (verifying_key, certificate)));
}
finish!(timer);
// Return the deployment.
// Note: The owner placeholder `Address::zero()` is unconditionally overwritten by
// `VM::deploy()` before the deployment reaches the ledger (the caller's address is set
// for V9+, or the owner is cleared for pre-V9).
Deployment::new(
*self.program_edition,
self.program.clone(),
verifying_keys,
Some(self.program.to_checksum()),
Some(Address::zero()),
)
}
/// Checks each function in the program on the given verifying key and certificate.
#[inline]
pub fn verify_deployment<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
&self,
_consensus_version: ConsensusVersion,
deployment: &Deployment<N>,
rng: &mut R,
) -> Result<()> {
let timer = timer!("Stack::verify_deployment");
// NOTE: As developer, you will likely still want to confirm that your
// deployment is within R1CS constraint and variable limits using
// targeted and parallelized synthesis.
if cfg!(all(feature = "dev_skip_checks", feature = "test_consensus_heights")) {
return Ok(());
}
// Sanity Checks //
// Ensure the deployment is ordered.
deployment.check_is_ordered()?;
// Ensure the program in the stack and deployment matches.
ensure!(&self.program == deployment.program(), "The stack program does not match the deployment program");
// If the deployment contains a checksum, ensure it matches the one computed by the stack.
if let Some(program_checksum) = deployment.program_checksum() {
ensure!(
program_checksum == self.program_checksum,
"The deployment checksum does not match the stack checksum"
);
}
// Check Verifying Keys //
// Get the program ID.
let program_id = self.program.id();
// Check that the number of combined variables does not exceed the deployment limit.
ensure!(deployment.num_combined_variables()? <= N::MAX_DEPLOYMENT_VARIABLES);
// Check that the number of combined constraints does not exceed the deployment limit.
ensure!(deployment.num_combined_constraints()? <= N::MAX_DEPLOYMENT_CONSTRAINTS);
// Construct the call stacks and assignments used to verify the certificates.
let mut call_stacks = Vec::with_capacity(deployment.function_verifying_keys().len());
// Sample a dummy `root_tvk` for circuit synthesis.
let root_tvk = None;
// Sample a dummy `caller` for circuit synthesis.
let caller = None;
// Check that the number of functions matches the number of function verifying keys.
ensure!(
deployment.program().functions().len() == deployment.function_verifying_keys().len(),
"The number of functions in the program does not match the number of function verifying keys"
);
#[cfg(not(any(test, feature = "test")))]
// Skip the certificate verification if the consensus version is before ConsensusVersion::V8.
// Circuit synthesis was changed in a backwards incompatible way in ConsensusVersion::V8.
if (ConsensusVersion::V1..=ConsensusVersion::V7).contains(&_consensus_version) {
finish!(timer);
return Ok(());
}
// Create a seeded rng to use for input value and sub-stack generation.
// This is needed to ensure that the verification results of deployments are consistent across all parties,
// because currently there is a possible flakiness due to overflows in Field to Scalar casting.
let seed = u64::from_bytes_le(&deployment.to_deployment_id()?.to_bytes_le()?[0..8])?;
let mut seeded_rng = rand_chacha::ChaChaRng::seed_from_u64(seed);
// Iterate through the program functions and construct the callstacks and corresponding assignments.
for (function, (_, (verifying_key, _))) in
deployment.program().functions().values().zip_eq(deployment.function_verifying_keys())
{
// Initialize a burner private key.
let burner_private_key = PrivateKey::new(rng)?;
// Compute the burner address.
let burner_address = Address::try_from(&burner_private_key)?;
// Retrieve the input types.
let input_types = function.input_types();
// Retrieve the program checksum, if the program has a constructor.
let program_checksum = match self.program().contains_constructor() {
true => Some(self.program_checksum_as_field()?),
false => None,
};
// Sample the inputs.
let inputs = input_types
.iter()
.map(|input_type| match input_type {
ValueType::ExternalRecord(locator) => {
// Retrieve the external stack.
let stack = self.get_external_stack(locator.program_id())?;
// Sample the input.
stack.sample_value(
&burner_address,
&ValueType::Record(*locator.resource()).into(),
&mut seeded_rng,
)
}
_ => self.sample_value(&burner_address, &input_type.into(), &mut seeded_rng),
})
.collect::<Result<Vec<_>>>()?;
lap!(timer, "Sample the inputs");
// Sample a dummy 'is_root'.
let is_root = true;
// Compute the request, with a burner private key.
let request = Request::sign(
&burner_private_key,
*program_id,
*function.name(),
inputs.into_iter(),
&input_types,
root_tvk,
is_root,
program_checksum,
false,
rng,
)?;
lap!(timer, "Compute the request for {}", function.name());
// Initialize the assignments.
let assignments = Assignments::<N>::default();
// Initialize the constraint limit. Account for the constraint added after synthesis that makes the Varuna zerocheck hiding.
let Some(constraint_limit) = verifying_key.circuit_info.num_constraints.checked_sub(1) else {
// Since a deployment must always pay non-zero fee, it must always have at least one constraint.
bail!("The constraint limit of 0 for function '{}' is invalid", function.name());
};
// Retrieve the variable limit.
let variable_limit = verifying_key.num_variables();
// Initialize the call stack.
let call_stack = CallStack::CheckDeployment(
vec![request],
burner_private_key,
assignments.clone(),
Some(constraint_limit as u64),
Some(variable_limit),
);
// Append the function name, callstack, and assignments.
call_stacks.push((function.name(), call_stack, assignments));
}
// Verify the certificates.
let rngs = (0..call_stacks.len()).map(|_| StdRng::from_seed(seeded_rng.random())).collect::<Vec<_>>();
cfg_into_iter!(call_stacks).zip_eq(deployment.function_verifying_keys()).zip_eq(rngs).try_for_each(
|(((function_name, call_stack, assignments), (_, (verifying_key, certificate))), mut rng)| {
// Synthesize the circuit.
if let Err(err) = self.execute_function::<A, _>(call_stack, caller, root_tvk, &mut rng) {
bail!("Failed to synthesize the circuit for '{function_name}': {err}")
}
// Check the certificate.
match assignments.read().last() {
None => bail!("The assignment for function '{function_name}' is missing in '{program_id}'"),
Some((assignment, _metrics)) => {
// Ensure the certificate is valid.
if !certificate.verify(&function_name.to_string(), assignment, verifying_key) {
bail!("The certificate for function '{function_name}' is invalid in '{program_id}'")
}
}
};
Ok(())
},
)?;
// If the deployment contains translation verifying keys, verify them.
if let Some(translation_verifying_keys) = deployment.translation_verifying_keys() {
// Iterate through the program records and produce translation assignments.
ensure!(
deployment.program().records().len() == translation_verifying_keys.len(),
"The number of records in the program does not match the number of translation verifying keys"
);
let translation_names_assignments = deployment
.program()
.records()
.iter()
.map(|(record_name, _record_type)| {
// Construct a random `TranslationAssignment`.
let program_id = *self.program_id();
let function_id = Field::<N>::from_u64(Uniform::rand(rng));
let record_name = *record_name;
let record_static = self.sample_record(&Address::rand(rng), &record_name, Group::rand(rng), rng)?;
let record_dynamic = DynamicRecord::<N>::from_record(&record_static)?;
let translation_index: u16 = Uniform::rand(rng);
let tvk = Uniform::rand(rng);
let record_register_index = Uniform::rand(rng);
let record_view_key: Option<Field<N>> = UniformExt::rand_option(rng);
let gamma: Option<Group<N>> = UniformExt::rand_option(rng);
let id_dynamic = compute_console_dynamic_or_external_record_id(
function_id,
record_dynamic.to_fields()?,
tvk,
U16::new(record_register_index),
)?;
let is_to_static = Uniform::rand(rng);
let is_external_record = Uniform::rand(rng);
let id_static = Uniform::rand(rng);
lap!(timer, "Sample the inputs to the translation circuit for record {record_name}");
Ok((
record_name,
translation_index,
TranslationAssignment::new(
record_static,
record_dynamic,
program_id,
function_id,
record_name,
is_to_static,
is_external_record,
tvk,
record_view_key,
gamma,
record_register_index,
id_dynamic,
id_static,
),
))
})
.collect::<Result<Vec<_>>>()?;
// Verify the translation certificates.
// Note: `Deployment::check_is_ordered` (called above) ensures that the translation
// verifying keys appear in the same order as the program's record definitions, so
// `zip_eq` correctly pairs each record assignment with its corresponding key.
cfg_into_iter!(translation_names_assignments).zip_eq(translation_verifying_keys).try_for_each(
|((record_name, translation_index, translation_assignment), (_, (verifying_key, certificate)))| {
// Synthesize the circuit.
match translation_assignment.to_circuit_assignment::<A>(translation_index) {
Err(err) => Err(anyhow!("Failed to synthesize the circuit for '{record_name}': {err}")),
Ok(circuit_assignment) => {
// Ensure the certificate is valid.
ensure!(
certificate.verify(&record_name.to_string(), &circuit_assignment, verifying_key),
"The translation-circuit certificate for record '{record_name}' is invalid in '{program_id}'"
);
Ok(())
},
}
})?;
}
finish!(timer);
Ok(())
}
}