Skip to content

Commit c7493c6

Browse files
authored
Merge pull request #2598 from kpandl/optimize_callstack_synthesize
Refactor CallStack::Synthesize to produce consistent dummy outputs for faster nested import deployments
2 parents 2210d99 + 46c8498 commit c7493c6

File tree

9 files changed

+194
-103
lines changed

9 files changed

+194
-103
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

synthesizer/process/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ use synthesizer_program::{
6262
Program,
6363
RegistersLoad,
6464
RegistersStore,
65+
StackKeys,
6566
StackProgram,
6667
};
6768
use synthesizer_snark::{ProvingKey, UniversalSRS, VerifyingKey};

synthesizer/process/src/stack/call/mod.rs

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use synthesizer_program::{
3030
RegistersSignerCircuit,
3131
RegistersStore,
3232
RegistersStoreCircuit,
33+
StackKeys,
3334
StackMatches,
3435
StackProgram,
3536
};
@@ -45,7 +46,7 @@ pub trait CallTrait<N: Network> {
4546
/// Executes the instruction.
4647
fn execute<A: circuit::Aleo<Network = N>, R: CryptoRng + Rng>(
4748
&self,
48-
stack: &(impl StackEvaluate<N> + StackExecute<N> + StackMatches<N> + StackProgram<N>),
49+
stack: &(impl StackEvaluate<N> + StackExecute<N> + StackMatches<N> + StackKeys<N> + StackProgram<N>),
4950
registers: &mut (
5051
impl RegistersCall<N>
5152
+ RegistersSigner<N>
@@ -138,7 +139,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
138139
#[inline]
139140
fn execute<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
140141
&self,
141-
stack: &(impl StackEvaluate<N> + StackExecute<N> + StackMatches<N> + StackProgram<N>),
142+
stack: &(impl StackEvaluate<N> + StackExecute<N> + StackMatches<N> + StackKeys<N> + StackProgram<N>),
142143
registers: &mut (
143144
impl RegistersCall<N>
144145
+ RegistersSigner<N>
@@ -225,11 +226,12 @@ impl<N: Network> CallTrait<N> for Call<N> {
225226

226227
// Set the (console) caller.
227228
let console_caller = Some(*stack.program_id());
229+
// Check if the substack has a proving key or not.
230+
let pk_missing = !substack.contains_proving_key(function.name());
228231

229232
match registers.call_stack() {
230-
// If the circuit is in authorize or synthesize mode, then add any external calls to the stack.
231-
CallStack::Authorize(_, private_key, authorization)
232-
| CallStack::Synthesize(_, private_key, authorization) => {
233+
// If the circuit is in authorize mode, then add any external calls to the stack.
234+
CallStack::Authorize(_, private_key, authorization) => {
233235
// Compute the request.
234236
let request = Request::sign(
235237
&private_key,
@@ -256,7 +258,8 @@ impl<N: Network> CallTrait<N> for Call<N> {
256258
// Return the request and response.
257259
(request, response)
258260
}
259-
CallStack::PackageRun(_, private_key, ..) => {
261+
// If the proving key is missing, build real sub-circuit.
262+
CallStack::Synthesize(_, private_key, ..) if pk_missing => {
260263
// Compute the request.
261264
let request = Request::sign(
262265
&private_key,
@@ -271,16 +274,18 @@ impl<N: Network> CallTrait<N> for Call<N> {
271274

272275
// Retrieve the call stack.
273276
let mut call_stack = registers.call_stack();
277+
274278
// Push the request onto the call stack.
275279
call_stack.push(request.clone())?;
276280

277-
// Evaluate the request.
278-
let response = substack.execute_function::<A, _>(call_stack, console_caller, root_tvk, rng)?;
281+
// Execute the request.
282+
let response = substack.execute_function::<A, R>(call_stack, console_caller, root_tvk, rng)?;
279283

280284
// Return the request and response.
281285
(request, response)
282286
}
283-
CallStack::CheckDeployment(_, private_key, ..) => {
287+
// In Synthesize mode (with an existing proving key) or CheckDeployment mode, we generate dummy outputs to avoid building a full sub-circuit.
288+
CallStack::Synthesize(_, private_key, _) | CallStack::CheckDeployment(_, private_key, ..) => {
284289
// Compute the request.
285290
let request = Request::sign(
286291
&private_key,
@@ -295,32 +300,32 @@ impl<N: Network> CallTrait<N> for Call<N> {
295300

296301
// Compute the address.
297302
let address = Address::try_from(&private_key)?;
298-
// Sample dummy outputs
303+
304+
// For each output, if it's a record, compute the randomizer and nonce.
299305
let outputs = function
300306
.outputs()
301307
.iter()
302308
.map(|output| match output.value_type() {
303309
ValueType::Record(record_name) => {
304-
// Get the register index containing the record.
305310
let index = match output.operand() {
306311
Operand::Register(Register::Locator(index)) => Field::from_u64(*index),
307312
_ => bail!("Expected a `Register::Locator` operand for a record output."),
308313
};
309-
// Compute the encryption randomizer as `HashToScalar(tvk || index)`.
310-
let randomizer = N::hash_to_scalar_psd2(&[*request.tvk(), index])?;
311-
// Construct the record nonce.
312-
let record_nonce = N::g_scalar_multiply(&randomizer);
313-
Ok(Value::Record(substack.sample_record(
314+
// Sample the record.
315+
Ok(Value::Record(substack.sample_record_using_tvk(
314316
&address,
315317
record_name,
316-
record_nonce,
318+
*request.tvk(),
319+
index,
317320
rng,
318321
)?))
319322
}
323+
// For non-record outputs, call sample_value.
320324
_ => substack.sample_value(&address, output.value_type(), rng),
321325
})
322326
.collect::<Result<Vec<_>>>()?;
323-
// Map the output operands to registers.
327+
328+
// Construct the dummy response from these outputs.
324329
let output_registers = function
325330
.outputs()
326331
.iter()
@@ -330,7 +335,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
330335
})
331336
.collect::<Vec<_>>();
332337

333-
// Compute the response.
338+
// Execute the request.
334339
let response = crate::Response::new(
335340
request.network_id(),
336341
substack.program().id(),
@@ -346,6 +351,31 @@ impl<N: Network> CallTrait<N> for Call<N> {
346351
// Return the request and response.
347352
(request, response)
348353
}
354+
// In PackageRun mode, we sign and execute the request once.
355+
CallStack::PackageRun(_, private_key, ..) => {
356+
// Compute the request.
357+
let request = Request::sign(
358+
&private_key,
359+
*substack.program_id(),
360+
*function.name(),
361+
inputs.iter(),
362+
&function.input_types(),
363+
root_tvk,
364+
is_root,
365+
rng,
366+
)?;
367+
368+
// Retrieve the call stack.
369+
let mut call_stack = registers.call_stack();
370+
// Push the request onto the call stack.
371+
call_stack.push(request.clone())?;
372+
373+
// Evaluate the request.
374+
let response = substack.execute_function::<A, _>(call_stack, console_caller, root_tvk, rng)?;
375+
376+
// Return the request and response.
377+
(request, response)
378+
}
349379
// If the circuit is in evaluate mode, then throw an error.
350380
CallStack::Evaluate(..) => {
351381
bail!("Cannot 'execute' a function in 'evaluate' mode.")

synthesizer/process/src/stack/mod.rs

Lines changed: 93 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,82 @@ impl<N: Network> Stack<N> {
223223
}
224224
}
225225

226+
impl<N: Network> StackKeys<N> for Stack<N> {
227+
/// Returns `true` if the proving key for the given function name exists.
228+
#[inline]
229+
fn contains_proving_key(&self, function_name: &Identifier<N>) -> bool {
230+
self.proving_keys.read().contains_key(function_name)
231+
}
232+
233+
/// Returns the proving key for the given function name.
234+
#[inline]
235+
fn get_proving_key(&self, function_name: &Identifier<N>) -> Result<ProvingKey<N>> {
236+
// If the program is 'credits.aleo', try to load the proving key, if it does not exist.
237+
self.try_insert_credits_function_proving_key(function_name)?;
238+
// Return the proving key, if it exists.
239+
match self.proving_keys.read().get(function_name) {
240+
Some(pk) => Ok(pk.clone()),
241+
None => bail!("Proving key not found for: {}/{}", self.program.id(), function_name),
242+
}
243+
}
244+
245+
/// Inserts the given proving key for the given function name.
246+
#[inline]
247+
fn insert_proving_key(&self, function_name: &Identifier<N>, proving_key: ProvingKey<N>) -> Result<()> {
248+
// Ensure the function name exists in the program.
249+
ensure!(
250+
self.program.contains_function(function_name),
251+
"Function '{function_name}' does not exist in program '{}'.",
252+
self.program.id()
253+
);
254+
// Insert the proving key.
255+
self.proving_keys.write().insert(*function_name, proving_key);
256+
Ok(())
257+
}
258+
259+
/// Removes the proving key for the given function name.
260+
#[inline]
261+
fn remove_proving_key(&self, function_name: &Identifier<N>) {
262+
self.proving_keys.write().shift_remove(function_name);
263+
}
264+
265+
/// Returns `true` if the verifying key for the given function name exists.
266+
#[inline]
267+
fn contains_verifying_key(&self, function_name: &Identifier<N>) -> bool {
268+
self.verifying_keys.read().contains_key(function_name)
269+
}
270+
271+
/// Returns the verifying key for the given function name.
272+
#[inline]
273+
fn get_verifying_key(&self, function_name: &Identifier<N>) -> Result<VerifyingKey<N>> {
274+
// Return the verifying key, if it exists.
275+
match self.verifying_keys.read().get(function_name) {
276+
Some(vk) => Ok(vk.clone()),
277+
None => bail!("Verifying key not found for: {}/{}", self.program.id(), function_name),
278+
}
279+
}
280+
281+
/// Inserts the given verifying key for the given function name.
282+
#[inline]
283+
fn insert_verifying_key(&self, function_name: &Identifier<N>, verifying_key: VerifyingKey<N>) -> Result<()> {
284+
// Ensure the function name exists in the program.
285+
ensure!(
286+
self.program.contains_function(function_name),
287+
"Function '{function_name}' does not exist in program '{}'.",
288+
self.program.id()
289+
);
290+
// Insert the verifying key.
291+
self.verifying_keys.write().insert(*function_name, verifying_key);
292+
Ok(())
293+
}
294+
295+
/// Removes the verifying key for the given function name.
296+
#[inline]
297+
fn remove_verifying_key(&self, function_name: &Identifier<N>) {
298+
self.verifying_keys.write().shift_remove(function_name);
299+
}
300+
}
301+
226302
impl<N: Network> StackProgram<N> for Stack<N> {
227303
/// Returns the program.
228304
#[inline]
@@ -355,6 +431,23 @@ impl<N: Network> StackProgram<N> for Stack<N> {
355431
// Return the record.
356432
Ok(record)
357433
}
434+
435+
/// Returns a record for the given record name, deriving the nonce from tvk and index.
436+
fn sample_record_using_tvk<R: Rng + CryptoRng>(
437+
&self,
438+
burner_address: &Address<N>,
439+
record_name: &Identifier<N>,
440+
tvk: Field<N>,
441+
index: Field<N>,
442+
rng: &mut R,
443+
) -> Result<Record<N, Plaintext<N>>> {
444+
// Compute the randomizer.
445+
let randomizer = N::hash_to_scalar_psd2(&[tvk, index])?;
446+
// Construct the record nonce from that randomizer.
447+
let record_nonce = N::g_scalar_multiply(&randomizer);
448+
// Sample the record with that nonce.
449+
self.sample_record(burner_address, record_name, record_nonce, rng)
450+
}
358451
}
359452

360453
impl<N: Network> StackProgramTypes<N> for Stack<N> {
@@ -373,82 +466,6 @@ impl<N: Network> StackProgramTypes<N> for Stack<N> {
373466
}
374467
}
375468

376-
impl<N: Network> Stack<N> {
377-
/// Returns `true` if the proving key for the given function name exists.
378-
#[inline]
379-
pub fn contains_proving_key(&self, function_name: &Identifier<N>) -> bool {
380-
self.proving_keys.read().contains_key(function_name)
381-
}
382-
383-
/// Returns `true` if the verifying key for the given function name exists.
384-
#[inline]
385-
pub fn contains_verifying_key(&self, function_name: &Identifier<N>) -> bool {
386-
self.verifying_keys.read().contains_key(function_name)
387-
}
388-
389-
/// Returns the proving key for the given function name.
390-
#[inline]
391-
pub fn get_proving_key(&self, function_name: &Identifier<N>) -> Result<ProvingKey<N>> {
392-
// If the program is 'credits.aleo', try to load the proving key, if it does not exist.
393-
self.try_insert_credits_function_proving_key(function_name)?;
394-
// Return the proving key, if it exists.
395-
match self.proving_keys.read().get(function_name) {
396-
Some(proving_key) => Ok(proving_key.clone()),
397-
None => bail!("Proving key not found for: {}/{function_name}", self.program.id()),
398-
}
399-
}
400-
401-
/// Returns the verifying key for the given function name.
402-
#[inline]
403-
pub fn get_verifying_key(&self, function_name: &Identifier<N>) -> Result<VerifyingKey<N>> {
404-
// Return the verifying key, if it exists.
405-
match self.verifying_keys.read().get(function_name) {
406-
Some(verifying_key) => Ok(verifying_key.clone()),
407-
None => bail!("Verifying key not found for: {}/{function_name}", self.program.id()),
408-
}
409-
}
410-
411-
/// Inserts the given proving key for the given function name.
412-
#[inline]
413-
pub fn insert_proving_key(&self, function_name: &Identifier<N>, proving_key: ProvingKey<N>) -> Result<()> {
414-
// Ensure the function name exists in the program.
415-
ensure!(
416-
self.program.contains_function(function_name),
417-
"Function '{function_name}' does not exist in program '{}'.",
418-
self.program.id()
419-
);
420-
// Insert the proving key.
421-
self.proving_keys.write().insert(*function_name, proving_key);
422-
Ok(())
423-
}
424-
425-
/// Inserts the given verifying key for the given function name.
426-
#[inline]
427-
pub fn insert_verifying_key(&self, function_name: &Identifier<N>, verifying_key: VerifyingKey<N>) -> Result<()> {
428-
// Ensure the function name exists in the program.
429-
ensure!(
430-
self.program.contains_function(function_name),
431-
"Function '{function_name}' does not exist in program '{}'.",
432-
self.program.id()
433-
);
434-
// Insert the verifying key.
435-
self.verifying_keys.write().insert(*function_name, verifying_key);
436-
Ok(())
437-
}
438-
439-
/// Removes the proving key for the given function name.
440-
#[inline]
441-
pub fn remove_proving_key(&self, function_name: &Identifier<N>) {
442-
self.proving_keys.write().shift_remove(function_name);
443-
}
444-
445-
/// Removes the verifying key for the given function name.
446-
#[inline]
447-
pub fn remove_verifying_key(&self, function_name: &Identifier<N>) {
448-
self.verifying_keys.write().shift_remove(function_name);
449-
}
450-
}
451-
452469
impl<N: Network> Stack<N> {
453470
/// Inserts the proving key if the program ID is 'credits.aleo'.
454471
fn try_insert_credits_function_proving_key(&self, function_name: &Identifier<N>) -> Result<()> {

synthesizer/program/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ version = "0.3"
5757
version = "1.0"
5858
features = [ "preserve_order" ]
5959

60+
[dependencies.synthesizer-snark]
61+
package = "snarkvm-synthesizer-snark"
62+
path = "../snark"
63+
version = "=1.2.1"
64+
6065
[dev-dependencies.bincode]
6166
version = "1"
6267

0 commit comments

Comments
 (0)