diff --git a/book/book.toml b/book/book.toml
index 44d812cc99..2e20f98145 100644
--- a/book/book.toml
+++ b/book/book.toml
@@ -7,3 +7,6 @@ title = "OpenVM Book"
[output.html]
site-url = "https://book.openvm.dev/"
+additional-head = [
+ ""
+]
\ No newline at end of file
diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
index 3bf8b614ab..b40ab41b87 100644
--- a/book/src/SUMMARY.md
+++ b/book/src/SUMMARY.md
@@ -19,7 +19,7 @@
# Using Extensions
-- [Customizable Extensions](./using-extensions/customizable-extensions.md)
+- [Customizable Extensions](./custom-extensions/overview.md)
# Advanced Usage
diff --git a/book/src/advanced-usage/testing-program.md b/book/src/advanced-usage/testing-program.md
index 36839e91f5..1db3d71521 100644
--- a/book/src/advanced-usage/testing-program.md
+++ b/book/src/advanced-usage/testing-program.md
@@ -1,4 +1,3 @@
-
## Testing the program
### Running on the host machine
diff --git a/book/src/custom-extensions/algebra.md b/book/src/custom-extensions/algebra.md
new file mode 100644
index 0000000000..c9a6b32f95
--- /dev/null
+++ b/book/src/custom-extensions/algebra.md
@@ -0,0 +1,143 @@
+# OpenVM Algebra
+
+The OpenVM Algebra extension provides tools to create and manipulate modular arithmetic structures and their complex extensions. For example, if $p$ is prime, OpenVM Algebra can handle modular arithmetic in $\mathbb{F}_p$ and its quadratic extension fields $\mathbb{F}_p[x]/(x^2 + 1)$.
+
+The functional part is provided by the `openvm-algebra-guest` crate, which is a guest library that can be used in any OpenVM program. The macros for creating corresponding structs are in the `openvm-algebra-moduli-setup` and `openvm-algebra-complex-macros` crates.
+
+## Available traits and methods
+
+- `IntMod` trait:
+ Defines the type `Repr` and constants `MODULUS`, `NUM_LIMBS`, `ZERO`, and `ONE`. It also provides basic methods for constructing a modular arithmetic object and performing arithmetic operations.
+ - `Repr` typically is `[u8; NUM_LIMBS]`, representing the number’s underlying storage.
+ - `MODULUS` is the compile-time known modulus.
+ - `ZERO` and `ONE` represent the additive and multiplicative identities, respectively.
+ - Constructors include `from_repr`, `from_le_bytes`, `from_be_bytes`, `from_u8`, `from_u32`, and `from_u64`.
+
+- `Field` trait:
+ Provides constants `ZERO` and `ONE` and methods for basic arithmetic operations within a field.
+
+## Modular arithmetic
+
+To [leverage](./overview.md) compile-time known moduli for performance, you declare, initialize, and then set up the arithmetic structures:
+
+1. **Declare**: Use the `moduli_declare!` macro to define a modular arithmetic struct. This can be done multiple times in various crates or modules:
+
+```rust
+moduli_declare! {
+ Bls12_381Fp { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" },
+ Bn254Fp { modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" },
+}
+```
+
+This creates `Bls12_381Fp` and `Bn254Fp` structs, each implementing the `IntMod` trait. The modulus parameter must be a string literal in decimal or hexadecimal format.
+
+2. **Init**: Use the `moduli_init!` macro exactly once in the final binary:
+
+```rust
+moduli_init! {
+ "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab",
+ "21888242871839275222246405745257275088696311157297823662689037894645226208583"
+}
+```
+
+This step enumerates the declared moduli (e.g., `0` for the first one, `1` for the second one) and sets up internal linkage so the compiler can generate the appropriate RISC-V instructions associated with each modulus.
+
+3. **Setup**: At runtime, before performing arithmetic, a setup instruction must be sent to ensure security and correctness. For the $i$-th modulus, you call `setup_()` (e.g., `setup_0()` or `setup_1()`). Alternatively, `setup_all_moduli()` can be used to handle all declared moduli.
+
+**Summary**:
+- `moduli_declare!`: Declares modular arithmetic structures and can be done multiple times.
+- `moduli_init!`: Called once in the final binary to assign and lock in the moduli.
+- `setup_()`/`setup_all_moduli()`: Ensures at runtime that the correct modulus is in use, providing a security check and finalizing the environment for safe arithmetic operations.
+
+## Complex field extension
+
+Complex extensions, such as $\mathbb{F}_p[x]/(x^2 + 1)$, are defined similarly using `complex_declare!` and `complex_init!`:
+
+1. **Declare**:
+
+```rust
+complex_declare! {
+ Bn254Fp2 { mod_type = Bn254Fp }
+}
+```
+
+This creates a `Bn254Fp2` struct, representing a complex extension field. The `mod_type` must implement `IntMod`.
+
+2. **Init**: Called once, after `moduli_init!`, to enumerate these extensions and generate corresponding instructions:
+
+```rust
+complex_init! {
+ Bn254Fp2 { mod_idx = 0 },
+}
+```
+
+Note that you need to use the same type name in `complex_declare!` and `complex_init!`. For example, the following code will **fail** to compile:
+
+```rust
+// moduli related macros...
+
+complex_declare! {
+ Bn254Fp2 { mod_type = Bn254Fp },
+}
+
+pub type Fp2 = Bn254Fp2;
+
+complex_init! {
+ Fp2 { mod_idx = 0 },
+}
+```
+
+Here, `mod_idx` refers to the index of the underlying modulus as initialized by `moduli_init!`
+
+3. **Setup**: Similar to moduli, call `setup_complex_()` or `setup_all_complex_extensions()` at runtime to secure the environment.
+
+### Example program
+
+Here is a toy example using both the modular arithmetic and complex field extension capabilities:
+```rust
+#![cfg_attr(not(feature = "std"), no_main)]
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use openvm_algebra_guest::IntMod;
+
+openvm::entry!(main);
+
+// This macro will create two structs, `Mod1` and `Mod2`,
+// one for arithmetic modulo 998244353, and the other for arithmetic modulo 1000000007.
+openvm_algebra_moduli_setup::moduli_declare! {
+ Mod1 { modulus = "998244353" },
+ Mod2 { modulus = "1000000007" }
+}
+
+// This macro will initialize the moduli.
+// Now, `Mod1` is the "zeroth" modular struct, and `Mod2` is the "first" one.
+openvm_algebra_moduli_setup::moduli_init! {
+ "998244353", "1000000007"
+}
+
+// This macro will create two structs, `Complex1` and `Complex2`,
+// one for arithmetic in the field $\mathbb{F}_{998244353}[x]/(x^2 + 1)$,
+// and the other for arithmetic in the field $\mathbb{F}_{1000000007}[x]/(x^2 + 1)$.
+openvm_algebra_complex_macros::complex_declare! {
+ Complex1 { mod_type = Mod1 },
+ Complex2 { mod_type = Mod2 },
+}
+
+// The order of these structs does not matter,
+// given that we specify the `mod_idx` parameters properly.
+openvm_algebra_complex_macros::complex_init! {
+ Complex2 { mod_idx = 1 }, Complex1 { mod_idx = 0 },
+}
+
+pub fn main() {
+ // Since we only use an arithmetic operation with `Mod1` and not `Mod2`,
+ // we only need to call `setup_0()` here.
+ setup_0();
+ setup_all_complex_extensions();
+ let a = Complex1::new(Mod1::ZERO, Mod1::from_u32(0x3b8) * Mod1::from_u32(0x100000)); // a = -i in the corresponding field
+ let b = Complex2::new(Mod2::ZERO, Mod2::from_u32(1000000006)); // b = -i in the corresponding field
+ assert_eq!(a.clone() * &a * &a * &a * &a, a); // a^5 = a
+ assert_eq!(b.clone() * &b * &b * &b * &b, b); // b^5 = b
+ // Note that these assertions would fail, have we provided the `mod_idx` parameters wrongly.
+}
+```
diff --git a/book/src/custom-extensions/bigint.md b/book/src/custom-extensions/bigint.md
new file mode 100644
index 0000000000..61c459249e
--- /dev/null
+++ b/book/src/custom-extensions/bigint.md
@@ -0,0 +1 @@
+# OpenVM BigInt
diff --git a/book/src/custom-extensions/ecc.md b/book/src/custom-extensions/ecc.md
new file mode 100644
index 0000000000..9dc1e3453b
--- /dev/null
+++ b/book/src/custom-extensions/ecc.md
@@ -0,0 +1,30 @@
+# OpenVM ECC
+
+For elliptic curve cryptography, the `openvm-ecc` crate provides macros similar to those in [`openvm-algebra`](./algebra.md):
+
+1. **Declare**: Use `sw_declare!` to define elliptic curves over the previously declared moduli. For example:
+
+```rust
+sw_declare! {
+ Bls12_381G1Affine { mod_type = Bls12_381Fp, b = BLS12_381_B },
+ Bn254G1Affine { mod_type = Bn254Fp, b = BN254_B },
+}
+```
+
+Each declared curve must specify the `mod_type` (implementing `IntMod`) and a constant `b` for the Weierstrass curve equation $y^2 = x^3 + b$.
+
+2. **Init**: Called once, it enumerates these curves and allows the compiler to produce optimized instructions:
+
+```rust
+sw_init! {
+ Bls12_381Fp, Bn254Fp,
+}
+```
+
+3. **Setup**: Similar to the moduli and complex extensions, runtime setup instructions ensure that the correct curve parameters are being used, guaranteeing secure operation.
+
+**Summary**:
+
+- `sw_declare!`: Declares elliptic curve structures.
+- `sw_init!`: Initializes them once, linking them to the underlying moduli.
+- `setup_sw_()`/`setup_all_curves()`: Secures runtime correctness.
diff --git a/book/src/custom-extensions/keccak.md b/book/src/custom-extensions/keccak.md
new file mode 100644
index 0000000000..7e83568f07
--- /dev/null
+++ b/book/src/custom-extensions/keccak.md
@@ -0,0 +1 @@
+# OpenVM Keccak
\ No newline at end of file
diff --git a/book/src/custom-extensions/overview.md b/book/src/custom-extensions/overview.md
new file mode 100644
index 0000000000..a23da253f6
--- /dev/null
+++ b/book/src/custom-extensions/overview.md
@@ -0,0 +1,19 @@
+# Using Existing Extensions
+
+You can seamlessly integrate certain performance-optimized extensions maintained by the OpenVM team to enhance your arithmetic operations and cryptographic computations.
+
+Certain arithmetic operations, particularly modular arithmetic, can be optimized significantly when the modulus is known at compile time. This approach requires a framework to inform the compiler about all the moduli and associated arithmetic structures we intend to use. To achieve this, three steps are involved:
+
+1. **Declare**: Introduce a modular arithmetic or related structure, along with its modulus and functionality. This can be done in any library or binary file.
+2. **Init**: Performed exactly once in the final binary. It aggregates all previously declared structures, assigns them stable indices, and sets up linkage so that they can be referenced in generated code.
+3. **Setup**: A one-time runtime procedure for security. This ensures that the compiled code matches the virtual machine’s expectations and that each instruction set is tied to the correct modulus or extension.
+
+These steps ensure both performance and security: performance because the modulus is known at compile time, and security because runtime checks confirm that the correct structures have been initialized.
+
+The list of existing extensions:
+
+- [`openvm-algebra`](./algebra.md)
+- [`openvm-bigint`](./bigint.md)
+- [`openvm-keccak`](./keccak.md)
+- [`openvm-pairing`](./pairing.md)
+- [`openvm-ecc`](./ecc.md)
diff --git a/book/src/custom-extensions/pairing.md b/book/src/custom-extensions/pairing.md
new file mode 100644
index 0000000000..ebb943f14c
--- /dev/null
+++ b/book/src/custom-extensions/pairing.md
@@ -0,0 +1 @@
+# OpenVM Pairing
\ No newline at end of file
diff --git a/book/src/introduction.md b/book/src/introduction.md
index 08c1bde13e..80d66d608f 100644
--- a/book/src/introduction.md
+++ b/book/src/introduction.md
@@ -20,5 +20,5 @@ The following chapters will guide you through:
- [Getting started](./getting-started/install.md)
- [Writing applications](./writing-apps/overview.md) in Rust targeting OpenVM and generating proofs.
-- [Using existing extensions](./using-extensions/) to optimize your Rust programs.
+- [Using existing extensions](./custom-extensions/overview.md) to optimize your Rust programs.
- How to add custom VM extensions
diff --git a/book/src/using-extensions/customizable-extensions.md b/book/src/using-extensions/customizable-extensions.md
deleted file mode 100644
index 1d69997c2a..0000000000
--- a/book/src/using-extensions/customizable-extensions.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Using already existing extensions
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
-
-## `openvm-algebra`
-
-This crate allows one to create and use structs for convenient modular arithmetic operations, and also for their complex extensions (for example, if $p$ is a prime number, `openvm-algebra` provides methods for modular arithmetic in the field $\mathbb{F}_p[x]/(x^2 + 1)$).
-
-To declare a modular arithmetic struct, one needs to use the `moduli_declare!` macro. A usage example is given below:
-
-```rust
-moduli_declare! {
- Bls12_381Fp { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" },
- Bn254Fp { modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" },
-}
-```
-
-This creates two structs, `Bls12381_Fp` and `Bn254_Fp`, each representing the modular arithmetic class. These classes implement `Add`, `Sub` and other basic arithmetic operations; the underlying functions used for this are a part of the `IntMod` trait. The modulus for each struct is specified in the `modulus` parameter of the macro. It should be a string literal in either decimal or hexadecimal format (in the latter case, it must start with `0x`).
-
-The arithmetic operations for these classes, when compiling for the `zkvm` target, are converted into RISC-V asm instructions which are distinguished by the `funct7` field. The corresponding "distinguishers assignment" is happening when another macro is called:
-
-```rust
-moduli_init! {
- "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab",
- "21888242871839275222246405745257275088696311157297823662689037894645226208583"
-}
-```
-
-This macro **must be called exactly once** in the final executable program, and it must contain all the moduli that have ever been declared in the `moduli_declare!` macros across all the compilation units. It is possible to `declare` a number in decimal and `init` it in hexadecimal, and vice versa.
-
-When `moduli_init!` is called, the moduli in it are enumerated from `0`. For each chip that is used, the first instruction that this chip receives must be a `setup` instruction -- this adds a record to the trace that guarantees that the modulus this chip uses is exactly the one we `init`ed.
-
-To send a setup instruction for the $i$-th struct, one needs to call the `setup_()` function (for instance, `setup_1()`). There is also a function `setup_all_moduli()` that calls all the available `setup` functions.
-
-To summarize:
-
-- `moduli_declare!` declares a struct for a modular arithmetic class. It can be called multiple times across the compilation units.
-- `moduli_init!` initializes the data required for transpiling the program into the RISC-V assembly. **Every modulus ever `declare`d in the program must be among the arguments of `moduli_init!`**.
-- `setup_()` sends a setup instruction for the $i$-th struct. Here, **$i$-th struct is the one that corresponds to the $i$-th modulus in `moduli_init!`**. The order of `moduli_declare!` invocations or the arguments in them does not matter.
-- `setup_all_moduli()` sends setup instructions for all the structs.
-
-## `openvm-ecc`
-
-This crate allows one to create and use structs for elliptic curve cryptography. More specifically, it only supports curves where the defining equation is in short [Weierstrass curves](https://en.wikipedia.org/wiki/Weierstrass_form) (that is, `a = 0`).
-
-To declare an elliptic curve struct, one needs to use the `sw_declare!` macro. A usage example is given below:
-
-```rust
-sw_declare! {
- Bls12_381G1Affine { mod_type = Bls12_381Fp, b = BLS12_381_B },
- Bn254G1Affine { mod_type = Bn254Fp, b = BN254_B },
-}
-```
-
-Similar to the `moduli_declare!` macro, the `sw_declare!` macro creates a struct for an elliptic curve. The `mod_type` parameter specifies the type of the modulus for this curve, and the `b` parameter specifies the free coefficient of the curve equation; both of these parameters are required. The `mod_type` parameter must be a struct that implements the `IntMod` trait. The `b` parameter must be a constant.
-
-The arithmetic operations for these classes, when compiling for the `zkvm` target, are converted into RISC-V asm instructions which are distinguished by the `funct7` field. The corresponding "distinguishers assignment" is happening when another macro is called:
-
-```rust
-sw_init! {
- Bls12_381Fp, Bn254Fp,
-}
-```
-
-Again, this macro **must be called exactly once** in the final executable program, and it must contain all the curves that have ever been declared in the `sw_declare!` macros across all the compilation units.
-
-When `sw_init!` is called, the curves in it are enumerated from `0`. For each chip that is used, the first instruction that this chip receives must be a `setup` instruction -- this adds a record to the trace that guarantees that the curve this chip uses is exactly the one we `init`ed.
-
-To send a setup instruction for the $i$-th struct, one needs to call the `setup_sw_()` function (for instance, `setup_sw_1()`). There is also a function `setup_all_curves()` that calls all the available `setup` functions.
-
-To summarize:
-
-- `sw_declare!` declares a struct for an elliptic curve. It can be called multiple times across the compilation units.
-- `sw_init!` initializes the data required for transpiling the program into the RISC-V assembly. **Every curve ever `declare`d in the program must be among the arguments of `sw_init!`**.
-- `setup_sw_()` sends a setup instruction for the $i$-th struct. Here, **$i$-th struct is the one that corresponds to the $i$-th curve in `sw_init!`**. The order of `sw_declare!` invocations or the arguments in them does not matter.
-- `setup_all_curves()` sends setup instructions for all the structs.