mirror of
https://github.com/plume-sig/zk-nullifier-sig.git
synced 2026-01-08 04:23:51 -05:00
Replaces NPM package with Wasm wrapper
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 // <https://github.com/plume-sig/zk-nullifier-sig/issues/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))} }; ```
This commit is contained in:
41
.github/workflows/javascript.yml
vendored
41
.github/workflows/javascript.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Javascript checks
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [18]
|
||||
command: ["prettier", "types", "test:coverage"]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Check javascript
|
||||
run: |
|
||||
pnpm install --no-frozen-lockfile --prefer-offline
|
||||
pnpm run build
|
||||
pnpm run ${{ matrix.command }}
|
||||
working-directory: "./javascript"
|
||||
@@ -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" }
|
||||
|
||||
11
javascript/.appveyor.yml
Normal file
11
javascript/.appveyor.yml
Normal file
@@ -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
javascript/.github/dependabot.yml
vendored
Normal file
8
javascript/.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "08:00"
|
||||
open-pull-requests-limit: 10
|
||||
15
javascript/.gitignore
vendored
15
javascript/.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# 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
|
||||
69
javascript/.travis.yml
Normal file
69
javascript/.travis.yml
Normal file
@@ -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
javascript/Cargo.toml
Normal file
35
javascript/Cargo.toml
Normal file
@@ -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"
|
||||
@@ -1,48 +0,0 @@
|
||||
`plume-sig`
|
||||
==============
|
||||
TypeScript implementation of the ERC-7524 PLUME signature scheme.
|
||||
|
||||
A new type of cryptographic signature that would allow for anonymous and unique digital identities on the Ethereum blockchain in a verifiable way.
|
||||
|
||||
## Installation
|
||||
`npm install plume-sig`
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { computeAllInputs, PlumeVersion } from 'plume-sig';
|
||||
|
||||
return computeAllInputs(message, secretKey);
|
||||
```
|
||||
|
||||
The function returns the signature w.r.t. to given arguments as the object of the following structure.
|
||||
### `plume`
|
||||
`secp256k1` point
|
||||
### `s`
|
||||
`secp256k1` scalar hexstring
|
||||
### `pk`
|
||||
Public key of the signer; SEC1 encoded.
|
||||
### `c`
|
||||
SHA-256 hash. It's value depends on `PlumeVersion` of the signature.
|
||||
### `rPoint`
|
||||
`secp256k1` point representing the unique random scalar used for signing. V1 specific.
|
||||
### `hashedToCurveR`
|
||||
`secp256k1` point. V1 specific.
|
||||
|
||||
## Signature variants
|
||||
The scheme comes in two variants. V2 is default for this implementation.
|
||||
|
||||
### Version 1: Verifier Optimized
|
||||
|
||||
In a situation where there is a verifier who must *not* know the signer's `pk`, but the signer must nevertheless prove that they know `secretKey` corresponding to the signature given `message`, a zero-knowledge proof is required.
|
||||
|
||||
The following verification function may be described via a circuit as part of a non-interactive zero-knowledge proving system, such as Groth16. To create a proof, the prover supplies the following inputs:
|
||||
|
||||
### Version 2: Prover Optimized
|
||||
|
||||
Currently, SHA-256 hashing operations are particularly expensive with zk proofs in the browser. In the context of PLUME, the computation of $c$ is a bottleneck for efficient proof times, so one modification suggested by the Poseidon team was to move this hash computation outside the circuit, into the verifier.
|
||||
|
||||
Due to SHA-256 being a native precompile on Ethereum, this operation will still be efficient for smart contract verifiers.
|
||||
|
||||
## License
|
||||
MIT
|
||||
90
javascript/README.md
Normal file
90
javascript/README.md
Normal file
@@ -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>.
|
||||
@@ -1,5 +0,0 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "plume-sig",
|
||||
"version": "2.0.9",
|
||||
"keywords": ["nullifier", "zero-knowledge", "ECDSA", "PLUME"],
|
||||
"repository": "https://github.com/plume-sig/zk-nullifier-sig/",
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@noble/secp256k1": "$@noble/secp256k1"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 <19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/js-sha512": "^0",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/nodemon": "^1.19.2",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"prettier": "^3.0.3",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@noble/secp256k1": "^1.7.0",
|
||||
"amcl-js": "^3.1.0",
|
||||
"js-sha256": "^0.10.1"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"start": "nodemon",
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"prettier": "prettier -c . --config ../.prettierrc --ignore-path ../.prettierignore",
|
||||
"prettier:fix": "prettier -w . --config ../.prettierrc --ignore-path ../.prettierignore",
|
||||
"types": "tsc -p tsconfig.json --noEmit",
|
||||
"test": "jest",
|
||||
"test:coverage": "pnpm run test --coverage",
|
||||
"publish": "pnpm run build && pnpm publish"
|
||||
}
|
||||
}
|
||||
2695
javascript/pnpm-lock.yaml
generated
2695
javascript/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
export { computeAllInputs, PlumeVersion } from "./signals";
|
||||
197
javascript/src/lib.rs
Normal file
197
javascript/src/lib.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
//! sadly `wasm-bindgen` doesn't support top-level @module docs yet
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
use std::convert::TryInto;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
use elliptic_curve::sec1::FromEncodedPoint;
|
||||
use elliptic_curve::sec1::ToEncodedPoint;
|
||||
use signature::RandomizedSigner;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
/// @typedef {Object} PlumeSignature - Wrapper around [`plume_rustcrypto::PlumeSignature`](https://docs.rs/plume_rustcrypto/latest/plume_rustcrypto/struct.PlumeSignature.html).
|
||||
/// [`plume_rustcrypto::AffinePoint`](https://docs.rs/plume_rustcrypto/latest/plume_rustcrypto/struct.AffinePoint.html) is represented as a `Uint8Array` containing SEC1 encoded point.
|
||||
/// [`plume_rustcrypto::NonZeroScalar`](https://docs.rs/plume_rustcrypto/latest/plume_rustcrypto/type.NonZeroScalar.html) is represented as a `Uint8Array` containing SEC1 DER secret key.
|
||||
/// `Option` can be `undefined` or instance of [`PlumeSignatureV1Fields`].
|
||||
pub struct PlumeSignature {
|
||||
pub message: Vec<u8>,
|
||||
pub pk: Vec<u8>,
|
||||
pub nullifier: Vec<u8>,
|
||||
pub c: Vec<u8>,
|
||||
pub s: Vec<u8>,
|
||||
pub v1specific: Option<PlumeSignatureV1Fields>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
/// @typedef {Object} PlumeSignatureV1Fields - Wrapper around [`plume_rustcrypto::PlumeSignatureV1Fields`](https://docs.rs/plume_rustcrypto/latest/plume_rustcrypto/struct.PlumeSignatureV1Fields.html).
|
||||
#[derive(Clone)]
|
||||
pub struct PlumeSignatureV1Fields {
|
||||
pub r_point: Vec<u8>,
|
||||
pub hashed_to_curve_r: Vec<u8>,
|
||||
}
|
||||
#[wasm_bindgen()]
|
||||
impl PlumeSignatureV1Fields {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(r_point: Vec<u8>, hashed_to_curve_r: Vec<u8>) -> PlumeSignatureV1Fields {
|
||||
PlumeSignatureV1Fields {
|
||||
r_point,
|
||||
hashed_to_curve_r,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl PlumeSignature {
|
||||
#[cfg(feature = "verify")]
|
||||
/// @deprecated Use this only for testing purposes.
|
||||
/// @throws an error if the data in the object doesn't let it to properly run verification; message contains nature of the problem and indicates relevant property of the object. In case of other (crypto) problems returns `false`.
|
||||
pub fn verify(self) -> Result<bool, JsError> {
|
||||
Ok(plume_rustcrypto::PlumeSignature::verify(&self.try_into()?))
|
||||
}
|
||||
|
||||
/// there's no case for constructing it from values, so this only used internally and for testing
|
||||
/// `v1specific` discriminates if it's V1 or V2 scheme used. Pls, see wrapped docs for details.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
message: Vec<u8>,
|
||||
pk: Vec<u8>,
|
||||
nullifier: Vec<u8>,
|
||||
c: Vec<u8>,
|
||||
s: Vec<u8>,
|
||||
v1specific: Option<PlumeSignatureV1Fields>,
|
||||
) -> PlumeSignature {
|
||||
PlumeSignature {
|
||||
/* I really wonder how good is this pattern. But taking so much of args isn't good, and builder pattern seems redundant as all
|
||||
of the fields are required, and setters are just assignments. */
|
||||
// Actually there's no case for constructing it from values, so this only used internally and for testing.
|
||||
message,
|
||||
pk,
|
||||
nullifier,
|
||||
c,
|
||||
s,
|
||||
v1specific, //: if v1specific.is_falsy() {None} else {Some(v1specific)}
|
||||
}
|
||||
|
||||
// js_sys::Object::from_entries(&values)?
|
||||
// values.get
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = zeroizePrivateParts)]
|
||||
/// Zeroize private values of the object from Wasm memory.
|
||||
pub fn zeroize_privateparts(&mut self) {
|
||||
self.c.zeroize();
|
||||
self.pk.zeroize();
|
||||
}
|
||||
#[wasm_bindgen(js_name = zeroizeAll)]
|
||||
/// Zeroize all values of the object from Wasm memory.
|
||||
pub fn zeroize_all(&mut self) {
|
||||
self.zeroize_privateparts();
|
||||
self.message.zeroize();
|
||||
self.nullifier.zeroize();
|
||||
self.s.zeroize();
|
||||
if let Some(v1) = self.v1specific.as_mut() {
|
||||
v1.hashed_to_curve_r.zeroize();
|
||||
v1.r_point.zeroize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(skip_jsdoc)]
|
||||
/// @throws a "crypto error" in case of a problem with the secret key
|
||||
/// @param {boolean} v1 - is the flag to choose between V1 and V2 output.
|
||||
/// @param {Uint8Array} sk - secret key in SEC1 DER format.
|
||||
/// @param {Uint8Array} msg
|
||||
/// @returns {PlumeSignature}
|
||||
pub fn sign(v1: bool, sk: &mut [u8], msg: &[u8]) -> Result<PlumeSignature, JsError> {
|
||||
let sk_z = plume_rustcrypto::SecretKey::from_sec1_der(sk)?;
|
||||
sk.zeroize();
|
||||
let signer = plume_rustcrypto::randomizedsigner::PlumeSigner::new(&sk_z, v1);
|
||||
|
||||
Ok(signer
|
||||
.sign_with_rng(&mut signature::rand_core::OsRng, msg)
|
||||
.into())
|
||||
}
|
||||
|
||||
// TODO deprecate when `verify` gone
|
||||
#[cfg(feature = "verify")]
|
||||
impl TryInto<plume_rustcrypto::PlumeSignature> for PlumeSignature {
|
||||
type Error = JsError;
|
||||
|
||||
fn try_into(self) -> Result<plume_rustcrypto::PlumeSignature, Self::Error> {
|
||||
let point_check = |point_bytes: Vec<u8>| -> Result<AffinePoint, anyhow::Error> {
|
||||
let point_encoded = sec1::point::EncodedPoint::from_bytes(point_bytes)?; // TODO improve formatting (quotes e.g.)
|
||||
let result = plume_rustcrypto::AffinePoint::from_encoded_point(&point_encoded);
|
||||
if result.is_none().into() {
|
||||
Err(anyhow::Error::msg("the point isn't on the curve"))
|
||||
} else {
|
||||
Ok(result.expect("`None` is processed the line above"))
|
||||
}
|
||||
};
|
||||
|
||||
let err_field_wrap = |name_field: &str, er: anyhow::Error| -> JsError {
|
||||
JsError::new(
|
||||
("while proccessing ".to_owned() + name_field + " :" + er.to_string().as_str())
|
||||
.as_str(),
|
||||
)
|
||||
};
|
||||
|
||||
Ok(plume_rustcrypto::PlumeSignature {
|
||||
message: self.message,
|
||||
pk: point_check(self.pk).map_err(|er| err_field_wrap("`pk`", er))?,
|
||||
// plume_rustcrypto::AffinePoint::try_from(self.pk)?, //.try_into<[u8; 33]>()?.into(),
|
||||
nullifier: point_check(self.nullifier)
|
||||
.map_err(|er| err_field_wrap("`nullifier`", er))?,
|
||||
c: plume_rustcrypto::SecretKey::from_sec1_der(&self.c)?.into(),
|
||||
s: plume_rustcrypto::SecretKey::from_sec1_der(&self.s)?.into(), //scalar_from_bigint(self.s).map_err(|er| err_field_wrap("`s`", er))?,
|
||||
v1specific: if let Some(v1) = self.v1specific {
|
||||
Some(plume_rustcrypto::PlumeSignatureV1Fields {
|
||||
r_point: point_check(v1.r_point)
|
||||
.map_err(|er| err_field_wrap("`r_point`", er))?,
|
||||
hashed_to_curve_r: point_check(v1.hashed_to_curve_r)
|
||||
.map_err(|er| err_field_wrap("`hashed_to_curve_r`", er))?,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<plume_rustcrypto::PlumeSignature> for PlumeSignature {
|
||||
fn from(value: plume_rustcrypto::PlumeSignature) -> Self {
|
||||
PlumeSignature {
|
||||
message: value.message,
|
||||
pk: value.pk.to_encoded_point(true).as_bytes().to_vec(),
|
||||
nullifier: value.nullifier.to_encoded_point(true).as_bytes().to_vec(),
|
||||
c: plume_rustcrypto::SecretKey::from(value.c).to_sec1_der().expect("`k256` restricts this type to proper keys, so it's serialized representation shouldn't have a chance to fail")
|
||||
.to_vec(),
|
||||
s: plume_rustcrypto::SecretKey::from(value.s).to_sec1_der().expect("`k256` restricts this type to proper keys, so it's serialized representation shouldn't have a chance to fail")
|
||||
.to_vec(),
|
||||
v1specific: value.v1specific.map(|v1| {PlumeSignatureV1Fields {
|
||||
r_point: v1.r_point.to_encoded_point(true).as_bytes().to_vec(),
|
||||
hashed_to_curve_r: v1.hashed_to_curve_r.to_encoded_point(true).as_bytes().to_vec(),
|
||||
}})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = sec1DerScalarToBigint)]
|
||||
/// This might leave values in memory! Don't use for private values.
|
||||
/// 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` this util is left here.
|
||||
pub fn sec1derscalar_to_bigint(scalar: &[u8]) -> Result<js_sys::BigInt, JsError> {
|
||||
Ok(js_sys::BigInt::new(&JsValue::from_str(
|
||||
("0x".to_owned()
|
||||
+ plume_rustcrypto::SecretKey::from_sec1_der(scalar)?
|
||||
.to_nonzero_scalar()
|
||||
.to_string()
|
||||
.as_str())
|
||||
.as_str(),
|
||||
))
|
||||
.expect(
|
||||
"`BigInt` always can be created from hex string, and `v.to_string()` always produce that",
|
||||
))
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
import { CURVE, getPublicKey, Point, utils } from "@noble/secp256k1";
|
||||
import {
|
||||
concatUint8Arrays,
|
||||
hexToBigInt,
|
||||
hexToUint8Array,
|
||||
messageToUint8Array,
|
||||
uint8ArrayToBigInt,
|
||||
} from "./utils/encoding";
|
||||
import hashToCurve from "./utils/hashToCurve";
|
||||
import { HashedPoint, multiplyPoint } from "./utils/curve";
|
||||
import { sha256 } from "js-sha256";
|
||||
|
||||
// PLUME version
|
||||
export enum PlumeVersion {
|
||||
V1 = 1,
|
||||
V2 = 2,
|
||||
}
|
||||
|
||||
export function computeHashToCurve(
|
||||
message: Uint8Array,
|
||||
pk: Uint8Array,
|
||||
): HashedPoint {
|
||||
// Concatenate message and publicKey
|
||||
const preimage = new Uint8Array(message.length + pk.length);
|
||||
preimage.set(message);
|
||||
preimage.set(pk, message.length);
|
||||
return hashToCurve(Array.from(preimage));
|
||||
}
|
||||
|
||||
export function computeC_V2(
|
||||
nullifier: Point,
|
||||
rPoint: Point,
|
||||
hashedToCurveR: Point,
|
||||
) {
|
||||
const nullifierBytes = nullifier.toRawBytes(true);
|
||||
const preimage = concatUint8Arrays([
|
||||
nullifierBytes,
|
||||
rPoint.toRawBytes(true),
|
||||
hashedToCurveR.toRawBytes(true),
|
||||
]);
|
||||
return sha256.create().update(preimage).hex();
|
||||
}
|
||||
|
||||
export function computeC_V1(
|
||||
pkBytes: Uint8Array,
|
||||
hashedToCurve: HashedPoint,
|
||||
nullifier: Point,
|
||||
rPoint: Point,
|
||||
hashedToCurveR: Point,
|
||||
) {
|
||||
const nullifierBytes = nullifier.toRawBytes(true);
|
||||
const preimage = concatUint8Arrays([
|
||||
Point.BASE.toRawBytes(true),
|
||||
pkBytes,
|
||||
new Point(
|
||||
hexToBigInt(hashedToCurve.x.toString()),
|
||||
hexToBigInt(hashedToCurve.y.toString()),
|
||||
).toRawBytes(true),
|
||||
nullifierBytes,
|
||||
rPoint.toRawBytes(true),
|
||||
hashedToCurveR.toRawBytes(true),
|
||||
]);
|
||||
return sha256.create().update(preimage).hex();
|
||||
}
|
||||
|
||||
export function computeNullifer(hashedToCurve: HashedPoint, sk: Uint8Array) {
|
||||
return multiplyPoint(hashedToCurve, sk);
|
||||
}
|
||||
|
||||
export function computeRPoint(rScalar: Uint8Array) {
|
||||
return Point.fromPrivateKey(rScalar);
|
||||
}
|
||||
|
||||
export function computeHashToCurveR(
|
||||
hashedToCurve: HashedPoint,
|
||||
rScalar: Uint8Array,
|
||||
) {
|
||||
return multiplyPoint(hashedToCurve, rScalar);
|
||||
}
|
||||
|
||||
export function computeS(rScalar: Uint8Array, sk: Uint8Array, c: string) {
|
||||
return (
|
||||
(((uint8ArrayToBigInt(sk) * hexToBigInt(c)) % CURVE.n) +
|
||||
uint8ArrayToBigInt(rScalar)) %
|
||||
CURVE.n
|
||||
).toString(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and returns the Plume and other signals for the prover.
|
||||
* @param {string | Uint8Array} message - Message to sign, in either string or UTF-8 array format.
|
||||
* @param {string | Uint8Array} sk - ECDSA secret key to sign with.
|
||||
* @param {string| Uint8Array} rScalar - Optional seed for randomness.
|
||||
* @returns Object containing Plume and other signals - public key, s, c, gPowR, and hashMPKPowR.
|
||||
*/
|
||||
export function computeAllInputs(
|
||||
message: string | Uint8Array,
|
||||
sk: string | Uint8Array,
|
||||
rScalar?: string | Uint8Array,
|
||||
version: PlumeVersion = PlumeVersion.V2,
|
||||
) {
|
||||
const skBytes = typeof sk === "string" ? hexToUint8Array(sk) : sk;
|
||||
const messageBytes =
|
||||
typeof message === "string" ? messageToUint8Array(message) : message;
|
||||
const pkBytes = getPublicKey(skBytes, true);
|
||||
|
||||
let rScalarBytes: Uint8Array;
|
||||
if (rScalar) {
|
||||
rScalarBytes =
|
||||
typeof rScalar === "string" ? hexToUint8Array(rScalar) : rScalar;
|
||||
} else {
|
||||
rScalarBytes = utils.randomPrivateKey();
|
||||
}
|
||||
|
||||
const hashedToCurve = computeHashToCurve(messageBytes, pkBytes);
|
||||
const nullifier = computeNullifer(hashedToCurve, skBytes);
|
||||
const hashedToCurveR = computeHashToCurveR(hashedToCurve, rScalarBytes);
|
||||
const rPoint = computeRPoint(rScalarBytes);
|
||||
const c =
|
||||
version == PlumeVersion.V1
|
||||
? computeC_V1(pkBytes, hashedToCurve, nullifier, rPoint, hashedToCurveR)
|
||||
: computeC_V2(nullifier, rPoint, hashedToCurveR);
|
||||
const s = computeS(rScalarBytes, skBytes, c);
|
||||
|
||||
return {
|
||||
plume: nullifier,
|
||||
s,
|
||||
pk: pkBytes,
|
||||
c,
|
||||
rPoint,
|
||||
hashedToCurveR,
|
||||
};
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Point } from "@noble/secp256k1";
|
||||
import { uint8ArrayToHex } from "./encoding";
|
||||
|
||||
export interface HashedPoint {
|
||||
x: {
|
||||
toString(): string;
|
||||
};
|
||||
y: {
|
||||
toString(): string;
|
||||
};
|
||||
}
|
||||
|
||||
export function multiplyPoint(h: HashedPoint, secretKey: Uint8Array) {
|
||||
const hashPoint = new Point(
|
||||
BigInt("0x" + h.x.toString()),
|
||||
BigInt("0x" + h.y.toString()),
|
||||
);
|
||||
|
||||
return hashPoint.multiply(BigInt("0x" + uint8ArrayToHex(secretKey)));
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
const utf8Encoder = new TextEncoder();
|
||||
|
||||
export function messageToUint8Array(message: string): Uint8Array {
|
||||
return utf8Encoder.encode(message);
|
||||
}
|
||||
|
||||
export function hexToUint8Array(hexString: string): Uint8Array {
|
||||
// Source: https://stackoverflow.com/questions/38987784/how-to-convert-a-hexadecimal-string-to-uint8array-and-back-in-javascript/50868276#50868276
|
||||
return Uint8Array.from(
|
||||
hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)),
|
||||
);
|
||||
}
|
||||
|
||||
export function uint8ArrayToHex(uint8Array: Uint8Array) {
|
||||
// Source: https://stackoverflow.com/questions/38987784/how-to-convert-a-hexadecimal-string-to-uint8array-and-back-in-javascript/50868276#50868276
|
||||
return uint8Array.reduce(
|
||||
(str, byte) => str + byte.toString(16).padStart(2, "0"),
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
export function hexToBigInt(hex: string): bigint {
|
||||
return BigInt("0x" + hex);
|
||||
}
|
||||
|
||||
export function uint8ArrayToBigInt(buffer: Uint8Array): bigint {
|
||||
return hexToBigInt(uint8ArrayToHex(buffer));
|
||||
}
|
||||
|
||||
export function asciitobytes(s: string): number[] {
|
||||
const b: number[] = [];
|
||||
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
b.push(s.charCodeAt(i));
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
export function concatUint8Arrays(arrays: Uint8Array[]) {
|
||||
// sum of individual array lengths
|
||||
const totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
|
||||
|
||||
const result = new Uint8Array(totalLength);
|
||||
|
||||
if (!arrays.length) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// for each array - copy it over result
|
||||
// next array is copied right after the previous one
|
||||
let length = 0;
|
||||
for (let array of arrays) {
|
||||
result.set(array, length);
|
||||
length += array.length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { CTX } from "amcl-js";
|
||||
import { asciitobytes } from "./encoding";
|
||||
|
||||
// Refactored from miracl-core
|
||||
const ctx = new CTX("SECP256K1") as any;
|
||||
const ro = "QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_RO_";
|
||||
const hlen = ctx.ECP.HASH_TYPE;
|
||||
|
||||
function ceil(a, b) {
|
||||
return Math.floor((a - 1) / b + 1);
|
||||
}
|
||||
|
||||
function hashToField(ctx, hash, hlen, DST, M, ctr) {
|
||||
const u = [];
|
||||
const q = new ctx.BIG(0);
|
||||
q.rcopy(ctx.ROM_FIELD.Modulus);
|
||||
const k = q.nbits();
|
||||
const r = new ctx.BIG(0);
|
||||
r.rcopy(ctx.ROM_CURVE.CURVE_Order);
|
||||
const m = r.nbits();
|
||||
const L = ceil(k + ceil(m, 2), 8);
|
||||
const OKM = ctx.HMAC.XMD_Expand(hash, hlen, L * ctr, DST, M);
|
||||
const fd = [];
|
||||
|
||||
for (let i = 0; i < ctr; i++) {
|
||||
for (let j = 0; j < L; j++) {
|
||||
fd[j] = OKM[i * L + j];
|
||||
}
|
||||
|
||||
const dx = ctx.DBIG.fromBytes(fd);
|
||||
const w = new ctx.FP(dx.mod(q));
|
||||
u[i] = new ctx.FP(w);
|
||||
}
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
// Taken from https://github.com/miracl/core/blob/master/javascript/examples/node/TestHTP.js#L37
|
||||
function hashToPairing(ctx, M, ro, hlen) {
|
||||
const DSTRO = asciitobytes(ro);
|
||||
const u = hashToField(ctx, ctx.HMAC.MC_SHA2, hlen, DSTRO, M, 2);
|
||||
const P = ctx.ECP.map2point(u[0]);
|
||||
const P1 = ctx.ECP.map2point(u[1]);
|
||||
P.add(P1);
|
||||
P.cfp();
|
||||
P.affine();
|
||||
|
||||
return P;
|
||||
}
|
||||
|
||||
export default function hashToCurve(bytes: number[]) {
|
||||
return hashToPairing(ctx, bytes, ro, hlen);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { getPublicKey, Point } from "@noble/secp256k1";
|
||||
import {
|
||||
computeC_V1,
|
||||
computeC_V2,
|
||||
computeRPoint,
|
||||
computeHashToCurve,
|
||||
computeHashToCurveR,
|
||||
computeNullifer,
|
||||
computeS,
|
||||
} from "../src/signals";
|
||||
import { hexToUint8Array, messageToUint8Array } from "../src/utils/encoding";
|
||||
|
||||
export const testSecretKey = hexToUint8Array(
|
||||
"519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464",
|
||||
);
|
||||
|
||||
export const testPublicKeyPoint = Point.fromPrivateKey(testSecretKey);
|
||||
export const testPublicKey = getPublicKey(testSecretKey, true);
|
||||
|
||||
export const testR = hexToUint8Array(
|
||||
"93b9323b629f251b8f3fc2dd11f4672c5544e8230d493eceea98a90bda789808",
|
||||
);
|
||||
export const testMessageString = "An example app message string";
|
||||
export const testMessage = messageToUint8Array(testMessageString);
|
||||
export const hashMPk = computeHashToCurve(
|
||||
testMessage,
|
||||
Buffer.from(testPublicKey),
|
||||
);
|
||||
export const nullifier = computeNullifer(hashMPk, testSecretKey);
|
||||
export const hashedToCurveR = computeHashToCurveR(hashMPk, testR);
|
||||
export const rPoint = computeRPoint(testR);
|
||||
export const c_v1 = computeC_V1(
|
||||
testPublicKey,
|
||||
hashMPk,
|
||||
nullifier as unknown as Point,
|
||||
rPoint,
|
||||
hashedToCurveR,
|
||||
);
|
||||
export const s_v1 = computeS(testR, testSecretKey, c_v1);
|
||||
|
||||
export const c_v2 = computeC_V2(nullifier, rPoint, hashedToCurveR);
|
||||
export const s_v2 = computeS(testR, testSecretKey, c_v2);
|
||||
@@ -1,43 +0,0 @@
|
||||
import {
|
||||
hexToBigInt,
|
||||
hexToUint8Array,
|
||||
uint8ArrayToBigInt,
|
||||
uint8ArrayToHex,
|
||||
} from "../src/utils/encoding";
|
||||
|
||||
const TEST_VALS = [
|
||||
{
|
||||
hex: "a413bc5f",
|
||||
uint8: Uint8Array.from([164, 19, 188, 95]),
|
||||
bigint: 2752756831n,
|
||||
},
|
||||
{
|
||||
hex: "f09f8fb3efb88fe2808df09f8c88",
|
||||
uint8: Uint8Array.from([
|
||||
240, 159, 143, 179, 239, 184, 143, 226, 128, 141, 240, 159, 140, 136,
|
||||
]),
|
||||
bigint: 4880420056602345253094210752449672n,
|
||||
},
|
||||
];
|
||||
|
||||
describe("encoding", () => {
|
||||
it("hexToUint8Array", () => {
|
||||
expect(hexToUint8Array(TEST_VALS[0].hex)).toEqual(TEST_VALS[0].uint8);
|
||||
expect(hexToUint8Array(TEST_VALS[1].hex)).toEqual(TEST_VALS[1].uint8);
|
||||
});
|
||||
|
||||
it("uint8ArrayToHex", () => {
|
||||
expect(uint8ArrayToHex(TEST_VALS[0].uint8)).toEqual(TEST_VALS[0].hex);
|
||||
expect(uint8ArrayToHex(TEST_VALS[1].uint8)).toEqual(TEST_VALS[1].hex);
|
||||
});
|
||||
|
||||
it("hexToBigInt", () => {
|
||||
expect(hexToBigInt(TEST_VALS[0].hex)).toEqual(TEST_VALS[0].bigint);
|
||||
expect(hexToBigInt(TEST_VALS[1].hex)).toEqual(TEST_VALS[1].bigint);
|
||||
});
|
||||
|
||||
it("uint8ArrayToBigInt", () => {
|
||||
expect(uint8ArrayToBigInt(TEST_VALS[0].uint8)).toEqual(TEST_VALS[0].bigint);
|
||||
expect(uint8ArrayToBigInt(TEST_VALS[1].uint8)).toEqual(TEST_VALS[1].bigint);
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import hashToCurve from "../src/utils/hashToCurve";
|
||||
|
||||
describe("hashToCurve", () => {
|
||||
it("successfully hashes correct values", () => {
|
||||
const testPreimage = [
|
||||
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, 3, 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,
|
||||
];
|
||||
|
||||
const hash = hashToCurve(testPreimage);
|
||||
|
||||
expect(hash.x.toString()).toEqual(
|
||||
"bcac2d0e12679f23c218889395abcdc01f2affbc49c54d1136a2190db0800b65",
|
||||
);
|
||||
expect(hash.y.toString()).toEqual(
|
||||
"3bcfb339c974c0e757d348081f90a123b0a91a53e32b3752145d87f0cd70966e",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,166 +0,0 @@
|
||||
import {
|
||||
hashMPk,
|
||||
nullifier,
|
||||
hashedToCurveR,
|
||||
rPoint,
|
||||
c_v1,
|
||||
s_v1,
|
||||
c_v2,
|
||||
s_v2,
|
||||
testPublicKey,
|
||||
testSecretKey,
|
||||
testMessage,
|
||||
testR,
|
||||
} from "./consts";
|
||||
|
||||
import { computeAllInputs } from "../src";
|
||||
import { PlumeVersion } from "../src/signals";
|
||||
|
||||
describe("signals", () => {
|
||||
it("generates hash(m, pk)", () => {
|
||||
expect(hashMPk.x.toString()).toEqual(
|
||||
"bcac2d0e12679f23c218889395abcdc01f2affbc49c54d1136a2190db0800b65",
|
||||
);
|
||||
expect(hashMPk.y.toString()).toEqual(
|
||||
"3bcfb339c974c0e757d348081f90a123b0a91a53e32b3752145d87f0cd70966e",
|
||||
);
|
||||
});
|
||||
|
||||
it("generates nullifier (hash(m, pk))^sk", () => {
|
||||
expect(nullifier.x.toString(16)).toEqual(
|
||||
"57bc3ed28172ef8adde4b9e0c2cce745fcc5a66473a45c1e626f1d0c67e55830",
|
||||
);
|
||||
expect(nullifier.y.toString(16)).toEqual(
|
||||
"6a2f41488d58f33ae46edd2188e111609f9f3ae67ea38fa891d6087fe59ecb73",
|
||||
);
|
||||
});
|
||||
|
||||
describe("Plume V1", () => {
|
||||
it("generates c and intermediate values correctly", () => {
|
||||
expect(hashedToCurveR.x.toString(16)).toEqual(
|
||||
"6d017c6f63c59fa7a5b1e9a654e27d2869579f4d152131db270558fccd27b97c",
|
||||
);
|
||||
|
||||
expect(hashedToCurveR.y.toString(16)).toEqual(
|
||||
"586c43fb5c99818c564a8f80a88a65f83e3f44d3c6caf5a1a4e290b777ac56ed",
|
||||
);
|
||||
|
||||
expect(rPoint.x.toString(16)).toEqual(
|
||||
"9d8ca4350e7e2ad27abc6d2a281365818076662962a28429590e2dc736fe9804",
|
||||
);
|
||||
expect(rPoint.y.toString(16)).toEqual(
|
||||
"ff08c30b8afd4e854623c835d9c3aac6bcebe45112472d9b9054816a7670c5a1",
|
||||
);
|
||||
|
||||
expect(c_v1).toEqual(
|
||||
"c6a7fc2c926ddbaf20731a479fb6566f2daa5514baae5223fe3b32edbce83254",
|
||||
);
|
||||
});
|
||||
|
||||
it("generates an s signal", () => {
|
||||
expect(s_v1).toEqual(
|
||||
"e69f027d84cb6fe5f761e333d12e975fb190d163e8ea132d7de0bd6079ba28ca",
|
||||
);
|
||||
});
|
||||
|
||||
it("generates all signals", () => {
|
||||
const { plume, s, pk, c, rPoint, hashedToCurveR } = computeAllInputs(
|
||||
testMessage,
|
||||
testSecretKey,
|
||||
testR,
|
||||
PlumeVersion.V1,
|
||||
);
|
||||
|
||||
expect(pk).toEqual(testPublicKey);
|
||||
expect(rPoint.x.toString(16)).toEqual(
|
||||
"9d8ca4350e7e2ad27abc6d2a281365818076662962a28429590e2dc736fe9804",
|
||||
);
|
||||
expect(rPoint.y.toString(16)).toEqual(
|
||||
"ff08c30b8afd4e854623c835d9c3aac6bcebe45112472d9b9054816a7670c5a1",
|
||||
);
|
||||
expect(plume.x.toString(16)).toEqual(
|
||||
"57bc3ed28172ef8adde4b9e0c2cce745fcc5a66473a45c1e626f1d0c67e55830",
|
||||
);
|
||||
expect(plume.y.toString(16)).toEqual(
|
||||
"6a2f41488d58f33ae46edd2188e111609f9f3ae67ea38fa891d6087fe59ecb73",
|
||||
);
|
||||
expect(hashedToCurveR.x.toString(16)).toEqual(
|
||||
"6d017c6f63c59fa7a5b1e9a654e27d2869579f4d152131db270558fccd27b97c",
|
||||
);
|
||||
expect(c).toEqual(
|
||||
"c6a7fc2c926ddbaf20731a479fb6566f2daa5514baae5223fe3b32edbce83254",
|
||||
);
|
||||
expect(s).toEqual(
|
||||
"e69f027d84cb6fe5f761e333d12e975fb190d163e8ea132d7de0bd6079ba28ca",
|
||||
);
|
||||
expect(hashedToCurveR.y.toString(16)).toEqual(
|
||||
"586c43fb5c99818c564a8f80a88a65f83e3f44d3c6caf5a1a4e290b777ac56ed",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Plume V2", () => {
|
||||
it("generates c and intermediate values correctly", () => {
|
||||
expect(hashedToCurveR.x.toString(16)).toEqual(
|
||||
"6d017c6f63c59fa7a5b1e9a654e27d2869579f4d152131db270558fccd27b97c",
|
||||
);
|
||||
|
||||
expect(hashedToCurveR.y.toString(16)).toEqual(
|
||||
"586c43fb5c99818c564a8f80a88a65f83e3f44d3c6caf5a1a4e290b777ac56ed",
|
||||
);
|
||||
|
||||
expect(rPoint.x.toString(16)).toEqual(
|
||||
"9d8ca4350e7e2ad27abc6d2a281365818076662962a28429590e2dc736fe9804",
|
||||
);
|
||||
|
||||
expect(rPoint.y.toString(16)).toEqual(
|
||||
"ff08c30b8afd4e854623c835d9c3aac6bcebe45112472d9b9054816a7670c5a1",
|
||||
);
|
||||
|
||||
expect(c_v2).toEqual(
|
||||
"3dbfb717705010d4f44a70720c95e74b475bd3a783ab0b9e8a6b3b363434eb96",
|
||||
);
|
||||
});
|
||||
|
||||
it("generates an s signal", () => {
|
||||
expect(s_v2).toEqual(
|
||||
"528e8fbb6452f82200797b1a73b2947a92524bd611085a920f1177cb8098136b",
|
||||
);
|
||||
});
|
||||
|
||||
it("generates all signals", () => {
|
||||
const { plume, s, pk, c, rPoint, hashedToCurveR } = computeAllInputs(
|
||||
testMessage,
|
||||
testSecretKey,
|
||||
testR,
|
||||
PlumeVersion.V2,
|
||||
);
|
||||
|
||||
expect(pk).toEqual(testPublicKey);
|
||||
expect(rPoint.x.toString(16)).toEqual(
|
||||
"9d8ca4350e7e2ad27abc6d2a281365818076662962a28429590e2dc736fe9804",
|
||||
);
|
||||
expect(rPoint.y.toString(16)).toEqual(
|
||||
"ff08c30b8afd4e854623c835d9c3aac6bcebe45112472d9b9054816a7670c5a1",
|
||||
);
|
||||
expect(plume.x.toString(16)).toEqual(
|
||||
"57bc3ed28172ef8adde4b9e0c2cce745fcc5a66473a45c1e626f1d0c67e55830",
|
||||
);
|
||||
expect(plume.y.toString(16)).toEqual(
|
||||
"6a2f41488d58f33ae46edd2188e111609f9f3ae67ea38fa891d6087fe59ecb73",
|
||||
);
|
||||
expect(hashedToCurveR.x.toString(16)).toEqual(
|
||||
"6d017c6f63c59fa7a5b1e9a654e27d2869579f4d152131db270558fccd27b97c",
|
||||
);
|
||||
expect(c).toEqual(
|
||||
"3dbfb717705010d4f44a70720c95e74b475bd3a783ab0b9e8a6b3b363434eb96",
|
||||
);
|
||||
expect(s).toEqual(
|
||||
"528e8fbb6452f82200797b1a73b2947a92524bd611085a920f1177cb8098136b",
|
||||
);
|
||||
expect(hashedToCurveR.y.toString(16)).toEqual(
|
||||
"586c43fb5c99818c564a8f80a88a65f83e3f44d3c6caf5a1a4e290b777ac56ed",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["test/**/*.test.ts"]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2021",
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"baseUrl": ".",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"compileOnSave": true,
|
||||
"include": ["src", "test"]
|
||||
}
|
||||
Reference in New Issue
Block a user