Skip to content

Commit 6e4834c

Browse files
authored
Merge pull request #2760 from ProvableHQ/evaluate_authorization
[Perf] Speed up authorize by evaluating instead of executing
2 parents 0e0e3c7 + c6c6f5d commit 6e4834c

12 files changed

Lines changed: 212 additions & 60 deletions

File tree

synthesizer/process/src/authorize.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ impl<N: Network> Process<N> {
3030
self.get_stack(program_id)?.authorize::<A, R>(private_key, function_name, inputs, rng)
3131
}
3232

33+
/// Authorizes a call to the program function for the given inputs.
34+
/// Compared to `authorize`, this method does not check for circuit satisfiability of the request.
35+
#[inline]
36+
pub fn authorize_unchecked<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
37+
&self,
38+
private_key: &PrivateKey<N>,
39+
program_id: impl TryInto<ProgramID<N>>,
40+
function_name: impl TryInto<Identifier<N>>,
41+
inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
42+
rng: &mut R,
43+
) -> Result<Authorization<N>> {
44+
// Authorize the call.
45+
self.get_stack(program_id)?.authorize_unchecked::<A, R>(private_key, function_name, inputs, rng)
46+
}
47+
3348
/// Authorizes the fee given the credits record, the fee amount (in microcredits),
3449
/// and the deployment or execution ID.
3550
#[inline]

synthesizer/process/src/evaluate.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ impl<N: Network> Process<N> {
2929

3030
// Retrieve the stack.
3131
let stack = self.get_stack(request.program_id())?;
32+
// Initialize an RNG.
33+
let rng = &mut rand::thread_rng();
3234
// Evaluate the function.
33-
let response = stack.evaluate_function::<A>(CallStack::evaluate(authorization)?, None);
35+
let response = stack.evaluate_function::<A, _>(CallStack::evaluate(authorization)?, None, None, rng);
3436
lap!(timer, "Evaluate the function");
3537

3638
finish!(timer);

synthesizer/process/src/stack/authorize.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,46 @@ impl<N: Network> Stack<N> {
5656
// Return the authorization.
5757
Ok(authorization)
5858
}
59+
60+
/// Authorizes a call to the program function for the given inputs.
61+
/// Compared to `authorize`, this method does not check for circuit satisfiability of the request.
62+
#[inline]
63+
pub fn authorize_unchecked<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
64+
&self,
65+
private_key: &PrivateKey<N>,
66+
function_name: impl TryInto<Identifier<N>>,
67+
inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
68+
rng: &mut R,
69+
) -> Result<Authorization<N>> {
70+
let timer = timer!("Stack::authorize_unchecked");
71+
72+
// Get the program ID.
73+
let program_id = *self.program.id();
74+
// Prepare the function name.
75+
let function_name = function_name.try_into().map_err(|_| anyhow!("Invalid function name"))?;
76+
// Retrieve the input types.
77+
let input_types = self.get_function(&function_name)?.input_types();
78+
lap!(timer, "Retrieve the input types");
79+
// Set is_root to true.
80+
let is_root = true;
81+
82+
// This is the root request and does not have a caller.
83+
let caller = None;
84+
// This is the root request and we do not have a root_tvk to pass on.
85+
let root_tvk = None;
86+
// Compute the request.
87+
let request =
88+
Request::sign(private_key, program_id, function_name, inputs, &input_types, root_tvk, is_root, rng)?;
89+
lap!(timer, "Compute the request");
90+
// Initialize the authorization.
91+
let authorization = Authorization::new(request.clone());
92+
// Construct the call stack.
93+
let call_stack = CallStack::Authorize(vec![request], *private_key, authorization.clone());
94+
// Construct the authorization from the function.
95+
let _response = self.evaluate_function::<A, R>(call_stack, caller, root_tvk, rng)?;
96+
finish!(timer, "Construct the authorization from the function");
97+
98+
// Return the authorization.
99+
Ok(authorization)
100+
}
59101
}

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

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ use synthesizer_program::{
3232

3333
pub trait CallTrait<N: Network> {
3434
/// Evaluates the instruction.
35-
fn evaluate<A: circuit::Aleo<Network = N>>(&self, stack: &Stack<N>, registers: &mut Registers<N, A>) -> Result<()>;
35+
fn evaluate<A: circuit::Aleo<Network = N>, R: CryptoRng + Rng>(
36+
&self,
37+
stack: &Stack<N>,
38+
registers: &mut Registers<N, A>,
39+
rng: &mut R,
40+
) -> Result<()>;
3641

3742
/// Executes the instruction.
3843
fn execute<A: circuit::Aleo<Network = N>, R: CryptoRng + Rng>(
@@ -46,7 +51,12 @@ pub trait CallTrait<N: Network> {
4651
impl<N: Network> CallTrait<N> for Call<N> {
4752
/// Evaluates the instruction.
4853
#[inline]
49-
fn evaluate<A: circuit::Aleo<Network = N>>(&self, stack: &Stack<N>, registers: &mut Registers<N, A>) -> Result<()> {
54+
fn evaluate<A: circuit::Aleo<Network = N>, R: CryptoRng + Rng>(
55+
&self,
56+
stack: &Stack<N>,
57+
registers: &mut Registers<N, A>,
58+
rng: &mut R,
59+
) -> Result<()> {
5060
let timer = timer!("Call::evaluate");
5161

5262
// Load the operands values.
@@ -97,10 +107,38 @@ impl<N: Network> CallTrait<N> for Call<N> {
97107
if function.inputs().len() != inputs.len() {
98108
bail!("Expected {} inputs, found {}", function.inputs().len(), inputs.len())
99109
}
110+
111+
// Get the 'root_tvk'.
112+
let root_tvk = Some(registers.root_tvk()?);
113+
114+
// Get the call stack.
115+
let mut call_stack = registers.call_stack();
116+
117+
// In Authorize mode, we need to compute the new request and push it onto the call stack.
118+
if let CallStack::Authorize(ref mut requests, private_key, authorization) = &mut call_stack {
119+
// Set 'is_root'.
120+
let is_root = false;
121+
// Compute the request.
122+
let request = Request::sign(
123+
private_key,
124+
*substack.program_id(),
125+
*function.name(),
126+
inputs.iter(),
127+
&function.input_types(),
128+
root_tvk,
129+
is_root,
130+
rng,
131+
)?;
132+
// Add the request to the requests.
133+
requests.push(request.clone());
134+
// Add the request to the authorization.
135+
authorization.push(request.clone())?;
136+
};
137+
100138
// Set the (console) caller.
101139
let console_caller = Some(*stack.program_id());
102140
// Evaluate the function.
103-
let response = substack.evaluate_function::<A>(registers.call_stack(), console_caller)?;
141+
let response = substack.evaluate_function::<A, R>(call_stack, console_caller, root_tvk, rng)?;
104142
// Load the outputs.
105143
response.outputs().to_vec()
106144
}
@@ -122,7 +160,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
122160

123161
/// Executes the instruction.
124162
#[inline]
125-
fn execute<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
163+
fn execute<A: circuit::Aleo<Network = N>, R: CryptoRng + Rng>(
126164
&self,
127165
stack: &Stack<N>,
128166
registers: &mut Registers<N, A>,
@@ -212,12 +250,12 @@ impl<N: Network> CallTrait<N> for Call<N> {
212250
// Check if the substack has a proving key or not.
213251
let pk_missing = !substack.contains_proving_key(function.name());
214252

215-
match registers.call_stack() {
253+
match registers.call_stack_ref() {
216254
// If the circuit is in authorize mode, then add any external calls to the stack.
217255
CallStack::Authorize(_, private_key, authorization) => {
218256
// Compute the request.
219257
let request = Request::sign(
220-
&private_key,
258+
private_key,
221259
*substack.program_id(),
222260
*function.name(),
223261
inputs.iter(),
@@ -245,7 +283,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
245283
CallStack::Synthesize(_, private_key, ..) if pk_missing => {
246284
// Compute the request.
247285
let request = Request::sign(
248-
&private_key,
286+
private_key,
249287
*substack.program_id(),
250288
*function.name(),
251289
inputs.iter(),
@@ -271,7 +309,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
271309
CallStack::Synthesize(_, private_key, _) | CallStack::CheckDeployment(_, private_key, ..) => {
272310
// Compute the request.
273311
let request = Request::sign(
274-
&private_key,
312+
private_key,
275313
*substack.program_id(),
276314
*function.name(),
277315
inputs.iter(),
@@ -282,7 +320,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
282320
)?;
283321

284322
// Compute the address.
285-
let address = Address::try_from(&private_key)?;
323+
let address = Address::try_from(private_key)?;
286324

287325
// For each output, if it's a record, compute the randomizer and nonce.
288326
let outputs = function
@@ -339,7 +377,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
339377
CallStack::PackageRun(_, private_key, ..) => {
340378
// Compute the request.
341379
let request = Request::sign(
342-
&private_key,
380+
private_key,
343381
*substack.program_id(),
344382
*function.name(),
345383
inputs.iter(),
@@ -375,8 +413,12 @@ impl<N: Network> CallTrait<N> for Call<N> {
375413
})?;
376414

377415
// Evaluate the function, and load the outputs.
378-
let console_response =
379-
substack.evaluate_function::<A>(registers.call_stack().replicate(), console_caller)?;
416+
let console_response = substack.evaluate_function::<A, R>(
417+
registers.call_stack(),
418+
console_caller,
419+
root_tvk,
420+
rng,
421+
)?;
380422
// Execute the request.
381423
let response =
382424
substack.execute_function::<A, R>(registers.call_stack(), console_caller, root_tvk, rng)?;

synthesizer/process/src/stack/evaluate.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,18 @@ impl<N: Network> Stack<N> {
9797
///
9898
/// # Errors
9999
/// This method will halt if the given inputs are not the same length as the input statements.
100-
pub fn evaluate_function<A: circuit::Aleo<Network = N>>(
100+
pub fn evaluate_function<A: circuit::Aleo<Network = N>, R: CryptoRng + Rng>(
101101
&self,
102-
call_stack: CallStack<N>,
102+
mut call_stack: CallStack<N>,
103103
caller: Option<ProgramID<N>>,
104+
root_tvk: Option<Field<N>>,
105+
rng: &mut R,
104106
) -> Result<Response<N>> {
105107
let timer = timer!("Stack::evaluate_function");
106108

107109
// Retrieve the next request, based on the call stack mode.
108-
let (request, call_stack) = match &call_stack {
110+
let (request, call_stack) = match &mut call_stack {
111+
CallStack::Authorize(..) => (call_stack.pop()?, call_stack),
109112
CallStack::Evaluate(authorization) => (authorization.next()?, call_stack),
110113
// If the evaluation is performed in the `Execute` mode, create a new `Evaluate` mode.
111114
// This is done to ensure that evaluation during execution is performed consistently.
@@ -117,7 +120,9 @@ impl<N: Network> Stack<N> {
117120
let call_stack = CallStack::Evaluate(authorization);
118121
(request, call_stack)
119122
}
120-
_ => bail!("Illegal operation: call stack must be `Evaluate` or `Execute` in `evaluate_function`."),
123+
_ => bail!(
124+
"Illegal operation: call stack must be `Authorize`, `Evaluate` or `Execute` in `evaluate_function`."
125+
),
121126
};
122127
lap!(timer, "Retrieve the next request");
123128

@@ -161,10 +166,16 @@ impl<N: Network> Stack<N> {
161166
registers.set_caller(caller);
162167
// Set the transition view key.
163168
registers.set_tvk(tvk);
169+
// Set the root tvk.
170+
if let Some(root_tvk) = root_tvk {
171+
registers.set_root_tvk(root_tvk);
172+
} else {
173+
registers.set_root_tvk(tvk);
174+
}
164175
lap!(timer, "Initialize the registers");
165176

166177
// Ensure the request is well-formed.
167-
ensure!(request.verify(&function.input_types(), is_root), "Request is invalid");
178+
ensure!(request.verify(&function.input_types(), is_root), "[Evaluate] Request is invalid");
168179
lap!(timer, "Verify the request");
169180

170181
// Store the inputs.
@@ -180,7 +191,7 @@ impl<N: Network> Stack<N> {
180191
// Evaluate the instruction.
181192
let result = match instruction {
182193
// If the instruction is a `call` instruction, we need to handle it separately.
183-
Instruction::Call(call) => CallTrait::evaluate(call, self, &mut registers),
194+
Instruction::Call(call) => CallTrait::evaluate(call, self, &mut registers, rng),
184195
// Otherwise, evaluate the instruction normally.
185196
_ => instruction.evaluate(self, &mut registers),
186197
};
@@ -243,9 +254,18 @@ impl<N: Network> Stack<N> {
243254
outputs,
244255
&function.output_types(),
245256
&output_registers,
246-
);
257+
)?;
247258
finish!(timer);
248259

249-
response
260+
// If the circuit is in `Authorize` mode, then save the transition.
261+
if let CallStack::Authorize(_, _, authorization) = registers.call_stack_ref() {
262+
// Construct the transition.
263+
let transition = Transition::from(&request, &response, &function.output_types(), &output_registers)?;
264+
// Add the transition to the authorization.
265+
authorization.insert_transition(transition)?;
266+
lap!(timer, "Save the transition");
267+
}
268+
269+
Ok(response)
250270
}
251271
}

synthesizer/process/src/stack/execute.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl<N: Network> Stack<N> {
5656
// Store the inputs.
5757
closure.inputs().iter().map(|i| i.register()).zip_eq(inputs).try_for_each(|(register, input)| {
5858
// If the circuit is in execute mode, then store the console input.
59-
if let CallStack::Execute(..) = registers.call_stack() {
59+
if let CallStack::Execute(..) = registers.call_stack_ref() {
6060
use circuit::Eject;
6161
// Assign the console input to the register.
6262
registers.store(self, register, input.eject_value())?;
@@ -69,7 +69,7 @@ impl<N: Network> Stack<N> {
6969
// Execute the instructions.
7070
for instruction in closure.instructions() {
7171
// If the circuit is in execute mode, then evaluate the instructions.
72-
if let CallStack::Execute(..) = registers.call_stack() {
72+
if let CallStack::Execute(..) = registers.call_stack_ref() {
7373
// If the evaluation fails, bail and return the error.
7474
if let Err(error) = instruction.evaluate(self, &mut registers) {
7575
bail!("Failed to evaluate instruction ({instruction}): {error}");
@@ -267,7 +267,7 @@ impl<N: Network> Stack<N> {
267267
// Store the inputs.
268268
function.inputs().iter().map(|i| i.register()).zip_eq(request.inputs()).try_for_each(|(register, input)| {
269269
// If the circuit is in execute mode, then store the console input.
270-
if let CallStack::Execute(..) = registers.call_stack() {
270+
if let CallStack::Execute(..) = registers.call_stack_ref() {
271271
// Assign the console input to the register.
272272
registers.store(self, register, input.eject_value())?;
273273
}
@@ -282,11 +282,11 @@ impl<N: Network> Stack<N> {
282282
// Execute the instructions.
283283
for instruction in function.instructions() {
284284
// If the circuit is in execute mode, then evaluate the instructions.
285-
if let CallStack::Execute(..) = registers.call_stack() {
285+
if let CallStack::Execute(..) = registers.call_stack_ref() {
286286
// Evaluate the instruction.
287287
let result = match instruction {
288288
// If the instruction is a `call` instruction, we need to handle it separately.
289-
Instruction::Call(call) => CallTrait::evaluate(call, self, &mut registers),
289+
Instruction::Call(call) => CallTrait::evaluate(call, self, &mut registers, rng),
290290
// Otherwise, evaluate the instruction normally.
291291
_ => instruction.evaluate(self, &mut registers),
292292
};
@@ -410,7 +410,7 @@ impl<N: Network> Stack<N> {
410410
})?;
411411

412412
// If the circuit is in `Execute` or `PackageRun` mode, then ensure the circuit is satisfied.
413-
if matches!(registers.call_stack(), CallStack::Execute(..) | CallStack::PackageRun(..)) {
413+
if matches!(registers.call_stack_ref(), CallStack::Execute(..) | CallStack::PackageRun(..)) {
414414
// If the circuit is empty or not satisfied, then throw an error.
415415
ensure!(
416416
A::num_constraints() > 0 && A::is_satisfied(),
@@ -425,7 +425,7 @@ impl<N: Network> Stack<N> {
425425
let assignment = A::eject_assignment_and_reset();
426426

427427
// If the circuit is in `Synthesize` or `Execute` mode, synthesize the circuit key, if it does not exist.
428-
if matches!(registers.call_stack(), CallStack::Synthesize(..) | CallStack::Execute(..)) {
428+
if matches!(registers.call_stack_ref(), CallStack::Synthesize(..) | CallStack::Execute(..)) {
429429
// If the proving key does not exist, then synthesize it.
430430
if !self.contains_proving_key(function.name()) {
431431
// Add the circuit key to the mapping.
@@ -434,15 +434,15 @@ impl<N: Network> Stack<N> {
434434
}
435435
}
436436
// If the circuit is in `Authorize` mode, then save the transition.
437-
if let CallStack::Authorize(_, _, authorization) = registers.call_stack() {
437+
if let CallStack::Authorize(_, _, authorization) = registers.call_stack_ref() {
438438
// Construct the transition.
439439
let transition = Transition::from(&console_request, &response, &output_types, &output_registers)?;
440440
// Add the transition to the authorization.
441441
authorization.insert_transition(transition)?;
442442
lap!(timer, "Save the transition");
443443
}
444444
// If the circuit is in `CheckDeployment` mode, then save the assignment.
445-
else if let CallStack::CheckDeployment(_, _, ref assignments, _, _) = registers.call_stack() {
445+
else if let CallStack::CheckDeployment(_, _, ref assignments, _, _) = registers.call_stack_ref() {
446446
// Construct the call metrics.
447447
let metrics = CallMetrics {
448448
program_id: *self.program_id(),
@@ -457,7 +457,7 @@ impl<N: Network> Stack<N> {
457457
lap!(timer, "Save the circuit assignment");
458458
}
459459
// If the circuit is in `Execute` mode, then execute the circuit into a transition.
460-
else if let CallStack::Execute(_, ref trace) = registers.call_stack() {
460+
else if let CallStack::Execute(_, ref trace) = registers.call_stack_ref() {
461461
registers.ensure_console_and_circuit_registers_match()?;
462462

463463
// Construct the transition.
@@ -484,7 +484,7 @@ impl<N: Network> Stack<N> {
484484
)?;
485485
}
486486
// If the circuit is in `PackageRun` mode, then save the assignment.
487-
else if let CallStack::PackageRun(_, _, ref assignments) = registers.call_stack() {
487+
else if let CallStack::PackageRun(_, _, ref assignments) = registers.call_stack_ref() {
488488
// Construct the call metrics.
489489
let metrics = CallMetrics {
490490
program_id: *self.program_id(),

0 commit comments

Comments
 (0)