Skip to content

Commit c5b5e5f

Browse files
committed
feat: Build and test on wasm32-unknown-unknown
2 parents 23d175e + f4951a1 commit c5b5e5f

27 files changed

Lines changed: 975 additions & 567 deletions

.github/workflows/wasm.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: WASM
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
branches:
9+
- master
10+
11+
jobs:
12+
wasm:
13+
name: Build, lint, test
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
19+
- name: Install stable toolchain
20+
uses: dtolnay/rust-toolchain@stable
21+
with:
22+
targets: wasm32-unknown-unknown
23+
24+
- name: Setup Node.js
25+
uses: actions/setup-node@v6
26+
with:
27+
node-version: '20'
28+
29+
- name: Install wasm-pack
30+
uses: taiki-e/install-action@v2
31+
with:
32+
tool: wasm-pack
33+
34+
- name: Build with wasm-pack
35+
working-directory: twenty-first
36+
run: wasm-pack build --release --target nodejs
37+
38+
- name: Run wasm-pack tests
39+
working-directory: twenty-first
40+
run: wasm-pack test --node

README.md

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,25 @@ A collection of cryptography primitives written in Rust.
1111
This library contains primarily the following cryptographic primitives:
1212

1313
- The Tip5 hash function
14-
- [The Tip5 Hash Function for Recursive STARKs](https://eprint.iacr.org/2023/107)
14+
- [The Tip5 Hash Function for Recursive STARKs](https://eprint.iacr.org/2023/107)
1515
- Lattice-crypto
16-
- arithmetic for the quotient ring $\mathbb{F}_ p[X] / \langle X^{64} + 1 \rangle$
17-
- arithmetic for modules over this quotient ring
18-
- a IND-CCA2-secure key encapsulation mechanism
19-
- [Lattice-Based Cryptography in Miden VM](https://eprint.iacr.org/2022/1041)
16+
- arithmetic for the quotient ring $\mathbb{F}_ p[X] / \langle X^{64} + 1 \rangle$
17+
- arithmetic for modules over this quotient ring
18+
- a IND-CCA2-secure key encapsulation mechanism
19+
- [Lattice-Based Cryptography in Miden VM](https://eprint.iacr.org/2022/1041)
2020
- `BFieldElement`, `XFieldElement`
21-
- The prime-field type $\mathbb{F}_p$ where $p = 2^{64} - 2^{32} + 1$
22-
- The extension field $\mathbb{F}_p[x]/(x^3 - x + 1)$
23-
- A codec trait for encoding and decoding structs as `Vec`s of `BFieldElement`
24-
- [An efficient prime for number-theoretic transforms](https://cp4space.hatsya.com/2021/09/01/an-efficient-prime-for-number-theoretic-transforms/)
21+
- The prime-field type $\mathbb{F}_p$ where $p = 2^{64} - 2^{32} + 1$
22+
- The extension field $\mathbb{F}_p[x]/(x^3 - x + 1)$
23+
- A codec trait for encoding and decoding structs as `Vec`s of `BFieldElement`
24+
- [An efficient prime for number-theoretic transforms](https://cp4space.hatsya.com/2021/09/01/an-efficient-prime-for-number-theoretic-transforms/)
2525
- NTT
26-
- Number Theoretic Transform (discrete Fast Fourier Transform)
27-
- [Anatomy of a STARK, Part 6: Speeding Things Up](https://neptune.cash/learn/stark-anatomy/faster/)
28-
- Univariate and multivariate polynomials
26+
- Number Theoretic Transform (discrete Fast Fourier Transform)
27+
- [Anatomy of a STARK, Part 6: Speeding Things Up](https://neptune.cash/learn/stark-anatomy/faster/)
28+
- Univariate polynomials
2929
- Merkle Trees
3030
- Merkle Mountain Ranges
3131

32-
## Release protocol
32+
## Wasm support
3333

34-
While twenty-first's version is `0.x.y`, releasing a new version:
35-
36-
1. Is the release backwards-compatible?
37-
Then the new version is `0.x.y+1`. Otherwise the new version is `0.x+1.0`.
38-
2. Checkout the last commit on Mjolnir, and run `make bench-publish`. Save the benchmark's result
39-
and verify that there is no performance degradation.
40-
3. Create a commit that increases `version = "0.x.y"` in twenty-first/Cargo.toml.
41-
The commit message should give a one-line summary of each release change. Include the benchmark
42-
result at the bottom.
43-
4. Have a `v0.x.y` [git tag][tag] on this commit created. (`git tag v0.x.y [sha]`, `git push upstream --tags`)
44-
5. Have this commit `cargo publish`ed on [crates.io][crates] and in GitHub [tags][tags].
45-
46-
[tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging
47-
[tags]: https://github.com/Neptune-Crypto/twenty-first/tags
48-
[crates]: https://crates.io/crates/twenty-first/versions
49-
50-
If you do not have the privilege to create git tags or run `cargo publish`, submit a PR and the merger will take care of these.
34+
The `twenty-first` library can be built for WebAssembly. See the [dedicated readme](twenty-first/README-wasm32.md) for
35+
further information.

twenty-first/.cargo/config.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
[target.wasm32-unknown-unknown]
2-
rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""]
2+
# debuginfo and symbols are stripped to decrease size of generated debug binary.
3+
# Otherwise, the binary would be so huge that the wasm test harness barfs on it.
4+
rustflags = [
5+
"--cfg", "getrandom_backend=\"wasm_js\"",
6+
"-C", "debuginfo=0",
7+
"-C", "strip=symbols",
8+
]
9+
runner = "wasm-bindgen-test-runner"

twenty-first/Cargo.toml

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ readme.workspace = true
1515
keywords = ["polynomial", "merkle-tree", "post-quantum", "algebra", "tip5"]
1616
categories = ["cryptography", "mathematics"]
1717

18+
[lib]
19+
crate-type = ["cdylib", "rlib"]
20+
1821
[dependencies]
1922
arbitrary = { version = "1", features = ["derive"] }
2023
bfieldcodec_derive = "0.7"
@@ -34,27 +37,28 @@ sha3 = "^0.10.8"
3437
thiserror = "2.0"
3538
zeroize = { version = "1.8.1", features = ["derive"] }
3639

37-
# deps for wasm32 target arch
3840
[target.'cfg(target_arch = "wasm32")'.dependencies]
39-
40-
# convince getrandom to build for wasm target.
41-
# note that there is also a flag in .cargo/config.toml
42-
getrandom = { version = "0.3", features = ["wasm_js"] }
41+
getrandom = { version = "0.3", features = ["wasm_js"] } # note that there is also a flag in .cargo/config.toml
4342
wasm-bindgen = "=0.2.104"
4443

4544
[dev-dependencies]
4645
bincode = "1.3.3"
4746
blake3 = "1.5.5"
47+
macro_rules_attr = "0.1.3"
4848
test-strategy = "=0.4.3"
4949
trybuild = "1.0"
50+
proptest = { version = "1.7.0", default-features = false, features = ["std"] }
5051

5152
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
5253
criterion = { package = "codspeed-criterion-compat", version = "4.0", features = ["html_reports"] }
53-
proptest = { version = "1.7.0", default-features = false, features = ["std"] }
54-
proptest-arbitrary-interop = "0.1"
5554

5655
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
5756
criterion = { version = "0.7.0", default-features = false }
57+
wasm-bindgen-test = "0.3.42"
58+
59+
# Workaround for Rust 1.87.0 (see also: <https://github.com/rust-lang/rust/issues/141048>)
60+
[package.metadata.wasm-pack.profile.release]
61+
wasm-opt = ["-O4", "--enable-bulk-memory"]
5862

5963
[lints]
6064
workspace = true

twenty-first/README-wasm32.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Building and Testing `twenty-first` for wasm32
2+
3+
This document provides instructions on how to build and test the `twenty-first` crate for the `wasm32-unknown-unknown`
4+
target, which allows the library to run in WebAssembly environments.
5+
6+
## What is wasm32?
7+
8+
WebAssembly (Wasm) is a binary instruction format for a stack-based virtual machine. The `wasm32-unknown-unknown` target
9+
allows Rust code to be compiled into Wasm, enabling high-performance applications to run directly in web browsers and
10+
other Wasm-compatible environments. This is ideal for bringing computationally intensive tasks, like the cryptographic
11+
operations in `twenty-first`, to the web without sacrificing performance.
12+
13+
For more detailed information, see the official [WebAssembly website](https://webassembly.org/).
14+
15+
## Required Tools and Setup
16+
17+
To build and test for `wasm32`, you need to set up your Rust environment with the correct target and tooling.
18+
19+
### Add the `wasm32` Target
20+
21+
First, add the `wasm32-unknown-unknown` target to your Rust toolchain using `rustup`:
22+
23+
```shell
24+
rustup target add wasm32-unknown-unknown
25+
```
26+
27+
### Install `wasm-pack`
28+
29+
`wasm-pack` is the primary tool for building, testing, and publishing Rust-generated WebAssembly. It coordinates the
30+
build process and handles the interaction with other tools like `wasm-bindgen`.
31+
32+
Install `wasm-pack` using `cargo`:
33+
34+
```shell
35+
cargo install wasm-pack
36+
```
37+
38+
### Install Node.js (for Testing)
39+
40+
Running the `wasm32` test suite requires a JavaScript runtime. `wasm-pack` uses Node.js for this purpose.
41+
42+
You must have Node.js v20 (LTS) or later installed. The `getrandom` crate, a dependency for our tests, requires the Web
43+
Crypto API, which is stable and fully supported in all modern LTS releases of Node.js.
44+
45+
You can download Node.js from the [official Node.js website](https://nodejs.org/) or install it using a version manager
46+
like `nvm`.
47+
48+
## Build and Test Commands
49+
50+
With the environment configured, you can now build and test the crate. Make sure your current working directory is
51+
`twenty-first/twenty-first` before executing any of the commands below.
52+
53+
### Build the Crate
54+
55+
To compile the `twenty-first` crate for WebAssembly, run the following command:
56+
57+
```shell
58+
wasm-pack build --target nodejs
59+
```
60+
61+
This command compiles the crate and generates the necessary JavaScript bindings, placing the output in a `pkg/`
62+
directory.
63+
64+
### Run Tests
65+
66+
To run the test suite for the `wasm32` target, use the `test` command from `wasm-pack`. This command will compile the
67+
tests and execute them using your installed Node.js runtime.
68+
69+
```shell
70+
wasm-pack test --node
71+
```

twenty-first/src/lib.rs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ pub mod prelude;
2020
pub mod tip5;
2121
pub mod util_types;
2222

23+
#[cfg(test)]
24+
#[cfg_attr(coverage_nightly, feature(coverage_attribute))]
25+
mod proptest_arbitrary_interop;
26+
2327
// This is needed for `#[derive(BFieldCodec)]` macro to work consistently across crates.
2428
// Specifically:
2529
// From inside the `twenty-first` crate, we need to refer to `twenty-first` by `crate`.
@@ -40,6 +44,64 @@ pub(crate) mod tests {
4044

4145
use super::*;
4246

47+
/// A crate-specific replacement of the `#[test]` attribute for tests that
48+
/// should also be executed on `wasm` targets (which is almost all tests).
49+
///
50+
/// If you specifically want to exclude a test from `wasm` targets, use the
51+
/// usual `#[test]` attribute instead.
52+
///
53+
/// # Usage
54+
///
55+
/// ```
56+
/// #[macro_rules_attr::apply(test)]
57+
/// fn foo() {
58+
/// assert_eq!(4, 2 + 2);
59+
/// }
60+
/// ```
61+
macro_rules! test {
62+
($item:item) => {
63+
#[test]
64+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
65+
$item
66+
};
67+
}
68+
pub(crate) use test;
69+
70+
/// A crate-specific replacement of the `#[test_strategy::proptest]`
71+
/// attribute for tests that should also be executed on `wasm` targets
72+
/// (which is almost all tests).
73+
///
74+
/// If you specifically want to exclude a test from `wasm` targets, use the
75+
/// usual `#[test_strategy::proptest]` attribute instead.
76+
///
77+
/// # Usage
78+
///
79+
/// ```
80+
/// # use proptest::prop_assert_eq;
81+
/// #[macro_rules_attr::apply(proptest)]
82+
/// fn foo(#[strategy(0..=42)] x: i32) {
83+
/// prop_assert_eq!(2 * x, x + x);
84+
/// }
85+
/// ```
86+
///
87+
/// If you want to configure the test, use the usual syntax defined by
88+
/// [`test_strategy`]:
89+
/// ```
90+
/// # use proptest::prop_assert_eq;
91+
/// #[macro_rules_attr::apply(proptest(cases = 10, max_local_rejects = 5))]
92+
/// fn foo(#[strategy(0..=42)] x: i32) {
93+
/// prop_assert_eq!(2 * x, x + x);
94+
/// }
95+
/// ```
96+
macro_rules! proptest {
97+
($item:item $(($($config:tt)*))?) => {
98+
#[test_strategy::proptest $(($($config)*))?]
99+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
100+
$item
101+
};
102+
}
103+
pub(crate) use proptest;
104+
43105
/// The compiler automatically adds any applicable auto trait (all of which are
44106
/// marker traits) to self-defined types. This implies that these trait bounds
45107
/// might vanish if the necessary pre-conditions are no longer met. That'd be a
@@ -55,7 +117,7 @@ pub(crate) mod tests {
55117
/// Inspired by “Rust for Rustaceans” by Jon Gjengset.
56118
pub fn implements_usual_auto_traits<T: Sized + Send + Sync + Unpin>() {}
57119

58-
#[test]
120+
#[macro_rules_attr::apply(test)]
59121
fn types_in_prelude_implement_the_usual_auto_traits() {
60122
implements_usual_auto_traits::<BFieldElement>();
61123
implements_usual_auto_traits::<Polynomial<BFieldElement>>();
@@ -68,7 +130,7 @@ pub(crate) mod tests {
68130
implements_usual_auto_traits::<MmrMembershipProof>();
69131
}
70132

71-
#[test]
133+
#[macro_rules_attr::apply(test)]
72134
fn public_types_implement_the_usual_auto_traits() {
73135
implements_usual_auto_traits::<math::lattice::CyclotomicRingElement>();
74136
implements_usual_auto_traits::<math::lattice::ModuleElement<42>>();
@@ -85,7 +147,7 @@ pub(crate) mod tests {
85147
>();
86148
}
87149

88-
#[test]
150+
#[macro_rules_attr::apply(test)]
89151
fn errors_implement_the_usual_auto_traits() {
90152
implements_usual_auto_traits::<error::BFieldCodecError>();
91153
implements_usual_auto_traits::<error::PolynomialBFieldCodecError>();

0 commit comments

Comments
 (0)