From e011081e707ef01fbf8729cb9870f9ba522c73f5 Mon Sep 17 00:00:00 2001 From: skaunov Date: Sun, 7 Jul 2024 15:32:00 +0300 Subject: [PATCH] Logic finished # 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 // 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 // // 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 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 { // ... 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 { 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))} }; ``` --- Cargo.toml | 2 +- javascript/Cargo.toml | 30 ++-- javascript/src/lib.rs | 309 ++++++++++++---------------------------- javascript/src/utils.rs | 10 -- 4 files changed, 103 insertions(+), 248 deletions(-) delete mode 100644 javascript/src/utils.rs diff --git a/Cargo.toml b/Cargo.toml index 7197ef6..97028a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/javascript/Cargo.toml b/javascript/Cargo.toml index 0de3ce6..81c345f 100644 --- a/javascript/Cargo.toml +++ b/javascript/Cargo.toml @@ -10,38 +10,30 @@ repository = "https://github.com/plume-sig/zk-nullifier-sig/" crate-type = ["cdylib", "rlib"] [features] -default = ["console_error_panic_hook"] +# I'd alias this to `sec1` if that won't be trickyt +verify = ["dep:sec1"] [dependencies] wasm-bindgen = "~0.2.84" - -# The `console_error_panic_hook` crate provides better debugging of panics by -# logging them with `console.error`. This is great for development, but requires -# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for -# code size when deploying. -console_error_panic_hook = { version = "0.1.7", optional = true } - js-sys = "0.3" -plume_rustcrypto = "0.2" # TODO change to -rand_chacha = "0.3" -sec1 = "0.7.3" # match with `k256` -elliptic-curve = {version = "0.13.8", features = ["sec1"]} # match with `k256` -hex = "0.4" -k256 = "~0.13.3" # match with `k256` +plume_rustcrypto = {version = "~0.2.1", default-features = false} +# rand_chacha = "0.3" +sec1 = {version = "0.7.3", optional = true} # match with `k256` +# elliptic-curve = {version = "0.13.8", features = ["sec1"]} # match with `k256` +elliptic-curve = {version = "0.13.8"} +# hex = "0.4" +# k256 = "~0.13.3" # match with `k256` zeroize = "1.8" signature = "^2.2.0" getrandom = { version = "0.2", features = ["js"] } # getrandom_or_panic = "0.0.3" -rand = {version = "0.8"} +# rand = {version = "0.8"} anyhow = "1" -web-sys = { version = "0.3", features = ["CryptoKey", "SubtleCrypto", "Crypto", "EcKeyImportParams"] } -wasm-bindgen-futures = "0.4" - [dev-dependencies] wasm-bindgen-test = "~0.3.34" -[profile.release] +[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" diff --git a/javascript/src/lib.rs b/javascript/src/lib.rs index 63f4fa7..906a698 100644 --- a/javascript/src/lib.rs +++ b/javascript/src/lib.rs @@ -1,40 +1,33 @@ -//! Will this add module JSdoc? -// TODO add interop testing for JSON +//! TODO is it possible to add top-level? // TODO should I do examples rustdoc style or javadoc? /* I want to have a look at good and *small* example for... * JS tests and CI -* documenting -* setters/getters/exporting, general tricks, API, constructors */ +* documenting */ -mod utils; // TODO I guess this one isn't panicing, +use std::convert::TryFrom; +#[cfg(feature = "verify")] +use std::convert::TryInto; -// use std::convert::{TryFrom, TryInto}; - -use k256::pkcs8::DecodePrivateKey; -use k256::SecretKey; -use plume_rustcrypto::AffinePoint; use wasm_bindgen::prelude::*; +#[cfg(feature = "verify")] use elliptic_curve::sec1::FromEncodedPoint; use zeroize::Zeroize; -use elliptic_curve::rand_core::SeedableRng; use elliptic_curve::sec1::ToEncodedPoint; use signature::RandomizedSigner; -use wasm_bindgen_futures::JsFuture; - #[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 `BigInt`. +/// [`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, - pub pk: Vec, // TODO protect with public `web_sys::CryptoKey` + pub pk: Vec, pub nullifier: Vec, - pub c: web_sys::CryptoKey, - pub s: js_sys::BigInt, // it's overprotected in the crate, no need for `CryptoKey` here + pub c: Vec, + pub s: Vec, pub v1specific: Option, } @@ -55,25 +48,26 @@ impl PlumeSignatureV1Fields { #[wasm_bindgen] impl PlumeSignature { - // #[wasm_bindgen(setter)] - // pub fn set_message(&mut self, field: Vec) { - // self.message = field; - // } - + #[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 {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, pk: Vec, nullifier: Vec, - c: web_sys::CryptoKey, - s: js_sys::BigInt, + c: Vec, + s: Vec, v1specific: Option ) -> 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. */ - // TODO solve/test v1 field: start with adding its constructor + // 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)} } @@ -81,6 +75,12 @@ impl PlumeSignature { // js_sys::Object::from_entries(&values)? // values.get } + + /// Depending on your context you may want to zeroize from Wasm memory private parts of the result after getting the values. + pub fn zeroize_privateparts(&mut self) { + self.c.zeroize(); + self.pk.zeroize(); + } } #[wasm_bindgen(skip_jsdoc)] @@ -89,204 +89,77 @@ impl PlumeSignature { /// @param {Uint8Array} sk - must be exactly 32 bytes, and strictly represent a non-zero scalar of `secp256` in big-endian. /// @param {Uint8Array} msg /// @returns {PlumeSignature} -// 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 { -pub async fn sign(v1: bool, sk: web_sys::CryptoKey, msg: &[u8]) -> Result { - 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 how this is 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 - // js_sys::global().entries().find(); // TODO throw if no Crypto in global +pub fn sign(v1: bool, sk: &mut [u8], msg: &[u8]) -> Result { + let sk_z = + plume_rustcrypto::SecretKey + ::from(plume_rustcrypto::NonZeroScalar::try_from(sk.as_ref())?); + sk.zeroize(); + let signer = plume_rustcrypto::randomizedsigner::PlumeSigner::new(&sk_z, v1); - let global_the: Global = js_sys::global().unchecked_into(); // is it not `dyn_into` here for speed? https://github.com/rust-random/getrandom/commit/120a1d7f4796356a1f60cd84bd7a0ceafbac9d0e#commitcomment-143690574 - 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?; + Ok(signer.sign_with_rng( + &mut signature::rand_core::OsRng, + msg + ).into()) +} - let mut sk_z = - // plume_rustcrypto::SecretKey - // ::from(plume_rustcrypto::NonZeroScalar::try_from(sk.as_ref())?); - // ::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| JsError::new(er.as_string().expect("TODO check how this is failing").as_str()) - )?).await.map_err( - |er| JsError::new(er.as_string().expect("TODO check how this is failing").as_str()) - )?).to_vec()); - let sk = plume_rustcrypto::SecretKey::from_pkcs8_der(sk_z.as_ref())?; - sk_z.zeroize(); - let signer = plume_rustcrypto::randomizedsigner::PlumeSigner::new(&sk, v1); - // let seed_z: zeroize::Zeroizing<[u8; 32]> = zeroize::Zeroizing::new(seed.try_into()?); - // // TODO protect `seed` with ~~zeroization~~ and so on - // seed.zeroize(); +// TODO deprecate when `verify` gone +#[cfg(feature = "verify")] +impl TryInto for PlumeSignature { + type Error = JsError; - // 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) - // ), - // }; + fn try_into(self) -> Result { + let point_check = |point_bytes: Vec| -> Result { + 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"))} + }; - /* current implementation does bad job protecting few parts meant to be private, see - I feel it doesn't make sense to try mitigating that in the wrapper, but just to update this when the issue is solved */ - let sig_the = signer.sign_with_rng( - // &mut rand_chacha::ChaCha20Rng::from_seed(*seed_z), - &mut rand::rngs::OsRng, - msg - ); - // I had the trait for this, but due to `async` it seems more natural to just move that code here. - // impl From for PlumeSignature { - // fn from(value: plume_rustcrypto::PlumeSignature) -> Self { - let nonzeroscalartobigint = |v: plume_rustcrypto::NonZeroScalar| -> js_sys::BigInt { - js_sys::BigInt::new(&JsValue::from_str(("0x".to_owned() + v.to_string().as_str()).as_str())).expect("`BigInt` always can be created from hex string, and `v.to_string()` always produce that") - }; - - Ok(PlumeSignature { - message: msg.to_vec(), - pk: sig_the.pk.to_encoded_point(true).as_bytes().to_vec(), - nullifier: sig_the.nullifier.to_encoded_point(true).as_bytes().to_vec(), - c: JsFuture::from(subtle_the.import_key_with_object( - "pkcs8", - js_sys::Uint8Array::from(SecretKey::from(sig_the.c).to_sec1_der()?.as_ref()).as_ref(), - web_sys::EcKeyImportParams::new("ECDSA").named_curve("P-256"), - true, - js_sys::Array::new().as_ref(), // I can't see a valid usage for this among - ).map_err( - |er| JsError::new(er.as_string().expect("TODO check how this is failing").as_str()) - )?).await.map_err( - |er| JsError::new(er.as_string().expect("TODO check how this is failing").as_str()) - )?.dyn_into().map_err( - |er| JsError::new(er.as_string().expect("TODO check how this is failing").as_str()) - )?, - s: nonzeroscalartobigint(sig_the.s), - v1specific: if let Some(v1) = sig_the.v1specific {Some( - 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(), - } - )} else {None}, - }) - // } - // } - - // let debugging = signer.sign_with_rng( - // &mut rand_chacha::ChaCha20Rng::from_seed(*seed_z), msg - // ); - // js_sys::BigInt::new(&JsValue::from_str(("0x".to_owned() + debugging.c.to_string().as_str()).as_str())).expect("`BigInt` always can be created from decimal string, and `c.to_string()` always produce that"); - // // Err(JsError::new(debugging.c.to_string().as_str())) - // Ok( - // PlumeSignature{ message: Default::default(), pk: Default::default(), nullifier: Default::default(), c: Default::default(), s: Default::default(), v1specific: Default::default() } - // ) + 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}, + }) + } } -#[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; + +impl From 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(), + }}) + } + } } -// this was a `PlumeSignature` method, but thanks to `async` it been moved to standalone -/// @deprecated Use this only for testing purposes. -/// @throws a 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`. -#[wasm_bindgen] -pub async fn verify(self_: PlumeSignature) -> Result { - // #async #traits - // impl TryInto for PlumeSignature { - // type Error = JsError; - - // fn try_into(self) -> Result { - // if let Some(v1) = self.v1specific {Some( - // plume_rustcrypto::PlumeSignatureV1Fields{ - // r_point: v1.r_point.try_into()?, - // hashed_to_curve_r: v1.hashed_to_curve_r.try_into()?, - // } - // )} - - // let mut points = [self.pk, self.nullifier]; //, self.v1specific); - // if Some(points_v1) = self.v1specific {po} - - let point_check = |/* name_field: &str, */ point_bytes: Vec| -> Result { - 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(EXPECT_NONEALREADYCHECKED))} - }; - - // plume_rustcrypto::Scalar::try_from(self.c.to_string(16)?.as_string()?); - // k256::Scalar::from_repr(hex::decode(self.c.to_string(16)?.as_ref())?); // TODO test endianness here and correctness - let scalar_from_bigint = - |/* name_field: &str, */ n: js_sys::BigInt| -> Result { - 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))} - }; - const EXPECT_NONEALREADYCHECKED: &'static str = "`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() - /* "while proccessing ".to_owned().join(), - name_field, - " :", - er.to_string().as_str() - ].concat()) */)}; - - // zeroization protection ommitted here due to deprecation // - // 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().map_err( - |er| JsError::new(er.as_string().expect("TODO check how this is failing").as_str()) - )?).includes(&JsValue::from_str("P-256"), 0) {return Err(JsError::new("`c` must be from `secp256`"))} - // TODO finish - // this was my approach, but seems I got what they did at - // 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).map_err( - |er| JsError::new(er.as_string().expect("TODO check how this is failing").as_str()) - )?).await.map_err( - |er| JsError::new(er.as_string().expect("TODO check how this is failing").as_str()) - )?).to_vec(); - // ); - let c_scalar = &plume_rustcrypto::SecretKey::from_pkcs8_der(&c_pkcs)?.to_nonzero_scalar(); - // sk_z.zeroize(); - - 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: *c_scalar, - s: 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}, - }.verify()) - // } - // } +#[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 { + 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")) } \ No newline at end of file diff --git a/javascript/src/utils.rs b/javascript/src/utils.rs deleted file mode 100644 index b1d7929..0000000 --- a/javascript/src/utils.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub fn set_panic_hook() { - // When the `console_error_panic_hook` feature is enabled, we can call the - // `set_panic_hook` function at least once during initialization, and then - // we will get better error messages if our code ever panics. - // - // For more details see - // https://github.com/rustwasm/console_error_panic_hook#readme - #[cfg(feature = "console_error_panic_hook")] - console_error_panic_hook::set_once(); -}