Skip to content
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

chore: Fix API to return errors #21

Merged
merged 21 commits into from
Feb 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/js/tests/ffi.spec.ts
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import { Context } from '../verkleFFIBindings/index'
describe('bindings', () => {
let context: Context
beforeAll(() => {
context = Context._default()
context = new Context()
})

test('getTreeKey', () => {
@@ -133,4 +133,14 @@ describe('bindings', () => {

expect(updatedCommitmentHex).toBe(expectedHex)
})

test('smoke test errors are thrown', () => {
const scalar = new Uint8Array([0])

expect(() => {
// This method will throw an error because scalars must be 32 bytes
// but we gave it 1 byte
const commitment = context.commitToScalars([scalar])
}).toThrow('Expected 32 bytes, got 1')
})
})
2 changes: 1 addition & 1 deletion src/js/verkleFFIBindings/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ContextWrapper as Context, zeroCommitment } from '../../../dist/cjs/wasm/rust_verkle_wasm'
import { Context, zeroCommitment } from '../../../dist/cjs/wasm/rust_verkle_wasm'

// This is a 32 byte serialized field element
type Scalar = Uint8Array
105 changes: 57 additions & 48 deletions src/rust-wasm/src/verkle_ffi_api.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
use ffi_interface::Context;
use ffi_interface::Context as InnerContext;
pub use ffi_interface::{CommitmentBytes, ScalarBytes, ZERO_POINT};
use ipa_multipoint::committer::Committer;
use js_sys::Uint8Array;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue};

#[wasm_bindgen]
pub struct ContextWrapper {
pub(crate) inner: Context,
pub struct Context {
pub(crate) inner: InnerContext,
}

#[wasm_bindgen]
impl ContextWrapper {
impl Context {
/// Default constructor to initialize the context
///
/// This context holds the necessary configurations to allow you to
/// modify the verkle trie structure, including the ability to ability
/// to make and verify proofs
#[wasm_bindgen(js_name = "default")]
pub fn default() -> Self {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
inner: Context::default(),
inner: InnerContext::default(),
}
}
/// Computes `get_tree_key` method as specified in the verkle hackmd.
@@ -30,43 +30,40 @@ impl ContextWrapper {
address: Uint8Array,
tree_index_le: Uint8Array,
sub_index: u8,
) -> Uint8Array {
// TODO: This will silently truncate values which are not 32 bytes
// TODO We should return an error instead
let address = js_value_to_bytes::<32>(address.into());
let tree_index_le = js_value_to_bytes::<32>(tree_index_le.into());
) -> Result<Uint8Array, JsError> {
let address = js_value_to_bytes::<32>(address.into())?;
let tree_index_le = js_value_to_bytes::<32>(tree_index_le.into())?;

let key =
ffi_interface::get_tree_key(&self.inner.committer, address, tree_index_le, sub_index);

bytes_to_js_value(key).into()
Ok(bytes_to_js_value(key).into())
}

/// Commits to a collection of scalar values, returning the commitment in
/// uncompressed form.
#[wasm_bindgen(js_name = "commitToScalars")]
pub fn commit_to_scalars(&self, scalars: Vec<Uint8Array>) -> Uint8Array {
let scalars: Vec<u8> = scalars
.into_iter()
.map(|scalar| js_value_to_bytes::<32>(scalar.into()))
.flatten()
.collect();
pub fn commit_to_scalars(&self, scalars_js: Vec<Uint8Array>) -> Result<Uint8Array, JsError> {
let mut scalars = Vec::with_capacity(scalars_js.len());
for scalar in scalars_js {
scalars.extend(js_value_to_bytes::<32>(scalar.into())?);
}

let commitment = ffi_interface::commit_to_scalars(&self.inner.committer, &scalars)
.expect("could not commit to scalars");
.map_err(|err| JsError::new(&format!("could not commit to scalars: {:?}", err)))?;

bytes_to_js_value(commitment).into()
Ok(bytes_to_js_value(commitment).into())
}
/// Computes the hash of a commitment, returning a scalar value
///
// Note: This method does need context. It is here for API convenience.
#[wasm_bindgen(js_name = "hashCommitment")]
pub fn hash_commitment(&self, commitment: Uint8Array) -> Uint8Array {
let commitment = js_value_to_bytes::<64>(commitment.into());
pub fn hash_commitment(&self, commitment: Uint8Array) -> Result<Uint8Array, JsError> {
let commitment = js_value_to_bytes::<64>(commitment.into())?;

let hash = ffi_interface::hash_commitment(commitment);

bytes_to_js_value(hash).into()
Ok(bytes_to_js_value(hash).into())
}

/// Computes the hash of multiple commitment, returning a vector of scalar values.
@@ -78,18 +75,21 @@ impl ContextWrapper {
///
// Note: This method does need context. It is here for API convenience.
#[wasm_bindgen(js_name = "hashCommitments")]
pub fn hash_commitments(&self, commitments: Vec<Uint8Array>) -> Vec<Uint8Array> {
let commitments: Vec<CommitmentBytes> = commitments
.into_iter()
.map(|commitment| js_value_to_bytes::<64>(commitment.into()))
.collect();
pub fn hash_commitments(
&self,
commitments_js: Vec<Uint8Array>,
) -> Result<Vec<Uint8Array>, JsError> {
let mut commitments = Vec::with_capacity(commitments_js.len());
for commitment in commitments_js {
commitments.push(js_value_to_bytes::<64>(commitment.into())?);
}

let hashes = ffi_interface::hash_commitments(&commitments);

hashes
Ok(hashes
.into_iter()
.map(|hash| bytes_to_js_value(hash).into())
.collect()
.collect())
}

/// Serialize a commitment, returning 32 bytes.
@@ -99,12 +99,12 @@ impl ContextWrapper {
/// Note: We plan to deprecate this method from the public API in favour of using hash commitment
/// This method will only be used internally once that is done.
#[wasm_bindgen(js_name = "deprecateSerializeCommitment")]
pub fn serialize_commitment(&self, commitment: Uint8Array) -> Uint8Array {
let commitment = js_value_to_bytes::<64>(commitment.into());
pub fn serialize_commitment(&self, commitment: Uint8Array) -> Result<Uint8Array, JsError> {
let commitment = js_value_to_bytes::<64>(commitment.into())?;

let hash = ffi_interface::deprecated_serialize_commitment(commitment);

bytes_to_js_value(hash).into()
Ok(bytes_to_js_value(hash).into())
}

/// Updates a commitment from aG to bG.
@@ -125,11 +125,11 @@ impl ContextWrapper {
commitment_index: u8,
old_scalar_value: Uint8Array,
new_scalar_value: Uint8Array,
) -> Uint8Array {
let commitment = js_value_to_bytes::<64>(commitment.into());
) -> Result<Uint8Array, JsError> {
let commitment = js_value_to_bytes::<64>(commitment.into())?;

let old_scalar_value = js_value_to_bytes::<32>(old_scalar_value.into());
let new_scalar_value = js_value_to_bytes::<32>(new_scalar_value.into());
let old_scalar_value = js_value_to_bytes::<32>(old_scalar_value.into())?;
let new_scalar_value = js_value_to_bytes::<32>(new_scalar_value.into())?;

let updated_commitment = ffi_interface::update_commitment(
&self.inner.committer,
@@ -138,31 +138,35 @@ impl ContextWrapper {
old_scalar_value,
new_scalar_value,
)
.unwrap();
.map_err(|err| JsError::new(&format!("could not update commitment: {:?}", err)))?;

bytes_to_js_value(updated_commitment).into()
Ok(bytes_to_js_value(updated_commitment).into())
}

/// This method should ideally only be used for tests.
///
/// For updating a commitment, one should use the `update_commitment` method
#[wasm_bindgen(js_name = "scalarMulIndex")]
pub fn scalar_mul_index(&self, scalar_value: Uint8Array, commitment_index: u8) -> Uint8Array {
pub fn scalar_mul_index(
&self,
scalar_value: Uint8Array,
commitment_index: u8,
) -> Result<Uint8Array, JsError> {
fn fr_from_le_bytes(bytes: &[u8]) -> banderwagon::Fr {
use banderwagon::trait_defs::*;
banderwagon::Fr::deserialize_uncompressed(bytes)
.expect("could not deserialize scalar field")
}

let scalar = fr_from_le_bytes(&js_value_to_bytes::<32>(scalar_value.into()));
let scalar = fr_from_le_bytes(&js_value_to_bytes::<32>(scalar_value.into())?);

let commitment = self
.inner
.committer
.scalar_mul(scalar, commitment_index as usize)
.to_bytes_uncompressed();

bytes_to_js_value(commitment).into()
Ok(bytes_to_js_value(commitment).into())
}
}

@@ -172,13 +176,18 @@ pub fn zero_commitment() -> Uint8Array {
bytes_to_js_value(ffi_interface::ZERO_POINT).into()
}

// TODO: This will silently truncate values which are not N bytes
// TODO We should return an error instead
fn js_value_to_bytes<const N: usize>(value: JsValue) -> [u8; N] {
/// Converts a JsValue to an array of size `N` or return an JsValue object representing an
/// error
fn js_value_to_bytes<const N: usize>(value: JsValue) -> Result<[u8; N], JsError> {
let array = js_sys::Uint8Array::new(&value);
if array.length() != N as u32 {
let mismatched_array_size = format!("Expected {} bytes, got {}", N, array.length());
return Err(JsError::new(&mismatched_array_size));
}

let mut result = [0u8; N];
array.copy_to(&mut result);
result
Ok(result)
}
fn bytes_to_js_value<const N: usize>(bytes: [u8; N]) -> JsValue {
js_sys::Uint8Array::from(&bytes[..]).into()