From a1692ae1e6dcbf84cf79cf541d1c693e53557cb2 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sat, 30 Mar 2019 17:35:43 +0100 Subject: [PATCH] Combine all crypto into a single package (#15) * Combine all crypto into a single package * Disable build for daled & schnrorrkel (combined) * Bump @polkadot/dev (for publish) * yarn.lock * Fix CHANGELOG --- CHANGELOG.md | 6 + package.json | 2 +- packages/wasm-crypto/Cargo.lock | 102 +++++ packages/wasm-crypto/Cargo.toml | 2 + packages/wasm-crypto/src/bip39.rs | 127 +++++++ packages/wasm-crypto/src/ed25519.rs | 164 ++++++++ packages/wasm-crypto/src/hashing.rs | 244 ++++++++++++ packages/wasm-crypto/src/js/index.d.ts | 12 + packages/wasm-crypto/src/js/index.js | 44 ++- packages/wasm-crypto/src/lib.rs | 357 +----------------- packages/wasm-crypto/src/sr25519.rs | 238 ++++++++++++ packages/wasm-crypto/test/all.js | 132 ------- packages/wasm-crypto/test/all/bip39.js | 56 +++ packages/wasm-crypto/test/all/ed25519.js | 62 +++ packages/wasm-crypto/test/all/hashing.js | 70 ++++ packages/wasm-crypto/test/all/index.js | 30 ++ packages/wasm-crypto/test/all/sr25519.js | 126 +++++++ packages/wasm-crypto/test/jest.spec.js | 2 +- .../{package.json => nobuild.package.json} | 0 packages/wasm-dalek-ed25519/test/all.js | 2 +- packages/wasm-dalek-ed25519/test/asm.js | 2 +- packages/wasm-dalek-ed25519/test/jest.spec.js | 2 +- packages/wasm-dalek-ed25519/test/wasm.js | 2 +- .../{package.json => nobuild.package.json} | 0 yarn.lock | 8 +- 25 files changed, 1285 insertions(+), 507 deletions(-) create mode 100644 packages/wasm-crypto/src/bip39.rs create mode 100644 packages/wasm-crypto/src/ed25519.rs create mode 100644 packages/wasm-crypto/src/hashing.rs create mode 100644 packages/wasm-crypto/src/sr25519.rs delete mode 100644 packages/wasm-crypto/test/all.js create mode 100644 packages/wasm-crypto/test/all/bip39.js create mode 100644 packages/wasm-crypto/test/all/ed25519.js create mode 100644 packages/wasm-crypto/test/all/hashing.js create mode 100644 packages/wasm-crypto/test/all/index.js create mode 100644 packages/wasm-crypto/test/all/sr25519.js rename packages/wasm-dalek-ed25519/{package.json => nobuild.package.json} (100%) rename packages/wasm-schnorrkel/{package.json => nobuild.package.json} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb3d36b..5486727a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.8.1 + +- Pull ed25519 & sr25519 into wasm-crypto as well (smaller final size) +- Disable build/publish of `wasm-dalek-ed25519` & `wasm-schnorrkel` (single package) +- Improve error messaging, instead of `__wasm_malloc of null` + # 0.7.1 - Fix build scripts to properly attach errors in the log (dropped in 0.6.1) diff --git a/package.json b/package.json index 68a50eeb..dd19dc01 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@babel/core": "^7.4.0", - "@polkadot/dev": "^0.28.1", + "@polkadot/dev": "^0.29.0-beta.0", "@polkadot/util": "^0.76.0-beta.0" } } diff --git a/packages/wasm-crypto/Cargo.lock b/packages/wasm-crypto/Cargo.lock index 69bb5a53..2913ed70 100644 --- a/packages/wasm-crypto/Cargo.lock +++ b/packages/wasm-crypto/Cargo.lock @@ -93,6 +93,14 @@ name = "cfg-if" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "clear_on_drop" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -120,6 +128,18 @@ dependencies = [ "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "curve25519-dalek" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "digest" version = "0.8.0" @@ -128,6 +148,18 @@ dependencies = [ "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ed25519-dalek" +version = "1.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "curve25519-dalek 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "failure" version = "0.1.5" @@ -201,6 +233,11 @@ dependencies = [ "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" version = "1.3.0" @@ -232,6 +269,17 @@ name = "memory_units" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "merlin" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.13" @@ -250,6 +298,14 @@ name = "opaque-debug" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "packed_simd" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.7.1" @@ -316,6 +372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -427,6 +484,23 @@ dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "schnorrkel" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "curve25519-dalek 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ed25519-dalek 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "merlin 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sha3 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scopeguard" version = "0.3.3" @@ -456,6 +530,18 @@ dependencies = [ "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sha3" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "smallvec" version = "0.6.9" @@ -466,6 +552,11 @@ name = "subtle" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "subtle" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" version = "0.15.29" @@ -546,10 +637,12 @@ version = "0.0.0" dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ed25519-dalek 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)", "hex-literal 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "hmac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "schnorrkel 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-bip39 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -651,11 +744,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d" "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" +"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +"checksum curve25519-dalek 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e1f8a6fc0376eb52dc18af94915cc04dfdf8353746c0e8c550ae683a0815e5c1" "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" +"checksum ed25519-dalek 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81956bcf7ef761fb4e1d88de3fa181358a0d26cbcb9755b587a08f9119824b86" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" @@ -665,14 +761,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hex-literal 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ddc2928beef125e519d69ae1baa8c37ea2e0d3848545217f6db0179c5eb1d639" "checksum hex-literal-impl 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "520870c3213943eb8d7803e80180d12a6c7ceb4ae74602544529d1643dc4ddda" "checksum hmac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f127a908633569f208325f86f71255d3363c79721d7f9fe31cd5569908819771" +"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum memory_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +"checksum merlin 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "83c2dda19c01176e8e7148f7bdb88bbdf215a8db0641f89fc40e4b81736aeda5" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum once_cell 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "532c29a261168a45ce28948f9537ddd7a5dd272cc513b3017b1e82a88f962c37" "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" +"checksum packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220" "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" "checksum pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" @@ -693,12 +792,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum schnorrkel 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a700659388785588c75b197cecda0f23c7112a9281ef703e8ffc651061ce014c" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" +"checksum sha3 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "34a5e54083ce2b934bf059fdf38e7330a154177e029ab6c4e18638f2f624053a" "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" "checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" +"checksum subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "702662512f3ddeb74a64ce2fbbf3707ee1b6bb663d28bb054e0779bbc720d926" "checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum tiny-bip39 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f5388a470627f97a01a6e13389ced797a42b1611f9de7e0f6ca705675ac55297" diff --git a/packages/wasm-crypto/Cargo.toml b/packages/wasm-crypto/Cargo.toml index 21d821e2..2933f06b 100644 --- a/packages/wasm-crypto/Cargo.toml +++ b/packages/wasm-crypto/Cargo.toml @@ -17,9 +17,11 @@ crate-type = ["cdylib", "rlib"] [dependencies] blake2-rfc = "0.2.18" byteorder = "1.3.1" +ed25519-dalek = { version = "1.0.0-pre.1", features = ["nightly"] } hmac = "0.7.0" # libsecp256k1 = "0.2.2" pbkdf2 = { version = "0.3.0", default-features = false } +schnorrkel = { version = "0.1.0", features = ["nightly"] } sha2 = "0.8.0" tiny-bip39 = { version = "0.6.1", default-features = false } tiny-keccak = "1.4.2" diff --git a/packages/wasm-crypto/src/bip39.rs b/packages/wasm-crypto/src/bip39.rs new file mode 100644 index 00000000..fb3d98a2 --- /dev/null +++ b/packages/wasm-crypto/src/bip39.rs @@ -0,0 +1,127 @@ +// Copyright 2019 @polkadot/wasm-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +use bip39::{Mnemonic, MnemonicType, Language, Seed}; +use hmac::Hmac; +use pbkdf2::pbkdf2; +use sha2::Sha512; +use wasm_bindgen::prelude::*; + +/// Generate a bip39 phrase +/// +/// words: number of words, either 12, 15, 18 21 or 24 +/// +/// Returns the bip 39 phrase +#[wasm_bindgen] +pub fn ext_bip39_generate(words: u32) -> String { + Mnemonic::new(MnemonicType::for_word_count(words as usize).unwrap(), Language::English) + .into_phrase() +} + +/// Create entropy from a bip39 phrase +/// +/// * phrase: mnemonic phrase +/// +/// Returns the entropy +#[wasm_bindgen] +pub fn ext_bip39_to_entropy(phrase: &str) -> Vec { + Mnemonic::from_phrase(phrase, Language::English) + .unwrap() + .entropy() + .to_vec() +} + +/// Create a mini-secret from a bip39 phrase +/// +/// * phrase: mnemonic phrase +/// +/// Returns the 32-byte mini-secret via entropy +#[wasm_bindgen] +pub fn ext_bip39_to_mini_secret(phrase: &str, password: &str) -> Vec { + let salt = format!("mnemonic{}", password); + let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap(); + let mut result = [0u8; 64]; + + pbkdf2::>(mnemonic.entropy(), salt.as_bytes(), 2048, &mut result); + + result[..32].to_vec() +} + +/// Creates a see from a bip-39 phrase +/// +/// @phrase: mnemonic phrase +/// +/// Returns a 32-byte seed +#[wasm_bindgen] +pub fn ext_bip39_to_seed(phrase: &str, password: &str) -> Vec { + let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap(); + + Seed::new(&mnemonic, password) + .as_bytes()[..32] + .to_vec() +} + +/// Validates a bip39 phrase +/// +/// * phrase: mnemonic phrase +/// +/// Returns the true/false +#[wasm_bindgen] +pub fn ext_bip39_validate(phrase: &str) -> bool { + match Mnemonic::validate(phrase, Language::English) { + Err(_) => false, + _ => true + } +} + +#[cfg(test)] +pub mod tests { + use hex_literal::{hex, hex_impl}; + use super::*; + + #[test] + fn can_bip39_entropy() { + let phrase = "legal winner thank year wave sausage worth useful legal winner thank yellow"; + let entropy = hex!("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"); + let result = ext_bip39_to_entropy(phrase); + + assert_eq!(result, entropy); + } + + #[test] + fn can_bip39_mini_secret() { + let phrase = "legal winner thank year wave sausage worth useful legal winner thank yellow"; + let password = "Substrate"; + let mini = hex!("4313249608fe8ac10fd5886c92c4579007272cb77c21551ee5b8d60b78041685"); + let result = ext_bip39_to_mini_secret(phrase, password); + + assert_eq!(result[..], mini[..]); + } + + #[test] + fn can_bip39_seed() { + let phrase = "seed sock milk update focus rotate barely fade car face mechanic mercy"; + let seed = hex!("3c121e20de068083b49c2315697fb59a2d9e8643c24e5ea7628132c58969a027"); + let result = ext_bip39_to_seed(phrase, ""); + + assert_eq!(result[..], seed[..]); + } + + #[test] + fn can_bip39_generate() { + let phrase = ext_bip39_generate(12); + let is_valid = ext_bip39_validate(&phrase); + + assert!(is_valid); + } + + #[test] + fn can_bip39_validate() { + let is_valid = ext_bip39_validate("seed sock milk update focus rotate barely fade car face mechanic mercy"); + let is_invalid = ext_bip39_validate("wine photo extra cushion basket dwarf humor cloud truck job boat submit"); + + assert_eq!(is_valid, true); + assert_eq!(is_invalid, false); + } +} diff --git a/packages/wasm-crypto/src/ed25519.rs b/packages/wasm-crypto/src/ed25519.rs new file mode 100644 index 00000000..320053ee --- /dev/null +++ b/packages/wasm-crypto/src/ed25519.rs @@ -0,0 +1,164 @@ +// Copyright 2019 @polkadot/wasm-dalek-ed25519 authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signature}; +use wasm_bindgen::prelude::*; + +/// Keypair helper function. +fn create_from_pair(pair: &[u8]) -> Keypair { + match Keypair::from_bytes(pair) { + Ok(pair) => return pair, + Err(_) => panic!("Provided pair is invalid.") + } +} + +/// Keypair helper function +fn create_from_parts(public: &[u8], secret: &[u8]) -> Keypair { + let mut pair = vec![]; + + pair.extend_from_slice(secret); + pair.extend_from_slice(public); + + create_from_pair(&pair) +} + +/// Keypair helper function. +fn create_from_seed(seed: &[u8]) -> Keypair { + let secret = SecretKey::from_bytes(seed).unwrap(); + let public: PublicKey = (&secret).into(); + + create_from_parts(public.as_bytes(), seed) +} + +/// PublicKey helper +fn create_public(public: &[u8]) -> PublicKey { + match PublicKey::from_bytes(public) { + Ok(public) => return public, + Err(_) => panic!("Provided public key is invalid.") + } +} + +/// Generate a key pair. +/// +/// * seed: UIntArray with 32 element +/// +/// returned vector is the concatenation of first the private key (64 bytes) +/// followed by the public key (32) bytes. +#[wasm_bindgen] +pub fn ext_ed_from_seed(seed: &[u8]) -> Vec { + create_from_seed(seed) + .to_bytes() + .to_vec() +} + +/// Sign a message +/// +/// The combination of both public and private key must be provided. +/// This is effectively equivalent to a keypair. +/// +/// * public: UIntArray with 32 element +/// * private: UIntArray with 64 element +/// * message: Arbitrary length UIntArray +/// +/// * returned vector is the signature consisting of 64 bytes. +#[wasm_bindgen] +pub fn ext_ed_sign(public: &[u8], secret: &[u8], message: &[u8]) -> Vec { + create_from_parts(public, secret) + .sign(message) + .to_bytes() + .to_vec() +} + +/// Verify a message and its corresponding against a public key; +/// +/// * signature: UIntArray with 64 element +/// * message: Arbitrary length UIntArray +/// * pubkey: UIntArray with 32 element +#[wasm_bindgen] +pub fn ext_ed_verify(signature: &[u8], message: &[u8], public: &[u8]) -> bool { + let signature = match Signature::from_bytes(signature) { + Ok(signature) => signature, + Err(_) => return false + }; + + create_public(public) + .verify(message, &signature) + .is_ok() +} + +#[cfg(test)] +pub mod tests { + extern crate rand; + + use hex_literal::{hex, hex_impl}; + use super::*; + use ed25519_dalek::{SIGNATURE_LENGTH, KEYPAIR_LENGTH, SECRET_KEY_LENGTH}; + + fn generate_random_seed() -> Vec { + (0..32).map(|_| rand::random::() ).collect() + } + + #[test] + fn can_create_keypair() { + let seed = generate_random_seed(); + let keypair = ext_ed_from_seed(seed.as_slice()); + + assert!(keypair.len() == KEYPAIR_LENGTH); + } + + #[test] + fn creates_pair_from_known() { + let seed = b"12345678901234567890123456789012"; + let expected = hex!("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee"); + let keypair = ext_ed_from_seed(seed); + let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + + assert_eq!(public, expected); + } + + #[test] + fn can_sign_message() { + let seed = generate_random_seed(); + let keypair = ext_ed_from_seed(seed.as_slice()); + let private = &keypair[0..SECRET_KEY_LENGTH]; + let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + let message = b"this is a message"; + let signature = ext_ed_sign(public, private, message); + + assert!(signature.len() == SIGNATURE_LENGTH); + } + + #[test] + fn can_verify_message() { + let seed = generate_random_seed(); + let keypair = ext_ed_from_seed(seed.as_slice()); + let private = &keypair[0..SECRET_KEY_LENGTH]; + let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + let message = b"this is a message"; + let signature = ext_ed_sign(public, private, message); + let is_valid = ext_ed_verify(&signature[..], message, public); + + assert!(is_valid); + } + + #[test] + fn can_verify_known() { + let public = hex!("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee"); + let message = b"this is a message"; + let signature = hex!("90588f3f512496f2dd40571d162e8182860081c74e2085316e7c4396918f07da412ee029978e4dd714057fe973bd9e7d645148bf7b66680d67c93227cde95202"); + let is_valid = ext_ed_verify(&signature, message, &public); + + assert!(is_valid); + } + + #[test] + fn can_verify_known_wrong() { + let public = hex!("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee"); + let message = b"this is a message"; + let signature = &[0u8; 64]; + let is_valid = ext_ed_verify(signature, message, &public); + + assert_eq!(is_valid, false); + } +} diff --git a/packages/wasm-crypto/src/hashing.rs b/packages/wasm-crypto/src/hashing.rs new file mode 100644 index 00000000..08138032 --- /dev/null +++ b/packages/wasm-crypto/src/hashing.rs @@ -0,0 +1,244 @@ +// Copyright 2019 @polkadot/wasm-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +use blake2_rfc::blake2b::blake2b; +use byteorder::{ByteOrder, LittleEndian}; +use hmac::Hmac; +use pbkdf2::pbkdf2; +use sha2::{Digest, Sha512}; +// use secp256k1; +use std::hash::Hasher; +use tiny_keccak::keccak256; +use twox_hash::XxHash; +use wasm_bindgen::prelude::*; + +/// helper function for a single twox round with a seed +fn create_twox(data: &[u8], seed: u64) -> [u8; 8] { + let mut hasher = XxHash::with_seed(seed); + + hasher.write(data); + + let hash64 = hasher.finish(); + let mut result = [0u8; 8]; + + LittleEndian::write_u64(&mut result, hash64); + + result +} + +/// blake2b hash for the specified input +/// +/// * data: Arbitrary data to be hashed +/// * key: Key to add to the hashing (normally empty) +/// * size: Size in bytes of the resulting output +/// +/// Returns a vector with the hash result +#[wasm_bindgen] +pub fn ext_blake2b(data: &[u8], key: &[u8], size: u32) -> Vec { + // we cast to usize here - due to the WASM, we'd rather have u32 inputs + blake2b(size as usize, key, data) + .as_bytes() + .to_vec() +} + +/// Create a keccak256 hash for the specified input +/// +// * data: Arbitrary data to be hashed +/// +/// Returns the hash as a vector +#[wasm_bindgen] +pub fn ext_keccak256(data: &[u8]) -> Vec { + keccak256(data) + .to_vec() +} + +/// pbkdf2 hash from an input, salt for the number of specified rounds +/// +/// * data: Arbitrary data to be hashed +/// * salt: Salt for this hash +/// * rounds: The number of rounds to perform +/// +/// Returns a vecor with the hash result +#[wasm_bindgen] +pub fn ext_pbkdf2(data: &[u8], salt: &[u8], rounds: u32) -> Vec { + let mut result = [0u8; 64]; + + // we cast to usize here - due to the WASM, we'd rather have u32 inputs + pbkdf2::>(data, salt, rounds as usize, &mut result); + + result.to_vec() +} + +// /// Checks for the recoverability of a signature from message +// /// +// /// * message: The message +// /// * signature: The signature +// /// +// /// Returns 0 on success, or a specific error code +// #[wasm_bindgen] +// pub fn ext_secp256k1_is_recoverable(message: &[u8], signature: &[u8]) -> u32 { +// let rs = match secp256k1::Signature::parse_slice(&signature[0..64]) { +// Ok(rs) => rs, +// _ => return 1 +// }; +// let v = match secp256k1::RecoveryId::parse(if signature[64] > 26 { signature[64] - 27 } else { signature[64] } as u8) { +// Ok(v) => v, +// _ => return 2 +// }; +// let mut msg = [0u8; 32]; + +// msg.copy_from_slice(&message); + +// match secp256k1::recover(&secp256k1::Message::parse(&msg), &rs, &v) { +// Ok(_) => 0, +// _ => 3 +// } +// } + +// /// Recovers the secp256k1 signature from the input +// /// +// /// * message: The message +// /// * signature: The signature +// /// +// /// Returns a vector with the recovered public key +// #[wasm_bindgen] +// pub fn ext_secp256k1_recover(message: &[u8], signature: &[u8]) -> Vec { +// let rs = match secp256k1::Signature::parse_slice(&signature[0..64]) { +// Ok(rs) => rs, +// _ => panic!("Unable to recover r,s") +// }; +// let v = match secp256k1::RecoveryId::parse(if signature[64] > 26 { signature[64] - 27 } else { signature[64] } as u8) { +// Ok(v) => v, +// _ => panic!("Unable to recover v") +// }; +// let mut msg = [0u8; 32]; + +// msg.copy_from_slice(&message); + +// match secp256k1::recover(&secp256k1::Message::parse(&msg), &rs, &v) { +// Ok(pk) => return pk.serialize()[1..65].to_vec(), +// _ => panic!("Unable to recover publickey") +// } +// } + +/// sha512 hash for the specified input +/// +/// * data: Arbitrary data to be hashed +/// +/// Returns a vecor with the hash result +#[wasm_bindgen] +pub fn ext_sha512(data: &[u8]) -> Vec { + let mut hasher = Sha512::new(); + + hasher.input(data); + + hasher + .result() + .to_vec() +} + +/// twox hash for the specified input and rounds +/// +/// * data: Arbitrary data to be hashed +/// * rounds: Number of 8-byte rounds to add to the output +/// +/// Returns a vecor with the hash result +#[wasm_bindgen] +pub fn ext_twox(data: &[u8], rounds: u32) -> Vec { + let mut vec = vec![]; + + for round in 0..rounds { + // we cast to u64 here - due to the WASM, we'd rather have u32 inputs + vec.extend_from_slice(&create_twox(data, round as u64)); + } + + vec +} + +#[cfg(test)] +pub mod tests { + use hex_literal::{hex, hex_impl}; + use super::*; + + // // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign. + // fn ethereum_signable_message(data: &[u8]) -> Vec { + // let prefix = b"Pay DOTs to the Polkadot account:"; + // let mut l = prefix.len() + data.len(); + // let mut rev = Vec::new(); + // let mut v = b"\x19Ethereum Signed Message:\n".to_vec(); + + // while l > 0 { + // rev.push(b'0' + (l % 10) as u8); + // l /= 10; + // } + + // v.extend(rev.into_iter().rev()); + // v.extend_from_slice(&prefix[..]); + // v.extend_from_slice(data); + // v + // } + + #[test] + fn can_blake2b() { + let data = b"abc"; + let expected_32 = hex!("bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319"); + let expected_64 = hex!("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"); + let hash_32 = ext_blake2b(data, &[], 32); + let hash_64 = ext_blake2b(data, &[], 64); + + assert_eq!(hash_32[..], expected_32[..]); + assert_eq!(hash_64[..], expected_64[..]); + } + + #[test] + fn can_keccak256() { + let data = b"test value"; + let expected = hex!("2d07364b5c231c56ce63d49430e085ea3033c750688ba532b24029124c26ca5e"); + let hash = ext_keccak256(data); + + assert_eq!(hash[..], expected[..]); + } + + #[test] + fn can_pbkdf2() { + let salt = b"this is a salt"; + let data = b"hello world"; + let expected = hex!("5fcbe04f05300a3ecc5c35d18ea0b78f3f6853d2ae5f3fca374f69a7d1f78b5def5c60dae1a568026c7492511e0c53521e8bb6e03a650e1263265fee92722270"); + let hash = ext_pbkdf2(data, salt, 2048); + + assert_eq!(hash[..], expected[..]); + } + + // #[test] + // fn can_secp256k1_real_eth_sig() { + // let sig = hex!("7505f2880114da51b3f5d535f8687953c0ab9af4ab81e592eaebebf53b728d2b6dfd9b5bcd70fee412b1f31360e7c2774009305cb84fc50c1d0ff8034dfa5fff1c"); + // let pk = hex!("DF67EC7EAe23D2459694685257b6FC59d1BAA1FE"); + // let data = [42, 0, 0, 0, 0, 0, 0, 0]; + // let msg = ext_keccak256(ðereum_signable_message(&data)); + // let result = ext_keccak256(&ext_secp256k1_recover(&msg[..], &sig[..])); + + // assert_eq!(result[12..], pk[..]); + // } + + #[test] + fn can_sha512() { + let data = b"hello world"; + let expected = hex!("309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f"); + let hash = ext_sha512(data); + + assert_eq!(hash[..], expected[..]); + } + + #[test] + fn can_twox() { + let data = b"abc"; + let expected_64 = hex!("990977adf52cbc44"); + let expected_256 = hex!("990977adf52cbc440889329981caa9bef7da5770b2b8a05303b75d95360dd62b"); + let hash_64 = ext_twox(data, 1); + let hash_256 = ext_twox(data, 4); + + assert_eq!(hash_64[..], expected_64[..]); + assert_eq!(hash_256[..], expected_256[..]); + } +} diff --git a/packages/wasm-crypto/src/js/index.d.ts b/packages/wasm-crypto/src/js/index.d.ts index 838f1797..321dbc13 100644 --- a/packages/wasm-crypto/src/js/index.d.ts +++ b/packages/wasm-crypto/src/js/index.d.ts @@ -7,6 +7,18 @@ export function bip39ToEntropy (phrase: string): Uint8Array; export function bip39ToMiniSecret (phrase: string, password: string): Uint8Array; export function bip39ToSeed (phrase: string, password: string): Uint8Array; export function bip39Validate (phrase: string): boolean; + +export function ed25519KeypairFromSeed (seed: Uint8Array): Uint8Array; +export function ed25519Sign (publicKey: Uint8Array, secretKey: Uint8Array, message: Uint8Array): Uint8Array; +export function ed25519Verify (signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): boolean; + +export function sr25519DeriveKeypairHard (pair: Uint8Array, chainCode: Uint8Array): Uint8Array; +export function sr25519DeriveKeypairSoft (pair: Uint8Array, chainCode: Uint8Array): Uint8Array; +export function sr25519DerivePublicSoft (publicKey: Uint8Array, chainCode: Uint8Array): Uint8Array; +export function sr25519KeypairFromSeed (seed: Uint8Array): Uint8Array; +export function sr25519Sign (publicKey: Uint8Array, secretKey: Uint8Array, message: Uint8Array): Uint8Array; +export function sr25519Verify (signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): boolean; + export function blake2b (data: Uint8Array, key: Uint8Array, byteSize: number): Uint8Array; export function keccak256 (data: Uint8Array): Uint8Array; export function pbkdf2 (data: Uint8Array, salt: Uint8Array, rounds: number): Uint8Array; diff --git a/packages/wasm-crypto/src/js/index.js b/packages/wasm-crypto/src/js/index.js index 5610af44..6bddc5c0 100644 --- a/packages/wasm-crypto/src/js/index.js +++ b/packages/wasm-crypto/src/js/index.js @@ -2,20 +2,40 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +const { assert } = require('@polkadot/util'); const stubbed = require('./wasm'); -module.exports.bip39Generate = stubbed.ext_bip39_generate; -module.exports.bip39ToEntropy = stubbed.ext_bip39_to_entropy; -module.exports.bip39ToMiniSecret = stubbed.ext_bip39_to_mini_secret; -module.exports.bip39ToSeed = stubbed.ext_bip39_to_seed; -module.exports.bip39Validate = stubbed.ext_bip39_validate -module.exports.blake2b = stubbed.ext_blake2b; -module.exports.keccak256 = stubbed.ext_keccak256; -module.exports.pbkdf2 = stubbed.ext_pbkdf2; -// module.exports.secp256k1IsRecoverable = stubbed.ext_secp256k1_is_recoverable; -// module.exports.secp256k1Recover = stubbed.ext_secp256k1_recover; -module.exports.sha512 = stubbed.ext_sha512; -module.exports.twox = stubbed.ext_twox; +const wrapReady = (fn) => + (...params) => { + assert(stubbed.isReady(), 'ERROR: @polkadot/wasm-crypto has not been initialized'); + + return fn(...params); + }; + +module.exports.bip39Generate = wrapReady(stubbed.ext_bip39_generate); +module.exports.bip39ToEntropy = wrapReady(stubbed.ext_bip39_to_entropy); +module.exports.bip39ToMiniSecret = wrapReady(stubbed.ext_bip39_to_mini_secret); +module.exports.bip39ToSeed = wrapReady(stubbed.ext_bip39_to_seed); +module.exports.bip39Validate = wrapReady(stubbed.ext_bip39_validate); + +module.exports.ed25519KeypairFromSeed = wrapReady(stubbed.ext_ed_from_seed); +module.exports.ed25519Sign = wrapReady(stubbed.ext_ed_sign); +module.exports.ed25519Verify = wrapReady(stubbed.ext_ed_verify); + +module.exports.sr25519DeriveKeypairHard = wrapReady(stubbed.ext_sr_derive_keypair_hard); +module.exports.sr25519DeriveKeypairSoft = wrapReady(stubbed.ext_sr_derive_keypair_soft); +module.exports.sr25519DerivePublicSoft = wrapReady(stubbed.ext_sr_derive_public_soft); +module.exports.sr25519KeypairFromSeed = wrapReady(stubbed.ext_sr_from_seed); +module.exports.sr25519Sign = wrapReady(stubbed.ext_sr_sign); +module.exports.sr25519Verify = wrapReady(stubbed.ext_sr_verify); + +module.exports.blake2b = wrapReady(stubbed.ext_blake2b); +module.exports.keccak256 = wrapReady(stubbed.ext_keccak256); +module.exports.pbkdf2 = wrapReady(stubbed.ext_pbkdf2); +// module.exports.secp256k1IsRecoverable = wrapReady(stubbed.ext_secp256k1_is_recoverable); +// module.exports.secp256k1Recover = wrapReady(stubbed.ext_secp256k1_recover); +module.exports.sha512 = wrapReady(stubbed.ext_sha512); +module.exports.twox = wrapReady(stubbed.ext_twox); module.exports.isReady = stubbed.isReady; module.exports.waitReady = stubbed.waitReady; diff --git a/packages/wasm-crypto/src/lib.rs b/packages/wasm-crypto/src/lib.rs index 9400b0f4..9f02cf3f 100644 --- a/packages/wasm-crypto/src/lib.rs +++ b/packages/wasm-crypto/src/lib.rs @@ -2,360 +2,11 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -use bip39::{Mnemonic, MnemonicType, Language, Seed}; -use blake2_rfc::blake2b::blake2b; -use byteorder::{ByteOrder, LittleEndian}; -use hmac::Hmac; -use pbkdf2::pbkdf2; -// use secp256k1; -use sha2::{Digest, Sha512}; -use std::hash::Hasher; -use tiny_keccak::keccak256; -use twox_hash::XxHash; -use wasm_bindgen::prelude::*; - // Use `wee_alloc` as the global allocator. #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; -/// helper function for a single twox round with a seed -fn create_twox(data: &[u8], seed: u64) -> [u8; 8] { - let mut hasher = XxHash::with_seed(seed); - - hasher.write(data); - - let hash64 = hasher.finish(); - let mut result = [0u8; 8]; - - LittleEndian::write_u64(&mut result, hash64); - - result -} - -/// Generate a bip39 phrase -/// -/// words: number of words, either 12, 15, 18 21 or 24 -/// -/// Returns the bip 39 phrase -#[wasm_bindgen] -pub fn ext_bip39_generate(words: u32) -> String { - Mnemonic::new(MnemonicType::for_word_count(words as usize).unwrap(), Language::English) - .into_phrase() -} - -/// Create entropy from a bip39 phrase -/// -/// * phrase: mnemonic phrase -/// -/// Returns the entropy -#[wasm_bindgen] -pub fn ext_bip39_to_entropy(phrase: &str) -> Vec { - Mnemonic::from_phrase(phrase, Language::English) - .unwrap() - .entropy() - .to_vec() -} - -/// Create a mini-secret from a bip39 phrase -/// -/// * phrase: mnemonic phrase -/// -/// Returns the 32-byte mini-secret via entropy -#[wasm_bindgen] -pub fn ext_bip39_to_mini_secret(phrase: &str, password: &str) -> Vec { - let salt = format!("mnemonic{}", password); - let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap(); - let mut result = [0u8; 64]; - - pbkdf2::>(mnemonic.entropy(), salt.as_bytes(), 2048, &mut result); - - result[..32].to_vec() -} - -/// Creates a see from a bip-39 phrase -/// -/// @phrase: mnemonic phrase -/// -/// Returns a 32-byte seed -#[wasm_bindgen] -pub fn ext_bip39_to_seed(phrase: &str, password: &str) -> Vec { - let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap(); - - Seed::new(&mnemonic, password) - .as_bytes()[..32] - .to_vec() -} - -/// Validates a bip39 phrase -/// -/// * phrase: mnemonic phrase -/// -/// Returns the true/false -#[wasm_bindgen] -pub fn ext_bip39_validate(phrase: &str) -> bool { - match Mnemonic::validate(phrase, Language::English) { - Err(_) => false, - _ => true - } -} - -/// blake2b hash for the specified input -/// -/// * data: Arbitrary data to be hashed -/// * key: Key to add to the hashing (normally empty) -/// * size: Size in bytes of the resulting output -/// -/// Returns a vector with the hash result -#[wasm_bindgen] -pub fn ext_blake2b(data: &[u8], key: &[u8], size: u32) -> Vec { - // we cast to usize here - due to the WASM, we'd rather have u32 inputs - blake2b(size as usize, key, data) - .as_bytes() - .to_vec() -} - -/// Create a keccak256 hash for the specified input -/// -// * data: Arbitrary data to be hashed -/// -/// Returns the hash as a vector -#[wasm_bindgen] -pub fn ext_keccak256(data: &[u8]) -> Vec { - keccak256(data) - .to_vec() -} - -/// pbkdf2 hash from an input, salt for the number of specified rounds -/// -/// * data: Arbitrary data to be hashed -/// * salt: Salt for this hash -/// * rounds: The number of rounds to perform -/// -/// Returns a vecor with the hash result -#[wasm_bindgen] -pub fn ext_pbkdf2(data: &[u8], salt: &[u8], rounds: u32) -> Vec { - let mut result = [0u8; 64]; - - // we cast to usize here - due to the WASM, we'd rather have u32 inputs - pbkdf2::>(data, salt, rounds as usize, &mut result); - - result.to_vec() -} - -// /// Checks for the recoverability of a signature from message -// /// -// /// * message: The message -// /// * signature: The signature -// /// -// /// Returns 0 on success, or a specific error code -// #[wasm_bindgen] -// pub fn ext_secp256k1_is_recoverable(message: &[u8], signature: &[u8]) -> u32 { -// let rs = match secp256k1::Signature::parse_slice(&signature[0..64]) { -// Ok(rs) => rs, -// _ => return 1 -// }; -// let v = match secp256k1::RecoveryId::parse(if signature[64] > 26 { signature[64] - 27 } else { signature[64] } as u8) { -// Ok(v) => v, -// _ => return 2 -// }; -// let mut msg = [0u8; 32]; - -// msg.copy_from_slice(&message); - -// match secp256k1::recover(&secp256k1::Message::parse(&msg), &rs, &v) { -// Ok(_) => 0, -// _ => 3 -// } -// } - -// /// Recovers the secp256k1 signature from the input -// /// -// /// * message: The message -// /// * signature: The signature -// /// -// /// Returns a vector with the recovered public key -// #[wasm_bindgen] -// pub fn ext_secp256k1_recover(message: &[u8], signature: &[u8]) -> Vec { -// let rs = match secp256k1::Signature::parse_slice(&signature[0..64]) { -// Ok(rs) => rs, -// _ => panic!("Unable to recover r,s") -// }; -// let v = match secp256k1::RecoveryId::parse(if signature[64] > 26 { signature[64] - 27 } else { signature[64] } as u8) { -// Ok(v) => v, -// _ => panic!("Unable to recover v") -// }; -// let mut msg = [0u8; 32]; - -// msg.copy_from_slice(&message); - -// match secp256k1::recover(&secp256k1::Message::parse(&msg), &rs, &v) { -// Ok(pk) => return pk.serialize()[1..65].to_vec(), -// _ => panic!("Unable to recover publickey") -// } -// } - -/// sha512 hash for the specified input -/// -/// * data: Arbitrary data to be hashed -/// -/// Returns a vecor with the hash result -#[wasm_bindgen] -pub fn ext_sha512(data: &[u8]) -> Vec { - let mut hasher = Sha512::new(); - - hasher.input(data); - - hasher - .result() - .to_vec() -} - -/// twox hash for the specified input and rounds -/// -/// * data: Arbitrary data to be hashed -/// * rounds: Number of 8-byte rounds to add to the output -/// -/// Returns a vecor with the hash result -#[wasm_bindgen] -pub fn ext_twox(data: &[u8], rounds: u32) -> Vec { - let mut vec = vec![]; - - for round in 0..rounds { - // we cast to u64 here - due to the WASM, we'd rather have u32 inputs - vec.extend_from_slice(&create_twox(data, round as u64)); - } - - vec -} - -#[cfg(test)] -pub mod tests { - use hex_literal::{hex, hex_impl}; - use super::*; - - // // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign. - // fn ethereum_signable_message(data: &[u8]) -> Vec { - // let prefix = b"Pay DOTs to the Polkadot account:"; - // let mut l = prefix.len() + data.len(); - // let mut rev = Vec::new(); - // let mut v = b"\x19Ethereum Signed Message:\n".to_vec(); - - // while l > 0 { - // rev.push(b'0' + (l % 10) as u8); - // l /= 10; - // } - - // v.extend(rev.into_iter().rev()); - // v.extend_from_slice(&prefix[..]); - // v.extend_from_slice(data); - // v - // } - - #[test] - fn can_bip39_entropy() { - let phrase = "legal winner thank year wave sausage worth useful legal winner thank yellow"; - let entropy = hex!("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"); - let result = ext_bip39_to_entropy(phrase); - - assert_eq!(result, entropy); - } - - #[test] - fn can_bip39_mini_secret() { - let phrase = "legal winner thank year wave sausage worth useful legal winner thank yellow"; - let password = "Substrate"; - let mini = hex!("4313249608fe8ac10fd5886c92c4579007272cb77c21551ee5b8d60b78041685"); - let result = ext_bip39_to_mini_secret(phrase, password); - - assert_eq!(result[..], mini[..]); - } - - #[test] - fn can_bip39_seed() { - let phrase = "seed sock milk update focus rotate barely fade car face mechanic mercy"; - let seed = hex!("3c121e20de068083b49c2315697fb59a2d9e8643c24e5ea7628132c58969a027"); - let result = ext_bip39_to_seed(phrase, ""); - - assert_eq!(result[..], seed[..]); - } - - #[test] - fn can_bip39_generate() { - let phrase = ext_bip39_generate(12); - let is_valid = ext_bip39_validate(&phrase); - - assert!(is_valid); - } - - #[test] - fn can_bip39_validate() { - let is_valid = ext_bip39_validate("seed sock milk update focus rotate barely fade car face mechanic mercy"); - let is_invalid = ext_bip39_validate("wine photo extra cushion basket dwarf humor cloud truck job boat submit"); - - assert_eq!(is_valid, true); - assert_eq!(is_invalid, false); - } - - #[test] - fn can_blake2b() { - let data = b"abc"; - let expected_32 = hex!("bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319"); - let expected_64 = hex!("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"); - let hash_32 = ext_blake2b(data, &[], 32); - let hash_64 = ext_blake2b(data, &[], 64); - - assert_eq!(hash_32[..], expected_32[..]); - assert_eq!(hash_64[..], expected_64[..]); - } - - #[test] - fn can_keccak256() { - let data = b"test value"; - let expected = hex!("2d07364b5c231c56ce63d49430e085ea3033c750688ba532b24029124c26ca5e"); - let hash = ext_keccak256(data); - - assert_eq!(hash[..], expected[..]); - } - - #[test] - fn can_pbkdf2() { - let salt = b"this is a salt"; - let data = b"hello world"; - let expected = hex!("5fcbe04f05300a3ecc5c35d18ea0b78f3f6853d2ae5f3fca374f69a7d1f78b5def5c60dae1a568026c7492511e0c53521e8bb6e03a650e1263265fee92722270"); - let hash = ext_pbkdf2(data, salt, 2048); - - assert_eq!(hash[..], expected[..]); - } - - // #[test] - // fn can_secp256k1_real_eth_sig() { - // let sig = hex!("7505f2880114da51b3f5d535f8687953c0ab9af4ab81e592eaebebf53b728d2b6dfd9b5bcd70fee412b1f31360e7c2774009305cb84fc50c1d0ff8034dfa5fff1c"); - // let pk = hex!("DF67EC7EAe23D2459694685257b6FC59d1BAA1FE"); - // let data = [42, 0, 0, 0, 0, 0, 0, 0]; - // let msg = ext_keccak256(ðereum_signable_message(&data)); - // let result = ext_keccak256(&ext_secp256k1_recover(&msg[..], &sig[..])); - - // assert_eq!(result[12..], pk[..]); - // } - - #[test] - fn can_sha512() { - let data = b"hello world"; - let expected = hex!("309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f"); - let hash = ext_sha512(data); - - assert_eq!(hash[..], expected[..]); - } - - #[test] - fn can_twox() { - let data = b"abc"; - let expected_64 = hex!("990977adf52cbc44"); - let expected_256 = hex!("990977adf52cbc440889329981caa9bef7da5770b2b8a05303b75d95360dd62b"); - let hash_64 = ext_twox(data, 1); - let hash_256 = ext_twox(data, 4); - - assert_eq!(hash_64[..], expected_64[..]); - assert_eq!(hash_256[..], expected_256[..]); - } -} +pub mod bip39; +pub mod ed25519; +pub mod hashing; +pub mod sr25519; diff --git a/packages/wasm-crypto/src/sr25519.rs b/packages/wasm-crypto/src/sr25519.rs new file mode 100644 index 00000000..5359d2f1 --- /dev/null +++ b/packages/wasm-crypto/src/sr25519.rs @@ -0,0 +1,238 @@ +// Copyright 2019 Paritytech via https://github.com/paritytech/schnorrkel-js/ +// Copyright 2019 @polkadot/wasm-schnorrkel authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +// Originally developed (as a fork) in https://github.com/polkadot-js/schnorrkel-js/ +// which was adpated from the initial https://github.com/paritytech/schnorrkel-js/ +// forked at commit eff430ddc3090f56317c80654208b8298ef7ab3f + +use schnorrkel::{ + Keypair, MiniSecretKey, PublicKey, SecretKey, Signature, + derive::{Derivation, ChainCode, CHAIN_CODE_LENGTH}, +}; +use wasm_bindgen::prelude::*; + +// We must make sure that this is the same as declared in the substrate source code. +const SIGNING_CTX: &'static [u8] = b"substrate"; + +/// ChainCode construction helper +fn create_cc(data: &[u8]) -> ChainCode { + let mut cc = [0u8; CHAIN_CODE_LENGTH]; + + cc.copy_from_slice(&data); + + ChainCode(cc) +} + +/// Keypair helper function. +fn create_from_seed(seed: &[u8]) -> Keypair { + match MiniSecretKey::from_bytes(seed) { + Ok(mini) => return mini.expand_to_keypair(), + Err(_) => panic!("Provided seed is invalid.") + } +} + +/// Keypair helper function. +fn create_from_pair(pair: &[u8]) -> Keypair { + match Keypair::from_bytes(pair) { + Ok(pair) => return pair, + Err(_) => panic!("Provided pair is invalid.") + } +} + +/// PublicKey helper +fn create_public(public: &[u8]) -> PublicKey { + match PublicKey::from_bytes(public) { + Ok(public) => return public, + Err(_) => panic!("Provided public key is invalid.") + } +} + +/// SecretKey helper +fn create_secret(secret: &[u8]) -> SecretKey { + match SecretKey::from_bytes(secret) { + Ok(secret) => return secret, + Err(_) => panic!("Provided private key is invalid.") + } +} + +/// Perform a derivation on a secret +/// +/// * secret: UIntArray with 64 bytes +/// * cc: UIntArray with 32 bytes +/// +/// returned vector the derived keypair as a array of 96 bytes +#[wasm_bindgen] +pub fn ext_sr_derive_keypair_hard(pair: &[u8], cc: &[u8]) -> Vec { + create_from_pair(pair).secret + .hard_derive_mini_secret_key(Some(create_cc(cc)), &[]).0 + .expand_to_keypair() + .to_bytes() + .to_vec() +} + +/// Perform a derivation on a secret +/// +/// * secret: UIntArray with 64 bytes +/// * cc: UIntArray with 32 bytes +/// +/// returned vector the derived keypair as a array of 96 bytes +#[wasm_bindgen] +pub fn ext_sr_derive_keypair_soft(pair: &[u8], cc: &[u8]) -> Vec { + create_from_pair(pair) + .derived_key_simple(create_cc(cc), &[]).0 + .to_bytes() + .to_vec() +} + +/// Perform a derivation on a publicKey +/// +/// * pubkey: UIntArray with 32 bytes +/// * cc: UIntArray with 32 bytes +/// +/// returned vector is the derived publicKey as a array of 32 bytes +#[wasm_bindgen] +pub fn ext_sr_derive_public_soft(public: &[u8], cc: &[u8]) -> Vec { + create_public(public) + .derived_key_simple(create_cc(cc), &[]).0 + .to_bytes().to_vec() +} + +/// Generate a key pair. +/// +/// * seed: UIntArray with 32 element +/// +/// returned vector is the concatenation of first the private key (64 bytes) +/// followed by the public key (32) bytes. +#[wasm_bindgen] +pub fn ext_sr_from_seed(seed: &[u8]) -> Vec { + create_from_seed(seed) + .to_bytes() + .to_vec() +} + +/// Sign a message +/// +/// The combination of both public and private key must be provided. +/// This is effectively equivalent to a keypair. +/// +/// * public: UIntArray with 32 element +/// * private: UIntArray with 64 element +/// * message: Arbitrary length UIntArray +/// +/// * returned vector is the signature consisting of 64 bytes. +#[wasm_bindgen] +pub fn ext_sr_sign(public: &[u8], secret: &[u8], message: &[u8]) -> Vec { + create_secret(secret) + .sign_simple(SIGNING_CTX, message, &create_public(public)) + .to_bytes() + .to_vec() +} + +/// Verify a message and its corresponding against a public key; +/// +/// * signature: UIntArray with 64 element +/// * message: Arbitrary length UIntArray +/// * pubkey: UIntArray with 32 element +#[wasm_bindgen] +pub fn ext_sr_verify(signature: &[u8], message: &[u8], public: &[u8]) -> bool { + let signature = match Signature::from_bytes(signature) { + Ok(signature) => signature, + Err(_) => return false + }; + + create_public(public) + .verify_simple(SIGNING_CTX, message, &signature) +} + +#[cfg(test)] +pub mod tests { + extern crate rand; + extern crate schnorrkel; + + use hex_literal::{hex, hex_impl}; + use super::*; + use schnorrkel::{SIGNATURE_LENGTH, KEYPAIR_LENGTH, SECRET_KEY_LENGTH}; + + fn generate_random_seed() -> Vec { + (0..32).map(|_| rand::random::() ).collect() + } + + #[test] + fn can_create_keypair() { + let seed = generate_random_seed(); + let keypair = ext_sr_from_seed(seed.as_slice()); + + assert!(keypair.len() == KEYPAIR_LENGTH); + } + + #[test] + fn creates_pair_from_known() { + let seed = hex!("fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e"); + let expected = hex!("46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a"); + let keypair = ext_sr_from_seed(&seed); + let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + + assert_eq!(public, expected); + } + + #[test] + fn can_sign_message() { + let seed = generate_random_seed(); + let keypair = ext_sr_from_seed(seed.as_slice()); + let private = &keypair[0..SECRET_KEY_LENGTH]; + let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + let message = b"this is a message"; + let signature = ext_sr_sign(public, private, message); + + assert!(signature.len() == SIGNATURE_LENGTH); + } + + #[test] + fn can_verify_message() { + let seed = generate_random_seed(); + let keypair = ext_sr_from_seed(seed.as_slice()); + let private = &keypair[0..SECRET_KEY_LENGTH]; + let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + let message = b"this is a message"; + let signature = ext_sr_sign(public, private, message); + let is_valid = ext_sr_verify(&signature[..], message, public); + + assert!(is_valid); + } + + #[test] + fn soft_derives_pair() { + let cc = hex!("0c666f6f00000000000000000000000000000000000000000000000000000000"); // foo + let seed = hex!("fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e"); + let expected = hex!("40b9675df90efa6069ff623b0fdfcf706cd47ca7452a5056c7ad58194d23440a"); + let keypair = ext_sr_from_seed(&seed); + let derived = ext_sr_derive_keypair_soft(&keypair, &cc); + let public = &derived[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + + assert_eq!(public, expected); + } + + #[test] + fn soft_derives_public() { + let cc = hex!("0c666f6f00000000000000000000000000000000000000000000000000000000"); // foo + let public = hex!("46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a"); + let expected = hex!("40b9675df90efa6069ff623b0fdfcf706cd47ca7452a5056c7ad58194d23440a"); + let derived = ext_sr_derive_public_soft(&public, &cc); + + assert_eq!(derived, expected); + } + + #[test] + fn hard_derives_pair() { + let cc = hex!("14416c6963650000000000000000000000000000000000000000000000000000"); // Alice + let seed = hex!("fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e"); + let expected = hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"); + let keypair = ext_sr_from_seed(&seed); + let derived = ext_sr_derive_keypair_hard(&keypair, &cc); + let public = &derived[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + + assert_eq!(public, expected); + } +} diff --git a/packages/wasm-crypto/test/all.js b/packages/wasm-crypto/test/all.js deleted file mode 100644 index 299f0dd8..00000000 --- a/packages/wasm-crypto/test/all.js +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2019 @polkadot/wasm-crypto authors & contributors -// This software may be modified and distributed under the terms -// of the Apache-2.0 license. See the LICENSE file for details. -// @ts-check - -const { assert, stringToU8a, u8aToHex } = require('@polkadot/util'); -const crypto = require('../build/index'); - -function bip39Generate () { - const RESULT = crypto.bip39Generate(21); - - console.log('\tPHR', RESULT); - - assert(RESULT.split(' ').length === 21, 'ERROR: Invalid bip39 Phase length'); -} - -function bip39Validate () { - const RESULT = crypto.bip39Validate(crypto.bip39Generate(12)); - - console.log('\tVAL', RESULT); - - assert(RESULT, 'ERROR: Invalid bip39 validation'); -} - -function bip39ToEntropy () { - const RESULT = u8aToHex(crypto.bip39ToEntropy('legal winner thank year wave sausage worth useful legal winner thank yellow')); - - console.log('\tENT', RESULT); - - assert(RESULT === '0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f', 'ERROR: Invalid bip39 entropy'); -} - -function bip39ToMiniSecret () { - const RESULT = u8aToHex(crypto.bip39ToMiniSecret('legal winner thank year wave sausage worth useful legal winner thank yellow', 'Substrate')); - - console.log('\tMIN', RESULT); - - assert(RESULT === '0x4313249608fe8ac10fd5886c92c4579007272cb77c21551ee5b8d60b78041685', 'ERROR: Invalid bip39 mini secret'); -} - -function bip39ToSeed () { - const RESULT = u8aToHex(crypto.bip39ToSeed('seed sock milk update focus rotate barely fade car face mechanic mercy', '')); - - console.log('\SEE', RESULT); - - assert(RESULT === '0x3c121e20de068083b49c2315697fb59a2d9e8643c24e5ea7628132c58969a027', 'ERROR: Invalid bip39 mini secret'); -} - -function blake2bHash () { - const hash = u8aToHex(crypto.blake2b(stringToU8a('abc'), new Uint8Array(), 64)); - - console.log('\tRES', hash); - - assert(hash === '0xba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923', 'ERROR: blake2bHash does not match'); -} - -function keccak256 () { - const hash = u8aToHex(crypto.keccak256(stringToU8a('test value'))); - - console.log('\tRES', hash); - - assert(hash === '0x2d07364b5c231c56ce63d49430e085ea3033c750688ba532b24029124c26ca5e', 'ERROR: keccak256 does not match'); -} - -function pbkdf2Hash () { - const hash = u8aToHex(crypto.pbkdf2(stringToU8a('hello world'), stringToU8a('this is a salt'), 2048)); - - console.log('\tRES', hash); - - assert(hash === '0x5fcbe04f05300a3ecc5c35d18ea0b78f3f6853d2ae5f3fca374f69a7d1f78b5def5c60dae1a568026c7492511e0c53521e8bb6e03a650e1263265fee92722270', 'ERROR: pbkdf2Hash does not match'); -} - -// function secp256k1Recover () { -// const pk = u8aToHex(crypto.keccak256(crypto.secp256k1Recover( -// hexToU8a('0x2bbfa926ee947dfac384d981f8913b4e0992e52d7b6711e0ec3d9f8b0f752648'), -// hexToU8a('0x7505f2880114da51b3f5d535f8687953c0ab9af4ab81e592eaebebf53b728d2b6dfd9b5bcd70fee412b1f31360e7c2774009305cb84fc50c1d0ff8034dfa5fff1c') -// ))); - -// console.log('\tPUB', pk); - -// assert(pk === '0x91d366769d44a7c7690428bfdf67ec7eae23d2459694685257b6fc59d1baa1fe', 'ERROR: secp256k1Recover does not match'); -// } - -function sha512Hash () { - const hash = u8aToHex(crypto.sha512(stringToU8a('hello world'))); - - console.log('\tRES', hash); - - assert(hash === '0x309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f', 'ERROR: sha512Hash does not match'); -} - -function twoxHash () { - const hash64 = u8aToHex(crypto.twox(stringToU8a('abc'), 1)); - const hash256 = u8aToHex(crypto.twox(stringToU8a('abc'), 4)); - - console.log('\t64 ', hash64); - console.log('\t256', hash256); - - assert(hash64 === '0x990977adf52cbc44' && hash256 === '0x990977adf52cbc440889329981caa9bef7da5770b2b8a05303b75d95360dd62b', 'ERROR: twoxHash does not match'); -} - -const tests = { - bip39Generate, - bip39Validate, - bip39ToEntropy, - bip39ToMiniSecret, - bip39ToSeed, - blake2bHash, - keccak256, - pbkdf2Hash, - // secp256k1Recover, - sha512Hash, - twoxHash -}; - -module.exports.beforeAll = async function beforeAll () { - return crypto.waitReady(); -} - -module.exports.runAll = function runAll () { - Object.keys(tests).forEach((name) => { - console.time(name); - console.log(); - console.log(name); - - tests[name](); - - console.timeEnd(name); - }); -} - -module.exports.tests = tests; diff --git a/packages/wasm-crypto/test/all/bip39.js b/packages/wasm-crypto/test/all/bip39.js new file mode 100644 index 00000000..bf7f6a5a --- /dev/null +++ b/packages/wasm-crypto/test/all/bip39.js @@ -0,0 +1,56 @@ +// Copyright 2019 @polkadot/wasm-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. +// @ts-check + +const { assert, u8aToHex } = require('@polkadot/util'); + +const wasm = require('../../build/index'); + +function bip39_generate () { + const RESULT = wasm.bip39Generate(21); + + console.log('\tPHR', RESULT); + + assert(RESULT.split(' ').length === 21, 'ERROR: Invalid bip39 Phase length'); +} + +function bip39_validate () { + const RESULT = wasm.bip39Validate(wasm.bip39Generate(12)); + + console.log('\tVAL', RESULT); + + assert(RESULT, 'ERROR: Invalid bip39 validation'); +} + +function bip39_toEntropy () { + const RESULT = u8aToHex(wasm.bip39ToEntropy('legal winner thank year wave sausage worth useful legal winner thank yellow')); + + console.log('\tENT', RESULT); + + assert(RESULT === '0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f', 'ERROR: Invalid bip39 entropy'); +} + +function bip39_toMiniSecret () { + const RESULT = u8aToHex(wasm.bip39ToMiniSecret('legal winner thank year wave sausage worth useful legal winner thank yellow', 'Substrate')); + + console.log('\tMIN', RESULT); + + assert(RESULT === '0x4313249608fe8ac10fd5886c92c4579007272cb77c21551ee5b8d60b78041685', 'ERROR: Invalid bip39 mini secret'); +} + +function bip39_toSeed () { + const RESULT = u8aToHex(wasm.bip39ToSeed('seed sock milk update focus rotate barely fade car face mechanic mercy', '')); + + console.log('\SEE', RESULT); + + assert(RESULT === '0x3c121e20de068083b49c2315697fb59a2d9e8643c24e5ea7628132c58969a027', 'ERROR: Invalid bip39 mini secret'); +} + +module.exports = { + bip39_generate, + bip39_validate, + bip39_toEntropy, + bip39_toMiniSecret, + bip39_toSeed +}; diff --git a/packages/wasm-crypto/test/all/ed25519.js b/packages/wasm-crypto/test/all/ed25519.js new file mode 100644 index 00000000..2f93de4d --- /dev/null +++ b/packages/wasm-crypto/test/all/ed25519.js @@ -0,0 +1,62 @@ +// Copyright 2019 @polkadot/wasm-wasm-ed25519 authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. +// @ts-check + +const crypto = require('crypto'); +const { assert, hexToU8a, stringToU8a, u8aToHex } = require('@polkadot/util'); + +const wasm = require('../../build/index'); + +function extractKeys (pair) { + return [pair, pair.slice(32), pair.slice(0, 32)]; +} + +function randomPair () { + return extractKeys(wasm.ed25519KeypairFromSeed(crypto.randomBytes(32))); +} + +function ed25519_pairFromSeed () { + const [pair, pk, sk] = extractKeys(wasm.ed25519KeypairFromSeed(stringToU8a('12345678901234567890123456789012'))); + + console.log('\tSEC', u8aToHex(sk)); + console.log('\tPUB', u8aToHex(pk)); + + assert(u8aToHex(pair) === '0x31323334353637383930313233343536373839303132333435363738393031322f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee', 'ERROR: pairFromSeed() does not match'); +} + +function ed25519_signAndVerify () { + const [, pk, sk] = randomPair(); + const signature = wasm.ed25519Sign(pk, sk, stringToU8a('this is a message')); + const isValid = wasm.ed25519Verify(signature, stringToU8a('this is a message'), pk); + + console.log('\tSIG', u8aToHex(signature)); + console.log('\tRES', isValid); + + assert(isValid, 'ERROR: Unable to verify signature'); +} + +function ed25519_verifyExisting () { + const isValid = wasm.ed25519Verify(hexToU8a('0x90588f3f512496f2dd40571d162e8182860081c74e2085316e7c4396918f07da412ee029978e4dd714057fe973bd9e7d645148bf7b66680d67c93227cde95202'), stringToU8a('this is a message'), hexToU8a('0x2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee')); + + console.log('\tRES', isValid); + + assert(isValid, 'ERROR: Unable to verify signature'); +} + +function ed25519_benchmark () { + const MESSAGE = stringToU8a('this is a message'); + + for (let i = 0; i < 256; i++) { + const [, pk, sk] = randomPair(); + + assert(wasm.ed25519Verify(wasm.ed25519Sign(pk, sk, MESSAGE), MESSAGE, pk), 'ERROR: Unable to verify signature'); + } +} + +module.exports = { + ed25519_pairFromSeed, + ed25519_signAndVerify, + ed25519_verifyExisting, + ed25519_benchmark +}; diff --git a/packages/wasm-crypto/test/all/hashing.js b/packages/wasm-crypto/test/all/hashing.js new file mode 100644 index 00000000..fb58c6d4 --- /dev/null +++ b/packages/wasm-crypto/test/all/hashing.js @@ -0,0 +1,70 @@ +// Copyright 2019 @polkadot/wasm-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. +// @ts-check + +const { assert, stringToU8a, u8aToHex } = require('@polkadot/util'); + +const wasm = require('../../build/index'); + +function blake2bHash () { + const hash = u8aToHex(wasm.blake2b(stringToU8a('abc'), new Uint8Array(), 64)); + + console.log('\tRES', hash); + + assert(hash === '0xba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923', 'ERROR: blake2bHash does not match'); +} + +function keccak256 () { + const hash = u8aToHex(wasm.keccak256(stringToU8a('test value'))); + + console.log('\tRES', hash); + + assert(hash === '0x2d07364b5c231c56ce63d49430e085ea3033c750688ba532b24029124c26ca5e', 'ERROR: keccak256 does not match'); +} + +function pbkdf2Hash () { + const hash = u8aToHex(wasm.pbkdf2(stringToU8a('hello world'), stringToU8a('this is a salt'), 2048)); + + console.log('\tRES', hash); + + assert(hash === '0x5fcbe04f05300a3ecc5c35d18ea0b78f3f6853d2ae5f3fca374f69a7d1f78b5def5c60dae1a568026c7492511e0c53521e8bb6e03a650e1263265fee92722270', 'ERROR: pbkdf2Hash does not match'); +} + +// function secp256k1Recover () { +// const pk = u8aToHex(wasm.keccak256(wasm.secp256k1Recover( +// hexToU8a('0x2bbfa926ee947dfac384d981f8913b4e0992e52d7b6711e0ec3d9f8b0f752648'), +// hexToU8a('0x7505f2880114da51b3f5d535f8687953c0ab9af4ab81e592eaebebf53b728d2b6dfd9b5bcd70fee412b1f31360e7c2774009305cb84fc50c1d0ff8034dfa5fff1c') +// ))); + +// console.log('\tPUB', pk); + +// assert(pk === '0x91d366769d44a7c7690428bfdf67ec7eae23d2459694685257b6fc59d1baa1fe', 'ERROR: secp256k1Recover does not match'); +// } + +function sha512Hash () { + const hash = u8aToHex(wasm.sha512(stringToU8a('hello world'))); + + console.log('\tRES', hash); + + assert(hash === '0x309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f', 'ERROR: sha512Hash does not match'); +} + +function twoxHash () { + const hash64 = u8aToHex(wasm.twox(stringToU8a('abc'), 1)); + const hash256 = u8aToHex(wasm.twox(stringToU8a('abc'), 4)); + + console.log('\t64 ', hash64); + console.log('\t256', hash256); + + assert(hash64 === '0x990977adf52cbc44' && hash256 === '0x990977adf52cbc440889329981caa9bef7da5770b2b8a05303b75d95360dd62b', 'ERROR: twoxHash does not match'); +} + +module.exports = { + blake2bHash, + keccak256, + pbkdf2Hash, + // secp256k1Recover, + sha512Hash, + twoxHash +}; diff --git a/packages/wasm-crypto/test/all/index.js b/packages/wasm-crypto/test/all/index.js new file mode 100644 index 00000000..409d7c67 --- /dev/null +++ b/packages/wasm-crypto/test/all/index.js @@ -0,0 +1,30 @@ +// Copyright 2019 @polkadot/wasm-crypto authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. +// @ts-check + +const wasm = require('../../build/index'); +const bip39 = require('./bip39'); +const ed25519 = require('./ed25519'); +const hashing = require('./hashing'); +const sr25519 = require('./sr25519'); + +const tests = { ...bip39, ...ed25519, ...hashing, ...sr25519 }; + +module.exports.beforeAll = async function beforeAll () { + return wasm.waitReady(); +} + +module.exports.runAll = function runAll () { + Object.keys(tests).forEach((name) => { + console.time(name); + console.log(); + console.log(name); + + tests[name](); + + console.timeEnd(name); + }); +} + +module.exports.tests = tests; diff --git a/packages/wasm-crypto/test/all/sr25519.js b/packages/wasm-crypto/test/all/sr25519.js new file mode 100644 index 00000000..24e9fd71 --- /dev/null +++ b/packages/wasm-crypto/test/all/sr25519.js @@ -0,0 +1,126 @@ +// Copyright 2019 @polkadot/wasm-schnorrkel authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. +// @ts-check + +const crypto = require('crypto'); +const { assert, hexToU8a, stringToU8a, u8aToHex } = require('@polkadot/util'); + +const wasm = require('../../build/index'); + +function extractKeys (pair) { + return [pair, pair.slice(64), pair.slice(0, 64)]; +} + +function randomPair () { + return extractKeys(wasm.sr25519KeypairFromSeed(crypto.randomBytes(32))); +} + +function sr25519_pairFromSeed () { + const pair = wasm.sr25519KeypairFromSeed(stringToU8a('12345678901234567890123456789012')); + + console.log('\tSEC', u8aToHex(pair.slice(0, 64))); + console.log('\tPUB', u8aToHex(pair.slice(64))); + + assert(u8aToHex(pair) === '0xf0106660c3dda23f16daa9ac5b811b963077f5bc0af89f85804f0de8e424f050f98d66f39442506ff947fd911f18c7a7a5da639a63e8d3b4e233f74143d951c1741c08a06f41c596608f6774259bd9043304adfa5d3eea62760bd9be97634d63', 'ERROR: pairFromSeed() does not match'); +} + +function sr25519_devFromSeed () { + const pair = wasm.sr25519KeypairFromSeed(hexToU8a('0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e')); + + console.log('\tSEC', u8aToHex(pair.slice(0, 64))); + console.log('\tPUB', u8aToHex(pair.slice(64))); + + assert(u8aToHex(pair) === '0x28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca3446ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a', 'ERROR: devFromSeed() does not match'); +} + +function sr25519_verifyExisting () { + const PK = hexToU8a('0x741c08a06f41c596608f6774259bd9043304adfa5d3eea62760bd9be97634d63'); + const MESSAGE = stringToU8a('this is a message'); + const SIGNATURE = hexToU8a('0xdecef12cf20443e7c7a9d406c237e90bcfcf145860722622f92ebfd5eb4b5b3990b6443934b5cba8f925a0ae75b3a77d35b8490cbb358dd850806e58eaf72904' +); + + const isValid = wasm.sr25519Verify(SIGNATURE, MESSAGE, PK); + + console.log('\tRES', isValid); + + assert(isValid, 'ERROR: Unable to verify signature'); +} + +function sr25519_signAndVerify () { + const [, pk, sk] = randomPair(); + const signature = wasm.sr25519Sign(pk, sk, stringToU8a('this is a message')); + const isValid = wasm.sr25519Verify(signature, stringToU8a('this is a message'), pk); + + console.log('\tSIG', u8aToHex(signature)); + console.log('\tRES', isValid); + + assert(isValid, 'ERROR: Unable to verify signature'); +} + +function sr25519_deriveHard () { + const [pair] = randomPair(); + const derived = wasm.sr25519DeriveKeypairHard(pair, hexToU8a('0x0c666f6f00000000000000000000000000000000000000000000000000000000')); + + console.log('\tSEC', u8aToHex(derived.slice(0, 64))); + console.log('\tPUB', u8aToHex(derived.slice(64))); +} + +function sr25519_deriveHardKnown () { + const derived = wasm.sr25519DeriveKeypairHard(hexToU8a('0x28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca3446ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a'), hexToU8a('0x14416c6963650000000000000000000000000000000000000000000000000000')); + const publicKey = u8aToHex(derived.slice(64)); + + console.log('\tSEC', u8aToHex(derived.slice(0, 64))); + console.log('\tPUB', publicKey); + + assert(publicKey === '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d', 'Unmatched resulting public keys'); +} + +function sr25519_deriveSoft () { + const [pair] = randomPair(); + const derived = wasm.sr25519DeriveKeypairSoft(pair, hexToU8a('0x0c666f6f00000000000000000000000000000000000000000000000000000000')); + + console.log('\tSEC', u8aToHex(derived.slice(0, 64))); + console.log('\tPUB', u8aToHex(derived.slice(64))); +} + +function sr25519_deriveSoftKnown () { + const derived = wasm.sr25519DeriveKeypairSoft(hexToU8a('0x28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca3446ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a'), hexToU8a('0x0c666f6f00000000000000000000000000000000000000000000000000000000')); + const publicKey = u8aToHex(derived.slice(64)); + + console.log('\tSEC', u8aToHex(derived.slice(0, 64))); + console.log('\tPUB', publicKey); + + assert(publicKey === '0x40b9675df90efa6069ff623b0fdfcf706cd47ca7452a5056c7ad58194d23440a', 'Unmatched resulting public keys'); +} + +function sr25519_deriveSoftPubkey () { + const derived = u8aToHex(wasm.sr25519DerivePublicSoft( hexToU8a('0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a'), hexToU8a('0x0c666f6f00000000000000000000000000000000000000000000000000000000'))); + + console.log('\tPUB', derived); + + assert(derived === '0x40b9675df90efa6069ff623b0fdfcf706cd47ca7452a5056c7ad58194d23440a', 'Unmatched resulting public keys'); +} + +function sr25519_benchmark () { + const MESSAGE = stringToU8a('this is a message'); + + for (let i = 0; i < 256; i++) { + const [, pk, sk] = randomPair(); + + assert(wasm.sr25519Verify(wasm.sr25519Sign(pk, sk, MESSAGE), MESSAGE, pk), 'ERROR: Unable to verify signature'); + } +} + +module.exports = { + sr25519_pairFromSeed, + sr25519_devFromSeed, + sr25519_deriveHard, + sr25519_deriveHardKnown, + sr25519_deriveSoft, + sr25519_deriveSoftKnown, + sr25519_deriveSoftPubkey, + sr25519_signAndVerify, + sr25519_verifyExisting, + sr25519_benchmark +}; diff --git a/packages/wasm-crypto/test/jest.spec.js b/packages/wasm-crypto/test/jest.spec.js index c9d4b9ee..3381d285 100644 --- a/packages/wasm-crypto/test/jest.spec.js +++ b/packages/wasm-crypto/test/jest.spec.js @@ -5,7 +5,7 @@ require('../build/crypto-polyfill'); -const { beforeAll, tests } = require('./all.js'); +const { beforeAll, tests } = require('./all'); describe('schnorrkel-js', () => { beforeEach(beforeAll); diff --git a/packages/wasm-dalek-ed25519/package.json b/packages/wasm-dalek-ed25519/nobuild.package.json similarity index 100% rename from packages/wasm-dalek-ed25519/package.json rename to packages/wasm-dalek-ed25519/nobuild.package.json diff --git a/packages/wasm-dalek-ed25519/test/all.js b/packages/wasm-dalek-ed25519/test/all.js index d2efbc54..6e1c0fe3 100644 --- a/packages/wasm-dalek-ed25519/test/all.js +++ b/packages/wasm-dalek-ed25519/test/all.js @@ -1,4 +1,4 @@ -// Copyright 2019 @polkadot/wasm-schnorrkel authors & contributors +// Copyright 2019 @polkadot/wasm-dalek-ed25519 authors & contributors // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. // @ts-check diff --git a/packages/wasm-dalek-ed25519/test/asm.js b/packages/wasm-dalek-ed25519/test/asm.js index 5cd58cd5..18e20464 100644 --- a/packages/wasm-dalek-ed25519/test/asm.js +++ b/packages/wasm-dalek-ed25519/test/asm.js @@ -1,4 +1,4 @@ -// Copyright 2019 @polkadot/wasm-schnorrkel authors & contributors +// Copyright 2019 @polkadot/wasm-dalek-ed25519 authors & contributors // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. // @ts-check diff --git a/packages/wasm-dalek-ed25519/test/jest.spec.js b/packages/wasm-dalek-ed25519/test/jest.spec.js index 5cd3e1d4..e6b4a4f4 100644 --- a/packages/wasm-dalek-ed25519/test/jest.spec.js +++ b/packages/wasm-dalek-ed25519/test/jest.spec.js @@ -1,4 +1,4 @@ -// Copyright 2019 @polkadot/wasm-schnorrkel authors & contributors +// Copyright 2019 @polkadot/wasm-dalek-ed25519 authors & contributors // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. // @ts-check diff --git a/packages/wasm-dalek-ed25519/test/wasm.js b/packages/wasm-dalek-ed25519/test/wasm.js index 265bbecf..223ec5a7 100644 --- a/packages/wasm-dalek-ed25519/test/wasm.js +++ b/packages/wasm-dalek-ed25519/test/wasm.js @@ -1,4 +1,4 @@ -// Copyright 2019 @polkadot/wasm-schnorrkel authors & contributors +// Copyright 2019 @polkadot/wasm-dalek-ed25519 authors & contributors // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. // @ts-check diff --git a/packages/wasm-schnorrkel/package.json b/packages/wasm-schnorrkel/nobuild.package.json similarity index 100% rename from packages/wasm-schnorrkel/package.json rename to packages/wasm-schnorrkel/nobuild.package.json diff --git a/yarn.lock b/yarn.lock index 5098eec4..1990b23d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1543,10 +1543,10 @@ universal-user-agent "^2.0.0" url-template "^2.0.8" -"@polkadot/dev@^0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@polkadot/dev/-/dev-0.28.1.tgz#c9b53a5948252c4f11686a13cd5990d84687f9a6" - integrity sha512-x2Yh/KFaTfiQNOs2A1cBJyZRAA5sk/hKerghfj2BubnWm9M+Qx112aoApKUL+s0KMOyKU3ZQjiGSuGyGn6TtqA== +"@polkadot/dev@^0.29.0-beta.0": + version "0.29.0-beta.0" + resolved "https://registry.yarnpkg.com/@polkadot/dev/-/dev-0.29.0-beta.0.tgz#ec4296b7365b1fb2c788cf78ea2ed97d1631b9e1" + integrity sha512-kKhTshLED1XvpecJ3ECBQARFAmRRw3xPxvHvV5+EaPZvN0XTGn7DRi3QfP9IugAsDbT5kVWLokojkBgjgiFstg== dependencies: "@babel/cli" "^7.2.3" "@babel/core" "^7.4.0"