Skip to content

Commit

Permalink
solving conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
luiz-lvj committed Oct 17, 2024
2 parents 51cfa1c + 584cad6 commit 9b20cc3
Show file tree
Hide file tree
Showing 38 changed files with 5,181 additions and 349 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
Garaga enables efficient elliptic curve operations on Starknet.
It achieves state of the art performance by
- using a dedicated builtin made by Starkware for emulated modular arithmetic
- using a suite of non-deterministic techniques for extension field multiplication, pairings, and multi scalar multiplication to reduce the number of steps to verifiy results.
- using a suite of non-deterministic techniques for extension field multiplication, pairings, and multi scalar multiplication to reduce the number of steps to verify results.

Garaga currently supports:
- Scalar & Multi-scalar multiplication for any Weirstrass curve, including BN254, BLS12_381, SECP256/R1, and ED25519. You can add the one you need by specifying the curve parameters.
Expand Down
123 changes: 122 additions & 1 deletion docs/gitbook/installation/npm-package.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,124 @@
# Npm package

soon
## Installation via NPM Registry (recommended)

The easiest way to install Garaga is via your prefered Node.js package manager, such as `npm` or `yarn`.

1. Open your terminal or command prompt.
2. Run the following command:

```bash
npm i -S garaga
```

or

```bash
yarn add garaga
```

## Building the package from source code

The package can be build directly from source code by cloning the garaga repository. Make sure you have both [Rust](https://www.rust-lang.org/tools/install) and [Node.js](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs) installed in you machine.

1. Open your terminal or command prompt.
2. Install `wasm-pack` by running:

```bash
cargo install wasm-pack
```

3. Run the following commands:

```bash
git clone https://github.com/keep-starknet-strange/garaga.git
cd tools/npm/garaga_ts
npm ci
npm run build
npm pack
```

4. The .tgz file with the package contents will be available in the current folder.
5. Install the .tgz file in your project

```bash
npm i -S <path-to-tgz-package-file>
```

For reproducible builds, one can use instead docker compose. Make sure [docker](https://docs.docker.com/engine/install/) is installed in you machine.

1. Open your terminal or command prompt.
2. Run the following commands:

```bash
git clone https://github.com/keep-starknet-strange/garaga.git
cd tools/npm/garaga_ts
docker compose up --build
```

3. The .tgz file with the package contents will be available in the current folder.
4. Install the .tgz file in your project

```bash
npm i -S <path-to-tgz-package-file>
```

## Development notes

The Garaga NPM package is a mixed package. It is implemented in TypeScript but also reuses Rust code targeted to WebAssembly (WASM) with the help of [`wasm-pack`](https://rustwasm.github.io/wasm-pack/).

The `src` folder is organized into two subfolders: `node` which contains the implementation in TypeScript; and `wasm` which has the interoperabilty code produced by `wasm-pack`.

Changes to the TypeScript library should only be made to files under the `node` subfolder. Changes to the Rust implementation requires regenerating files under the `wasm` subfolder.

Onces changes are in place they can be made permanent into the repository by committing the contents of both folders. Here is the bulk of the process:

1. Open your terminal or command prompt.
2. Use `git` to clone the repository:

```bash
git clone https://github.com/keep-starknet-strange/garaga.git
cd tools/npm/garaga_ts
npm ci
```

3. If you make TypeScript only changes, you can quickly rebuild the package using the `build:node` NPM script:

```bash
npm run build:node
npm pack
```

4. If instead you make Rust changes, it is necessary to generate the WASM interoperability code using the `build` NPM script:

```bash
npm run build
npm pack
```

5. However, before commiting changes, it is necessary to generate the WASM interoperability code in a reproducible manner using docker:

```bash
docker compose up --build
git commit .
```
### How `wasm-pack` is used to achieve interoperability

Internaly the `build` NPM script uses `wasm-pack` to produce the WASM interoperability code. This is achieved by running

```bash
cd tools/garaga_rs && wasm-pack build --target web --out-dir ../npm/garaga_ts/src/wasm/pkg --release --no-default-features
cd tools/npm/garaga_ts && node patch.wasm.cjs
```
Let's unpack it.
In the Rust source folder we run `wasm-pack` in `--target web` mode. This generates TypeScript code targeting web pages.
The `--release` option is required to minimize the size of the WASM module.
And the `--no-default-features` disables the need to build non WASM features of garaga_rs.
Once the `wasm-pack` is done, the code is generated under the folder `src/wasm/pkg` of garaga_ts that houses the TypeScript source code.
We then run a custom script `patch.wasm.cjs` which makes minimal changes to the code generated by wasm-pack to facilitate seamless support of the WASM module in both the browser and Node.js.
Basically it converts the WASM module to a [Base64](https://en.wikipedia.org/wiki/Base64) string that can be loaded in a portable way in both environments, amongst other minor tweaks.
(It is important to note that the use of a custom script is only required so long `wasm-pack` itself does not provide a more portable/universal target mode.)
131 changes: 44 additions & 87 deletions hydra/garaga/extension_field_modulo_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ def create_powers_of_Z(
max_degree = self.extension_degree
if isinstance(Z, PyFelt):
Z = self.write_cairo_native_felt(Z)
elif isinstance(Z, int):
Z = self.write_cairo_native_felt(self.field(Z))
elif isinstance(Z, ModuloCircuitElement):
pass
else:
Expand Down Expand Up @@ -566,100 +568,64 @@ def update_RHS_state(
)
return

def get_Z_and_nondeterministic_Q(
self, extension_degree: int, mock: bool = False
) -> tuple[PyFelt, tuple[list[PyFelt], list[PyFelt]]]:
nondeterministic_Qs = [Polynomial([self.field.zero()]) for _ in range(2)]
# Start by hashing circuit input
if self.hash_input:
self.transcript.hash_limbs_multi(self.circuit_input)

def finalize_circuit(
self,
extension_degree: int = None,
mock=False,
):
######### Flags #########
extension_degree = extension_degree or self.extension_degree
double_extension = self.accumulate_poly_instructions[1].n > 0

# Compute Random Linear Combination coefficients
acc_indexes = [0, 1] if double_extension else [0]
#########################

for acc_index in acc_indexes:
for i, instruction_type in enumerate(
self.accumulate_poly_instructions[acc_index].types
):
# print(f"{i=}, Hashing {instruction_type}")
match instruction_type:
case AccPolyInstructionType.MUL:
self.transcript.hash_limbs_multi(
self.accumulate_poly_instructions[acc_index].Ris[i],
self.accumulate_poly_instructions[acc_index].r_sparsities[
i
],
)
case AccPolyInstructionType.SQUARE_TORUS:
self.transcript.hash_limbs_multi(
self.accumulate_poly_instructions[acc_index].Ris[i]
)

case AccPolyInstructionType.DIV:
self.transcript.hash_limbs_multi(
self.accumulate_poly_instructions[acc_index].Pis[i][0],
)
################ Get base rlc coefficient c0 ################
if self.hash_input:
self.transcript.hash_limbs_multi(self.circuit_input)
self.transcript.hash_limbs_multi(self.commitments)

case _:
raise ValueError(
f"Unknown instruction type: {instruction_type}"
)
c0 = self.write_cairo_native_felt(self.field(self.transcript.s1))
##################################################################
################ Compute Qs ################
Qs = [Polynomial([self.field.zero()]) for _ in range(2)]

for acc_index in acc_indexes:
self.accumulate_poly_instructions[acc_index].rlc_coeffs.append(c0)
# Computes Q = Σ(ci * Qi)
Qs[acc_index] = self.accumulate_poly_instructions[acc_index].Qis[0] * c0
for i in range(1, self.accumulate_poly_instructions[acc_index].n):
self.accumulate_poly_instructions[acc_index].rlc_coeffs.append(
self.write_cairo_native_felt(self.field(self.transcript.RLC_coeff))
self.mul(
self.accumulate_poly_instructions[acc_index].rlc_coeffs[i - 1],
c0,
)
)
# Computes Q = Σ(ci * Qi)
for i, coeff in enumerate(
self.accumulate_poly_instructions[acc_index].rlc_coeffs
):
nondeterministic_Qs[acc_index] += (
self.accumulate_poly_instructions[acc_index].Qis[i] * coeff
Qs[acc_index] += (
self.accumulate_poly_instructions[acc_index].Qis[i]
* self.accumulate_poly_instructions[acc_index].rlc_coeffs[i]
)

# Extend Q with zeros if needed to match the expected degree.
nondeterministic_Qs[acc_index] = nondeterministic_Qs[acc_index].get_coeffs()
nondeterministic_Qs[acc_index] = nondeterministic_Qs[acc_index] + [
self.field.zero()
] * (
(acc_index + 1) * extension_degree
- 1
- len(nondeterministic_Qs[acc_index])
Qs[acc_index] = Qs[acc_index].get_coeffs()
# Extend Q with zeros if needed to match the minimal expected degree.
Qs[acc_index] = Qs[acc_index] + [self.field.zero()] * (
(acc_index + 1) * extension_degree - 1 - len(Qs[acc_index])
)
# HASH(COMMIT0, COMMIT1, Q0, Q1)
# Add Q to transcript to get Z.
if not mock:
self.transcript.hash_limbs_multi(nondeterministic_Qs[0])
if double_extension:
self.transcript.hash_limbs_multi(nondeterministic_Qs[1])
##################################################################

Z = self.field(self.transcript.continuable_hash)

return (Z, nondeterministic_Qs)

def finalize_circuit(
self,
extension_degree: int = None,
mock=False,
):
# print("\n Finalize Circuit")
extension_degree = extension_degree or self.extension_degree
Q = [self.write_elements(Qs[0], WriteOps.COMMIT)]

z, Qs = self.get_Z_and_nondeterministic_Q(extension_degree, mock)
compute_z_up_to = max(max(len(Qs[0]), len(Qs[1])) - 1, extension_degree)
# print(f"{self.name} compute_z_up_to: {compute_z_up_to}")

Q = [self.write_elements(Qs[0], WriteOps.COMMIT)]
double_extension = self.accumulate_poly_instructions[1].n > 0
self.big_q_len = len(Q[0])

self.transcript.hash_limbs_multi(Q[0])
if double_extension:
Q.append(self.write_elements(Qs[1], WriteOps.COMMIT))
self.transcript.hash_limbs_multi(Q[1])
compute_z_up_to = max(compute_z_up_to, extension_degree * 2)
self.big_q_len = self.big_q_len + len(Q[1])

self.create_powers_of_Z(z, mock=mock, max_degree=compute_z_up_to)
z = self.transcript.continuable_hash

acc_indexes = [0, 1] if double_extension else [0]
self.create_powers_of_Z(z, mock=mock, max_degree=compute_z_up_to)

for acc_index in acc_indexes:
for i in range(self.accumulate_poly_instructions[acc_index].n):
Expand Down Expand Up @@ -710,7 +676,6 @@ def finalize_circuit(
else:
eq_check = self.sub(rhs, lhs)
self.extend_output([eq_check])

return True

def summarize(self):
Expand Down Expand Up @@ -740,12 +705,12 @@ def compile_circuit_cairo_zero(
"add_offsets_ptr",
"mul_offsets_ptr",
"output_offsets_ptr",
"poseidon_indexes_ptr",
],
"felt": [
"constants_ptr_len",
"input_len",
"commitments_len",
"big_Q_len",
"witnesses_len",
"output_len",
"continuous_output",
Expand All @@ -759,7 +724,6 @@ def compile_circuit_cairo_zero(
},
) -> str:
dw_arrays = self.values_segment.get_dw_lookups()
dw_arrays["poseidon_indexes_ptr"] = self.transcript.poseidon_ptr_indexes
name = function_name or self.values_segment.name
function_name = f"get_{name}_circuit"
code = f"func {function_name}()->(circuit:{self.class_name}*)" + "{" + "\n"
Expand All @@ -774,6 +738,7 @@ def compile_circuit_cairo_zero(
code += f"let input_len = {len(self.values_segment.segment_stacks[WriteOps.INPUT])*N_LIMBS};\n"
code += f"let commitments_len = {len(self.commitments)*N_LIMBS};\n"
code += f"let witnesses_len = {len(self.values_segment.segment_stacks[WriteOps.WITNESS])*N_LIMBS};\n"
code += f"let big_Q_len = {self.big_q_len*N_LIMBS};\n"
code += f"let output_len = {len(self.output)*N_LIMBS};\n"
continuous_output = self.continuous_output
code += f"let continuous_output = {1 if continuous_output else 0};\n"
Expand Down Expand Up @@ -827,14 +792,6 @@ def compile_circuit_cairo_zero(
for val in dw_values:
code += f"\t dw {val};\n"

elif dw_array_name in [
"poseidon_indexes_ptr",
]:
for val in dw_values:
code += (
f"\t dw {POSEIDON_BUILTIN_SIZE*val+POSEIDON_OUTPUT_S1_INDEX};\n"
)

code += "\n"
code += "}\n"
return code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def groth16_calldata_from_vk_and_proof(
calldata.extend(proof.serialize_to_calldata())
calldata.extend(mpc.serialize_to_calldata())

if proof.image_id and proof.journal_digest:
if proof.image_id and proof.journal:
# Risc0 mode.
print("Risc0 mode")
msm = MSMCalldataBuilder(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ def gen_risc0_groth16_verifier(
use garaga::definitions::{{G1Point, G1G2Pair}};
use garaga::groth16::{{multi_pairing_check_{curve_id.name.lower()}_3P_2F_with_extra_miller_loop_result}};
use garaga::ec_ops::{{G1PointTrait, G2PointTrait, ec_safe_add}};
use garaga::utils::risc0::compute_receipt_claim;
use garaga::utils::calldata::{{FullProofWithHintsRisc0, deserialize_full_proof_with_hints_risc0}};
use garaga::utils::risc0::{{compute_receipt_claim, journal_sha256}};
use garaga::utils::calldata::deserialize_full_proof_with_hints_risc0;
use super::{{N_FREE_PUBLIC_INPUTS, vk, ic, precomputed_lines, T}};
const ECIP_OPS_CLASS_HASH: felt252 = {hex(ecip_class_hash)};
Expand All @@ -93,7 +93,7 @@ def gen_risc0_groth16_verifier(
let groth16_proof = fph.groth16_proof;
let image_id = fph.image_id;
let journal_digest = fph.journal_digest;
let journal = fph.journal;
let mpcheck_hint = fph.mpcheck_hint;
let small_Q = fph.small_Q;
let msm_hint = fph.msm_hint;
Expand All @@ -104,6 +104,7 @@ def gen_risc0_groth16_verifier(
let ic = ic.span();
let journal_digest = journal_sha256(journal);
let claim_digest = compute_receipt_claim(image_id, journal_digest);
// Start serialization with the hint array directly to avoid copying it.
Expand Down Expand Up @@ -142,10 +143,7 @@ def gen_risc0_groth16_verifier(
small_Q
);
if check == true {{
self
.process_public_inputs(
starknet::get_caller_address(), claim_digest
);
self.process_public_inputs(starknet::get_caller_address(), journal);
return true;
}} else {{
return false;
Expand All @@ -155,7 +153,7 @@ def gen_risc0_groth16_verifier(
#[generate_trait]
impl InternalFunctions of InternalFunctionsTrait {{
fn process_public_inputs(
ref self: ContractState, user: ContractAddress, claim_digest: u256,
ref self: ContractState, user: ContractAddress, public_inputs: Span<u8>,
) {{ // Process the public inputs with respect to the caller address (user).
// Update the storage, emit events, call other contracts, etc.
}}
Expand Down
Loading

0 comments on commit 9b20cc3

Please sign in to comment.