Skip to content

Commit

Permalink
Replaces NPM package with Wasm wrapper
Browse files Browse the repository at this point in the history
Resolves #114

removes respective GA
this one should be tested like a package I guess,
ideas for such tests are welcome as issues

Couple of things left out of the committed code.

# Subtle
I was really late to understand that Subtle crypto supports the different curve `secp256r`, *and* it doesn't provide a facility to store secret values. So implementation for `web_sys::SecretKey` turned out to be just extra miles leading nowhere.
```toml
web-sys = { version = "0.3", features = ["CryptoKey", "SubtleCrypto", "Crypto", "EcKeyImportParams"] }
wasm-bindgen-futures = "0.4"
```
```rust
#[wasm_bindgen]
extern "C" {
    // Return type of js_sys::global()
    type Global;
    // // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
    // type WebCrypto;
    // Getters for the WebCrypto API
    #[wasm_bindgen(method, getter)]
    fn crypto(this: &Global) -> web_sys::Crypto;
}

// `fn sign`
if sk.type_() != "secret" {return Err(JsError::new("`sk` must be secret key"))}
if !js_sys::Object::values(&sk.algorithm().map_err(
    |er|
        JsError::new(er.as_string().expect("TODO check this failing").as_str())
)?).includes(&JsValue::from_str("P-256"), 0) {return Err(JsError::new("`sk` must be from `secp256`"))}

// this was my approach, but seems I got what they did at <https://github.com/rust-random/getrandom/blob/master/src/js.rs>
// js_sys::global().entries().find(); // TODO throw if no Crypto in global

let global_the: Global = js_sys::global().unchecked_into();
let crypto_the: web_sys::Crypto = global_the.crypto();
let subtle_the = crypto_the.subtle();
let sk = JsFuture::from(subtle_the.export_key("pkcs8", &sk)?).await?;

// ...
::from_pkcs8_der(js_sys::ArrayBuffer::from(sk).try_into()?)?;
    zeroize::Zeroizing::new(js_sys::Uint8Array::from(JsFuture::from(subtle_the.export_key("pkcs8", &sk).map_err(
        |er|
            Err(JsError::new(er.as_string().expect("TODO check this failing").as_str()))
        )?).await?).to_vec());

// ...

// `fn try_into`

// ...

// zeroization protection ommitted here due to deprecation // <#112>
// mostly boilerplate from signing; also some excessive ops left for the same reason
// TODO align error-handling in this part
if self.c.type_() != "secret" {return Err(JsError::new("`c` must be secret key"))}
if !js_sys::Object::values(&self.c.algorithm()?).includes(js_sys::JsString::from("P-256").into(), 0) {return Err(JsError::new("`c` must be from `secp256`"))}
this was my approach, but seems I got what they did at <https://github.com/rust-random/getrandom/blob/master/src/js.rs>
js_sys::global().entries().find(); // TODO throw if no Crypto in global
let global_the: Global = js_sys::global().unchecked_into();
let crypto_the: web_sys::Crypto = global_the.crypto();
let subtle_the = crypto_the.subtle();
let c_pkcs = //zeroize::Zeroizing::new(
    js_sys::Uint8Array::from(JsFuture::from(subtle_the.export_key("pkcs8", &self.c)?).await?).to_vec();
// );
let c_scalar = &plume_rustcrypto::SecretKey::from_pkcs8_der(&c_pkcs)?.to_nonzero_scalar();
sk_z.zeroize();

// ...
```

# randomness
Somehow I thought Wasm doesn't have access to RNG, so I used a seedable one and required the seed. Here's how `sign` `fn` was different.
```rust
// Wasm environment doesn't have a suitable way to get randomness for the signing process, so this instantiates ChaCha20 RNG with the provided seed.
// @throws a "crypto error" in case of a problem with the secret key, and a verbal error on a problem with `seed`
// @param {Uint8Array} seed - must be exactly 32 bytes.
pub fn sign(seed: &mut [u8], v1: bool, sk: &mut [u8], msg: &[u8]) -> Result<PlumeSignature, JsError> {
    // ...

    let seed_z: zeroize::Zeroizing<[u8; 32]> = zeroize::Zeroizing::new(seed.try_into()?);
    seed.zeroize();

    // TODO switch to `wasi-random` when that will be ready for crypto
    let sig = match v1 {
        true => plume_rustcrypto::PlumeSignature::sign_v1(
            &sk_z, msg, &mut rand_chacha::ChaCha20Rng::from_seed(seed_z)
        ),
        false => plume_rustcrypto::PlumeSignature::sign_v2(
            &sk_z, msg, &mut rand_chacha::ChaCha20Rng::from_seed(seed_z)
        ),
    };

    let sig = signer.sign_with_rng(
        &mut rand_chacha::ChaCha20Rng::from_seed(*seed_z), msg
    );

    // ...
}
```

# `BigInt` conversion
It was appealing to leave `s` as `BigInt` (see the comments), but that seems to be confusing and hinder downstream code reusage. There's an util function left for anybody who would want to have it as `BigInt`, but leaving the contraty function makes less sense and also makes the thing larger. So let me left it here for reference.
```rust
let scalar_from_bigint =
    |n: js_sys::BigInt| -> Result<plume_rustcrypto::NonZeroScalar, anyhow::Error> {
        let result = plume_rustcrypto::NonZeroScalar::from_repr(k256::FieldBytes::from_slice(
            hex::decode({
                let hexstring_freelen = n.to_string(16).map_err(
                    |er|
                        anyhow::Error::msg(er.as_string().expect("`RangeError` can be printed out"))
                )?.as_string().expect("on `JsString` this always produce a `String`");
                let l = hexstring_freelen.len();
                if l > 32*2 {return Err(anyhow::Error::msg("too many digits"))}
                else {["0".repeat(64-l), hexstring_freelen].concat()}
            })?.as_slice()
        ).to_owned());
        if result.is_none().into() {Err(anyhow::Error::msg("isn't valid `secp256` non-zero scalar"))}
        else {Ok(result.expect(EXPECT_NONEALREADYCHECKED))}
    };
```
  • Loading branch information
skaunov authored Jul 17, 2024
1 parent bb24ab0 commit 83e3ed9
Show file tree
Hide file tree
Showing 25 changed files with 417 additions and 3,421 deletions.
41 changes: 0 additions & 41 deletions .github/workflows/javascript.yml

This file was deleted.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[workspace]
resolver = "2"

members = ["rust-arkworks", "rust-k256"]
members = ["rust-arkworks", "rust-k256", "javascript"]

[patch.crates-io]
ark-ec = { git = "https://github.com/FindoraNetwork/ark-algebra" }
Expand Down
11 changes: 11 additions & 0 deletions javascript/.appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
install:
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -V
- cargo -V

build: false

test_script:
- cargo test --locked
8 changes: 8 additions & 0 deletions javascript/.github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily
time: "08:00"
open-pull-requests-limit: 10
15 changes: 6 additions & 9 deletions javascript/.gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# Swap the comments on the following lines if you don't wish to use zero-installs
# Documentation here: https://yarnpkg.com/features/zero-installs
# !.yarn/cache
.pnp.*

# node.js
/node_modules
/dist

/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
14 changes: 0 additions & 14 deletions javascript/.npmignore

This file was deleted.

69 changes: 69 additions & 0 deletions javascript/.travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
language: rust
sudo: false

cache: cargo

matrix:
include:

# Builds with wasm-pack.
- rust: beta
env: RUST_BACKTRACE=1
addons:
firefox: latest
chrome: stable
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
script:
- cargo generate --git . --name testing
# Having a broken Cargo.toml (in that it has curlies in fields) anywhere
# in any of our parent dirs is problematic.
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- wasm-pack build
- wasm-pack test --chrome --firefox --headless

# Builds on nightly.
- rust: nightly
env: RUST_BACKTRACE=1
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- rustup target add wasm32-unknown-unknown
script:
- cargo generate --git . --name testing
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- cargo check
- cargo check --target wasm32-unknown-unknown
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features
- cargo check --no-default-features --features console_error_panic_hook
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
- cargo check --no-default-features --features "console_error_panic_hook wee_alloc"
- cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc"

# Builds on beta.
- rust: beta
env: RUST_BACKTRACE=1
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- rustup target add wasm32-unknown-unknown
script:
- cargo generate --git . --name testing
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- cargo check
- cargo check --target wasm32-unknown-unknown
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features
- cargo check --no-default-features --features console_error_panic_hook
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
# Note: no enabling the `wee_alloc` feature here because it requires
# nightly for now.
35 changes: 35 additions & 0 deletions javascript/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "plume-sig"
version = "3.0.0-rc.1"
authors = ["skaunov"]
edition = "2018"
keywords = ["nullifier", "zero-knowledge", "ECDSA", "PLUME"]
repository = "https://github.com/plume-sig/zk-nullifier-sig/"
description = "wrapper around `plume_rustcrypto` crate to produce PLUME signatures in JS contexts using Wasm"
license = "MIT"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
# I'd alias this to `sec1` if that won't be tricky
verify = ["dep:sec1"]

[dependencies]
wasm-bindgen = "~0.2.84"
js-sys = "0.3"

plume_rustcrypto = {version = "~0.2.1", default-features = false}
sec1 = {version = "~0.7.3", optional = true} # match with `k256`
elliptic-curve = {version = "~0.13.8"}
zeroize = "1.8"
signature = "^2.2.0"
getrandom = { version = "0.2", features = ["js"] }
anyhow = "1"

[dev-dependencies]
wasm-bindgen-test = "~0.3.34"

[profile.release] # This comes from template; didn't touch this yet - docs doesn't tell much about it.
# Tell `rustc` to optimize for small code size.
# opt-level = "s"
48 changes: 0 additions & 48 deletions javascript/README.MD

This file was deleted.

90 changes: 90 additions & 0 deletions javascript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
This is wrapper around `plume_rustcrypto` crate to produce PLUME signatures in JS contexts using Wasm.

TODO add here couple of examples from systems which uses this.

# Getting Started

Get the package from NPM. The repository contains Rust code for generating Wasm and packaging it.

The package usage outline; see the details in subsections.
```js
// ...
let result = plume.sign(isV1, secretKeySec1Der, msg);
console.log(result.nullifier);
result.zeroizePrivateParts();
```

Please, refer to the JS-doc for types description, function signatures, and exceptions notes.

Values in the following examples are in line with tests in the wrapped crate.
## producing the signature
```js
import * as plume from 'plume-sig';

let result = plume.sign(
false,
new Uint8Array([48, 107, 2, 1, 1, 4, 32, 81, 155, 66, 61, 113, 95, 139, 88, 31, 79, 168, 238, 89, 244, 119, 26, 91, 68, 200, 19, 11, 78, 62, 172, 202, 84, 165, 109, 218, 114, 180, 100, 161, 68, 3, 66, 0, 4, 12, 236, 2, 142, 224, 141, 9, 224, 38, 114, 166, 131, 16, 129, 67, 84, 249, 234, 191, 255, 13, 230, 218, 204, 28, 211, 167, 116, 73, 96, 118, 174, 239, 244, 113, 251, 160, 64, 152, 151, 182, 164, 142, 136, 1, 173, 18, 249, 93, 0, 9, 183, 83, 207, 143, 81, 193, 40, 191, 107, 11, 210, 127, 189]),
new Uint8Array([
65, 110, 32, 101, 120, 97, 109, 112, 108, 101, 32, 97, 112, 112, 32, 109, 101, 115, 115, 97, 103, 101, 32, 115, 116, 114, 105, 110, 103
])
);
```
## getters
`PlumeSignature` provide getters for each property of it, so you have access to any of them upon signing.
```js
// ...
console.log(result.nullifier);
/* Uint8Array(33) [
3, 87, 188, 62, 210, 129, 114, 239,
138, 221, 228, 185, 224, 194, 204, 231,
69, 252, 197, 166, 100, 115, 164, 92,
30, 98, 111, 29, 12, 103, 229, 88,
48
] */
console.log(result.s);
/* Uint8Array(109) [
48, 107, 2, 1, 1, 4, 32, 73, 27, 195, 183, 106,
202, 136, 167, 50, 193, 119, 152, 153, 233, 56, 176, 58,
221, 183, 4, 126, 189, 69, 201, 173, 102, 98, 248, 36,
112, 183, 176, 161, 68, 3, 66, 0, 4, 13, 18, 115,
220, 215, 120, 156, 20, 128, 225, 106, 29, 255, 16, 218,
5, 19, 179, 80, 204, 25, 144, 61, 150, 121, 83, 76,
174, 21, 232, 58, 153, 97, 227, 239, 78, 114, 199, 53,
138, 93, 108, 150, 98, 141, 89, 159, 219, 243, 182, 188,
22, 224, 154, 171,
... 9 more items
] */
console.log(result.c);
console.log(result.pk);
console.log(result.message);
console.log(result.v1specific);
// undefined
```
Note that variant is specified by `v1specific`; if it's `undefined` then the object contains V2, otherwise it's V1.
```js
// ...
if (result.v1specific) {
console.log(result.v1specific.r_point);
console.log(result.v1specific.hashed_to_curve_r);
}
```
Also there's #convertion utility provided.
## zeroization
Depending on your context you might want to clear values of the result from Wasm memory after getting the values.
```js
// ...
result.zeroizePrivateParts();
result.zeroizeAll();
```

# #convertion of `s` to `BigInt`
JS most native format for scalar is `BigInt`, but it's not really transportable or secure, so for uniformity of approach `s` in `PlumeSignature` is defined similar to `c`; but if you want to have it as a `BigInt` there's `sec1DerScalarToBigint` helper funtion.

# Working with source files

This package is built with the tech provided by <https://github.com/rustwasm> which contains everything needed to work with it. Also the wrapper crate was initiated with `wasm-pack-template`.

Note that the wrapper crate has `verify` feature which can check the resulting signature.

# License
See <https://github.com/plume-sig/zk-nullifier-sig/blob/main/LICENSE>.
5 changes: 0 additions & 5 deletions javascript/jest.config.js

This file was deleted.

Loading

0 comments on commit 83e3ed9

Please sign in to comment.