Skip to content

Rust native with neon-rs #3217

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 18, 2025
Merged

Rust native with neon-rs #3217

merged 4 commits into from
May 18, 2025

Conversation

richardpringle
Copy link
Contributor

  • plonk-wasm: factor out future common crates
  • plonk-neon: add new crate for nodejs

@richardpringle richardpringle force-pushed the rust-native-with-neon branch from 63e8d41 to 9d53572 Compare May 7, 2025 19:12
@richardpringle
Copy link
Contributor Author

I don't quite understand why the benchmarking failed... it passed here:
#3218 (I checked out the exact same code but didn't include plonk-neon). In any case, it looks like it's only kimchi that's being benchmarked, so none of my changes should have effected the benchmark.

I guess I'll try re-running it?

@richardpringle richardpringle force-pushed the rust-native-with-neon branch from 9d53572 to 44fd398 Compare May 7, 2025 20:54
@richardpringle richardpringle marked this pull request as ready for review May 7, 2025 21:04
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can back out the changes here if you (dear reviewer) would like, and put them in a separate PR. This is just a cargo clippy fix with clippy::pedantic

pub use group_affine::{WasmGPallas, WasmGVesta};
pub use group_projective::{WasmPallasGProjective, WasmVestaGProjective};
pub use pasta_fp::WasmPastaFp;
pub use pasta_fq::WasmPastaFq;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm being a lot more explicit about exports here.

@richardpringle richardpringle force-pushed the rust-native-with-neon branch from 44fd398 to 0ff4a4f Compare May 7, 2025 21:13
@richardpringle richardpringle force-pushed the rust-native-with-neon branch 5 times, most recently from 58a974c to 9d2c241 Compare May 7, 2025 22:08
@@ -103,12 +106,16 @@ pub fn caml_bigint_256_compare(x: WasmBigInteger256, y: WasmBigInteger256) -> i8
}
}

#[wasm_bindgen]
// I don't think this is used anywhere
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is required in pasta_bindings.ml on the Mina side, see here. When we compile Mina, we need to have this stub. The print statement might be used in the Caml code in different places.

Copy link
Contributor Author

@richardpringle richardpringle May 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, your comment made me prove that it's not used, so thank you for that!

In your comment, you're pointing to the ocaml bindings that either use some type of Rust dynamic library or the js here:
https://github.com/MinaProtocol/mina/blob/c2651d8b9711c0bb1cafac9468eff9e5df006045/src/lib/crypto/kimchi_bindings/js/bindings/bigint256.js#L30

plonk-wasm is only used for the js version. The key detail is that the js code is referring to the following:
https://github.com/o1-labs/o1js/blob/4c60cda83949262db1449bc1103b7b46f9d6980a/src/bindings/crypto/bindings/bigint256.ts#L41-L43

In other words, we can probably remove all the public functions in this file. I'm not going to do that now, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore, I don't think this can ever work when targeting wasm32-unknown-unknown because there's no stdout to write to. I'm pretty sure this code will panic no matter what.

let mut serialized_bytes = vec![];
// This is the size of the implementation of BigInteger256 at the time of writing
// but even if it changes, we will be no worse off than the original code `vec![]`
let mut serialized_bytes = Vec::with_capacity(core::mem::size_of::<WasmBigInteger256>());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not use BIGINT256_NUM_BYTES?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In short, no.

The foward-compatible way of checking the capacity is really to use CanonicalSerialize::serialized_size(&x.0), but that iterates over all the u8s, calling core::mem::size_of::<u8>() over and over.

I think it's safe to assume that for all BigInt<const N: usize>, the serialized size will always be core::mem::size_of::<BigInt>().

There's nothing programmatically connecting any of those constants to the actual serialization implementations, I think it's safer to use the knowledge of the implementation.

Thoughts?

I also was going to just hard-code 32 there with a comment saying that even if the size changes, 32 is not a very large allocation. In the case where the actual size needed is smaller (don't ask me why this would ever be the case), we wouldn't be wasting that much space. In the case where it's larger, we are no worse off than vec![] which is the equivalent of Vec::with_capacity(0).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WasmBigInteger256 could be implemented in such way that the size_of won't give 32 bytes, even though the underlying value is a field element that is 32 bytes. It could be implemented as a pointer, and therefore returns only 8 bytes (or 4 bytes in this case as the WebAssembly runtime is on 32 bits). Or it could have additional value in the structure that makes it bigger than 32 bytes. However, the underlying value requires to be encoded in 32 bytes.

It is true that it is valid, and it won't create any encoding error.

I was mostly making a comment, and was not requesting any change. It is already in the existing codebase.

impl wasm_types::FlatVectorElem for WasmPastaFq {
const FLATTENED_SIZE: usize = core::mem::size_of::<Fq>();
fn flatten(self) -> Vec<u8> {
let mut bytes: Vec<u8> = Vec::with_capacity(Self::FLATTENED_SIZE);
Copy link
Member

@dannywillems dannywillems May 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit/comment: I know it is the original codebase, but it is a bit surprising that it does use the size of the structure on Fp, where it should be unrelated IMO. An encoding is different from its representation, in particular in cryptography where we have different encoding in bytes for the same object (like curve points).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔, you don't think the structure and the serialized representation are related? Both Fp and Fq are just BigInt<const N: usize>s with constraints on N. That in turn is just an array of bytes. So they do seem somewhat coupled to me.

But now that I'm thinking about this... why aren't we just using the CanonicalSerialize implementation instead of rolling our own thing that just delegates to CanonicalSerialize? Kinda weird...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment than here. It happened in some codebase that the field element was a pointer, and not the direct value, and therefore was giving a wrong size.

No requested changes. Mostly a comment.

@@ -0,0 +1,13 @@
#![cfg_attr(not(feature = "std"), no_std)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be in favor of adding a top-level description of what the crate is supposed to contain to guide the next people coming into the repository. In addition to that, we are continuously publishing the documentation and it would be useful for everyone to understand the monorepo structure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the documentation that was there before didn't describe what arkworks is at all, it just demonstrated that you can convert some OCaml type things into the BigInt types. The example doesn't run with arkworks in its own crate because other types are foreign types.

So I don't think the documentation that was there before was very informative or helpful.

With that said, I'm happy add documentation here. What would you like it to say, "wasm-compatible wrappers around arkworks types and functions"?

IMO, there's no point in duplicating any of the descriptions or documentation of the underlying libraries.

@@ -0,0 +1,5 @@
#![no_std]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment than here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would ///! Shared wasm types suffice? Seems premature to put a description here if I'm being honest.

This crate should effectively be private. It makes sense for the public types to be documented, but there's not much to say about the purpose of this crate other than to put things in here that are shared between other, more public facing crates from the mono-repo.

It might be more helpful for me to rename the crate to shared-js-types or something.

What do you think?

@@ -0,0 +1,57 @@
use arkworks::{WasmPastaFp, WasmPastaFq};
Copy link
Member

@dannywillems dannywillems May 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a top-level documentation as it has been done for plonk-wasm/src/poseidon.rs, please? It was confusing for some people why we had these primitives.

Copy link
Contributor Author

@richardpringle richardpringle May 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it alrigth if I just put

//! This file defines wrappers for the Poseidon hash functions that are used in
//! the [Mina](https://github.com/MinaProtocol/mina) codebase.

All the extra stuff

//! It is a wrapper around the Poseidon implementation in the `mina_poseidon` crate.
//! It is required as the native OCaml implementation of Mina does use the Rust
//! implementation defined in the crate `mina_poseidon` instead of defining its
//! own natively in OCaml for performance reasons. The bindings in OCaml can be
//! found in `src/lib/crypto/kimchi_bindings/pasta_fp_poseidon` and
//! `src/lib/crypto/kimchi_bindings/pasta_fq_poseidon` in the Mina codebase.

is redundant and impossible to programmatically assure that the code locations stay up to date. We've already seen in this PR that documentation doesn't get kept up to date when referring to other code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do want references to code in mina, they should at the very least be github permalinks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do want references to code in mina, they should at the very least be github permalinks.

Agreed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it alrigth if I just put

//! This file defines wrappers for the Poseidon hash functions that are used in
//! the [Mina](https://github.com/MinaProtocol/mina) codebase.

All the extra stuff

//! It is a wrapper around the Poseidon implementation in the `mina_poseidon` crate.
//! It is required as the native OCaml implementation of Mina does use the Rust
//! implementation defined in the crate `mina_poseidon` instead of defining its
//! own natively in OCaml for performance reasons. The bindings in OCaml can be
//! found in `src/lib/crypto/kimchi_bindings/pasta_fp_poseidon` and
//! `src/lib/crypto/kimchi_bindings/pasta_fq_poseidon` in the Mina codebase.

is redundant and impossible to programmatically assure that the code locations stay up to date. We've already seen in this PR that documentation doesn't get kept up to date when referring to other code.

Yes sure.

@richardpringle
Copy link
Contributor Author

@dannywillems, do you mind if we defer the esthetic changes until after this PR lands? I left a couple of replies asking for your thoughts on semantic changes. Since this PR is quite large, I would prefer to fix all semantic issues, then do a cleanup pass immediately following this.

@dannywillems
Copy link
Member

dannywillems commented May 8, 2025

@dannywillems, do you mind if we defer the esthetic changes until after this PR lands? I left a couple of replies asking for your thoughts on semantic changes. Since this PR is quite large, I would prefer to fix all semantic issues, then do a cleanup pass immediately following this.

Not at all. I like doing it sequentially.

@richardpringle richardpringle force-pushed the rust-native-with-neon branch 6 times, most recently from 0726728 to 91687bb Compare May 14, 2025 18:26
@dannywillems
Copy link
Member

It would be nice to also have a way to add a target in the Makefile to build the project and add a job in the CI.

@richardpringle
Copy link
Contributor Author

It would be nice to also have a way to add a target in the Makefile to build the project and add a job in the CI.

100%... I will capture that in an issue. I just wanted to get this code in to make sure that it and plonk-wasm don't deviate

@richardpringle richardpringle enabled auto-merge May 15, 2025 16:41
Copy link
Member

@dannywillems dannywillems left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving again after master has been merged.

@dannywillems
Copy link
Member

Rebased and hope it will get merged this time.

@richardpringle richardpringle merged commit 3df6935 into master May 18, 2025
25 checks passed
@richardpringle richardpringle deleted the rust-native-with-neon branch May 18, 2025 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants