Skip to content

Commit 8788b96

Browse files
committed
Prv fn example update+doc
1 parent 36a93c0 commit 8788b96

File tree

6 files changed

+206
-63
lines changed

6 files changed

+206
-63
lines changed

.vscode/settings.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

docs/docs/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ The Private eXecution Environment (PXE) within the `aztec-wallet` will be used.
1414

1515
## Contributions
1616

17-
Example in this repo are heavily curated to showcase and explain atomic functionality of Aztec, as well as slightly larger examples for broader concepts.
17+
Examples in this repo are heavily curated to showcase and explain atomic functionality of Aztec, as well as slightly larger examples for broader concepts.
1818

19-
Developers: Pull requests contining examples with code AND documentation that fit the brief will be considered.
19+
Developers: Pull requests containing examples with code AND documentation that fit the brief will be considered.

hello/Nargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ type = "contract"
44
authors = [""]
55

66
[dependencies]
7-
aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.87.8", directory = "noir-projects/aztec-nr/aztec" }
7+
aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.87.9", directory = "noir-projects/aztec-nr/aztec" }

hello/hello.md

Lines changed: 120 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ aztec-nargo new --contract hello
2424
```bash
2525
# Add Aztec dependency to `hello/Nargo.toml`
2626
cd hello
27-
echo 'aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.87.8", directory = "noir-projects/aztec-nr/aztec" }' >> Nargo.toml
27+
echo 'aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v0.87.9", directory = "noir-projects/aztec-nr/aztec" }' >> Nargo.toml
2828
```
2929

3030
```bash
@@ -37,26 +37,49 @@ pub contract Hello {
3737
}' > src/main.nr
3838
```
3939

40-
### Add a private function
40+
### Division example
4141

42-
Inside the empty contract, add the following functions and their requisite macro imports:
42+
For demonstration purposes we'll show an implementation of division that is efficient for proving in private, but has an extra cost in public.
43+
44+
Inside the empty contract, we'll add the same function as public and private; outside will have the helper function.
4345

4446
```rust
45-
// ...
47+
#[aztec]
48+
pub contract Hello {
49+
use crate::divide; // Noir helper function outside of contract
50+
4651
// import function types
4752
use dep::aztec::macros::functions::{private, public};
4853

4954
#[private] // use macro to wrap for private execution
50-
fn mul_8_prv(num: u32) -> u32 {
51-
num * 8 // more efficient for proving
55+
fn div_prv(dividend: u32, divisor: u32) -> u32 {
56+
//Safety: constrain after
57+
let (quotient, remainder) = unsafe { divide(dividend, divisor) };
58+
assert(quotient * divisor + remainder == dividend);
59+
quotient
5260
}
5361

5462
#[public] // use macro to wrap for public execution
55-
fn mul_8_exe(num: u32) -> u32 {
56-
num << 3 // more efficient for execution
63+
fn div_exe(dividend: u32, divisor: u32) -> u32 {
64+
let (quotient, remainder) = divide(dividend, divisor);
65+
quotient
5766
}
58-
59-
// ...
67+
} // contract Hello
68+
69+
// iterative divide function
70+
pub unconstrained fn divide(dividend: u32, divisor: u32) -> (u32, u32) {
71+
let mut quotient: u32 = 0;
72+
let mut remainder: u32 = dividend;
73+
if divisor == 0 {
74+
(0, 0)
75+
} else {
76+
while remainder >= divisor {
77+
remainder = remainder - divisor;
78+
quotient = quotient + 1;
79+
}
80+
(quotient, remainder)
81+
}
82+
}
6083
```
6184

6285
## Understanding performance
@@ -65,7 +88,7 @@ Since private functions are executed client side and the proof of execution is u
6588

6689
Public functions are executed as part of the protocol, so the total cost of operations is important, which is measured in gas.
6790

68-
We can compile and then calculate the gates required for this private function.
91+
We can compile and then calculate the gates required for the private function, `div_prv`. To see gas for the public function, we will deploy the contract and call `div_exe`.
6992

7093
## Building the project
7194
### Compile the contract
@@ -82,12 +105,36 @@ make
82105
The gate flamegraph of a specific function can be calculate and presented by passing the function name to:
83106

84107
```bash
85-
make gate-flamegraph <function-name>
108+
make gate-flamegraph div_prv
86109
```
87110

88-
eg: `make gate-flamegraph mul_8_prv`
111+
In the terminal you'll see: `Opcode count: 775, Total gates by opcodes: 5197, Circuit size: 5962`
112+
113+
Also go to the URL in the command output to see the gate count total, and of each sub-section. The exported .svg file is in the `target` directory.
114+
115+
### Unconstrained cost in private
89116

90-
Go to the URL in the command output to see the gate count total, and of each sub-section. The exported .svg file is in the `target` directory.
117+
To assess the cost of the unconstrained function in private, lets replace its call with a result directly, eg `(2, 2)`
118+
119+
```rust
120+
#[private] // use macro to wrap for private execution
121+
fn div_prv(dividend: u32, divisor: u32) -> u32 {
122+
//Safety: constrain after
123+
let (quotient, remainder) = (2, 2); //unsafe { divide(dividend, divisor) };
124+
assert(quotient * divisor + remainder == dividend);
125+
quotient
126+
}
127+
128+
```
129+
130+
Now calculating the gates flamegraph again:
131+
```bash
132+
make gate-flamegraph div_prv
133+
```
134+
135+
In the terminal you'll see the same counts: `Opcode count: 775, Total gates by opcodes: 5197, Circuit size: 5962`
136+
137+
That is, the unconstrained function does NOT contribute to gate count in the private function. So the result from this unconstrained function must then be verified in the calling constrained function (via `assert`).
91138

92139
## Using the project
93140
### Deploy contract to Aztec dev node
@@ -115,7 +162,7 @@ aztec-wallet deploy --no-init target/hello-Hello.json --from test0 --alias hello
115162

116163
Note:
117164

118-
- `no-init` is specified because we do not have or need a constractor/`initializer` for this example
165+
- `no-init` is specified because we do not have or need a constructor/`initializer` for this example
119166
- The last param requests the deployed contract address be aliased to `hello`
120167

121168
### Command summary script
@@ -125,24 +172,75 @@ For convenience/reference, these commands are consolidated in a script. To see t
125172
./run.sh --help
126173
```
127174

128-
### Profile gate count
175+
### Showing gas cost for a transaction
129176

130-
To see a breakdown of proving times and gate counts per inner-circuit:
177+
With the sandbox running and contract deployed (see earlier section), we can now interact with the public function:
131178

132179
```bash
133-
./run.sh gate-profile mul_8_prv 8
180+
./run.sh hello-fn div_exe 8 3
134181
```
135182

136-
This command expects the contract in `hello.nr` to be deployed, and the contract address aliased to `hello`.
183+
```bash
184+
Transaction has been mined
185+
Tx fee: 39680550
186+
...
187+
```
137188

138-
This uses the deployed contract to provide a breakdown of time and gates for each inner function. This will become useful when comparing
189+
Lets assess the cost of the unconstrained function in public by replacing the call with a result:
139190

140-
## Compare implementations
191+
```rust
192+
let (quotient, remainder) = (2, 2); // divide(dividend, divisor);
193+
```
141194

195+
Compiling, deploying, then calling:
142196

197+
```bash
198+
make
199+
./run.sh hello-deploy
200+
./run.sh hello-fn div_exe 8 3
201+
```
202+
203+
The result will show a lower gas cost:
204+
```bash
205+
Transaction has been mined
206+
Tx fee: 33770160
207+
```
208+
209+
That is, the unconstrained function call from public contributes to the cost.
210+
211+
### Comparison
212+
213+
The above example highlights that coding things well in private may have unexpected costs if used in public.
214+
215+
Another example of this is in the use of memory. This is cheaper in private circuits (witness values) and expensive in public (operations to read across different memory locations).
216+
217+
## Further use
218+
219+
### Testing output
143220

144-
## Calling private functions
221+
For a quick sneak peak into testing, see the functions after the Noir divide function: `setup()` and `test_funcs()`. Notice the more explicity way of calling a function from a private or public context.
222+
223+
Tests are simply run with the command: `aztec test`
224+
225+
Under the hood this does two things:
226+
- Starts a Testing eXecution Environment - `aztec start --txe --port=8081`
227+
- Runs nargo tests pointing to the txe as an oracle - `nargo test --silence-warnings --pedantic-solving --oracle-resolver http://127.0.0.1:8081`
228+
229+
We'll modify the second command to show the `println` output:
230+
- Start TXE - `aztec start --txe --port=8081` (in a separate terminal)
231+
- Test - `nargo test --oracle-resolver http://127.0.0.1:8081 --show-output`
232+
233+
### Profile gate count
234+
235+
To see a breakdown of proving times and gate counts per inner-circuit:
236+
237+
```bash
238+
./run.sh gate-profile div_prv 8 3
239+
```
240+
241+
This command expects the contract in `hello.nr` to be deployed, and the contract address aliased to `hello`.
145242

243+
This uses the deployed contract to provide a breakdown of time and gates for each inner function. This will become useful when comparing calls into contexts.
146244

147245

148246
## Further reading

hello/run.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Commands:
1111
deploy-hello Deploy the hello contract without initialization
1212
gate-flamegraph <function_name> Generate gate flamegraph for the specified private function
1313
gate-profile <function_name> [args...] Show total gates for the specified private function with optional arguments
14+
hello-sim-fn <function_name> [args...] Simulate a function call on deployed hello contract
15+
hello-fn <function_name> [args...] Call a function on deployed hello contract
1416
-h, --help Show this help message
1517
1618
Examples:
@@ -19,6 +21,8 @@ Examples:
1921
$0 deploy-hello
2022
$0 gate-flamegraph myFunction
2123
$0 gate-profile myFunction 1 2 3
24+
$0 hello-sim-fn myFunction 1 2 3
25+
$0 hello-fn myFunction 1 2 3
2226
EOF
2327
}
2428

@@ -57,6 +61,24 @@ case "$COMMAND" in
5761
fn="$1"; shift
5862
aztec-wallet profile "$fn" --args "$@" --contract-address hello -f test0
5963
;;
64+
hello-sim-fn)
65+
if [[ $# -lt 1 ]]; then
66+
echo "Error: function_name required for call"
67+
usage
68+
exit 1
69+
fi
70+
fn="$1"; shift
71+
aztec-wallet simulate "$fn" --args "$@" --contract-address hello -f test0
72+
;;
73+
hello-fn)
74+
if [[ $# -lt 1 ]]; then
75+
echo "Error: function_name required for call"
76+
usage
77+
exit 1
78+
fi
79+
fn="$1"; shift
80+
aztec-wallet send "$fn" --args "$@" --contract-address hello -f test0
81+
;;
6082
*)
6183
echo "Unknown command: $COMMAND"
6284
usage

hello/src/main.nr

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,71 @@ use dep::aztec::macros::aztec;
22

33
#[aztec]
44
pub contract Hello {
5+
use crate::divide;
56

6-
use dep::aztec::macros::functions::{private, public, utility};
7-
use dep::aztec::prelude::{AztecAddress, FunctionSelector};
7+
// import function types
8+
use dep::aztec::macros::functions::{private, public};
89

9-
#[private]
10-
pub fn mul_8_exe(num: u32) -> u32 {
11-
num << 3
12-
}
10+
#[private] // use macro to wrap for private execution
11+
fn div_prv(dividend: u32, divisor: u32) -> u32 {
12+
//Safety: constrain after
13+
let (quotient, remainder) = unsafe { divide(dividend, divisor) };
14+
assert(quotient * divisor + remainder == dividend);
15+
quotient
16+
} // tx fee: 567952000 (with unconstrained divide)
1317

14-
#[private]
15-
pub fn mul_8_prv(num: u32) -> u32 {
16-
num * 8
17-
}
18+
#[public] // use macro to wrap for public execution
19+
fn div_exe(dividend: u32, divisor: u32) -> u32 {
20+
let (quotient, remainder) = divide(dividend, divisor);
21+
quotient
22+
} // tx fee: 1945438440 (with unconstrained divide)
23+
24+
}
1825

19-
#[private]
20-
fn math_ops_3_prv(num: u32) -> u32 {
21-
let mut result = num;
22-
result = context
23-
.call_private_function(
24-
context.this_address(),
25-
FunctionSelector::from_signature("mul_8_prv"),
26-
[result as Field],
27-
)
28-
.get_preimage();
29-
result = context
30-
.call_private_function(
31-
context.this_address(),
32-
FunctionSelector::from_signature("mul_8_prv"),
33-
[result as Field],
34-
)
35-
.get_preimage();
36-
result = context
37-
.call_private_function(
38-
context.this_address(),
39-
FunctionSelector::from_signature("mul_8_prv"),
40-
[result as Field],
41-
)
42-
.get_preimage();
43-
result
26+
pub unconstrained fn divide(dividend: u32, divisor: u32) -> (u32, u32) {
27+
let mut quotient: u32 = 0;
28+
let mut remainder: u32 = dividend;
29+
if divisor == 0 {
30+
(0, 0)
31+
} else {
32+
while remainder >= divisor {
33+
remainder = remainder - divisor;
34+
quotient = quotient + 1;
35+
}
36+
(quotient, remainder)
4437
}
38+
}
39+
40+
//////TESTING//////
41+
use dep::aztec::prelude::AztecAddress;
42+
use dep::aztec::test::helpers::test_environment::TestEnvironment;
43+
44+
#[test]
45+
unconstrained fn test_funcs() {
46+
let (env, hello_contract_address, user) = setup();
47+
env.advance_block_by(1);
48+
49+
// let result = env.call_private(contract_address, Hello::interface().math_ops_3_prv(8));
50+
let result = Hello::at(hello_contract_address).div_prv(8, 3).call(&mut env.private());
51+
println(result); // can use println in txe tests, but not aztec functions called by tests. debug_log doesn't work.
52+
assert(result == 2, "Expected 2 but got {result}");
53+
54+
let result = Hello::at(hello_contract_address).div_exe(8, 3).call(&mut env.public());
55+
println(result); // can use println in txe tests, but not aztec functions called by tests. debug_log doesn't work.
56+
assert(result == 2, "Expected 2 but got {result}");
57+
}
58+
59+
pub unconstrained fn setup() -> (&mut TestEnvironment, AztecAddress, AztecAddress) {
60+
// Setup env, generate keys
61+
let mut env = TestEnvironment::new();
62+
let user = env.create_account_contract(1);
63+
64+
// Start the test in the account contract address
65+
env.impersonate(user);
66+
67+
// Deploy token contract
68+
let hello_contract = env.deploy_self("Hello").without_initializer();
69+
let hello_contract_address = hello_contract.to_address();
4570

71+
(&mut env, hello_contract_address, user)
4672
}

0 commit comments

Comments
 (0)