diff --git a/.gitignore b/.gitignore index 111f816..18437a3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ /target /tests/browser -/lib/secp256k1.wasm +/lib/secp256k1* /package-lock.json diff --git a/Cargo.lock b/Cargo.lock index fce2217..98c0f8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,217 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bumpalo" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" + [[package]] name = "cc" version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "idna" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "js-sys" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9f84f9b115ce7843d60706df1422a916680bfdfcbdb0447c5614ff9d7e4d78" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "napi" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1981522b098db959ffc2e93908bc4d789b515b4fef3d1c0ceb771e68e19e4d" +dependencies = [ + "napi-build", + "napi-sys", + "winapi", +] + +[[package]] +name = "napi-build" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12ae7b330beeeb0b4ebfcd691e29270869717a9145e1ea8e82c9244d6abe27a" +dependencies = [ + "cfg-if", + "ureq", +] + +[[package]] +name = "napi-derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90b290d40fb2237fc658adcd768667fd779dc631066c1e522d838bffbd7d3f99" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "napi-sys" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3de3dc74e996f41f18007ec42d96944f32353a3c9edd4ba3e7a1ff6efee5f6d4" + +[[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secp256k1" +version = "0.0.0" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-node" +version = "0.0.0" +dependencies = [ + "lazy_static", + "napi", + "napi-build", + "napi-derive", + "secp256k1", +] + [[package]] name = "secp256k1-sys" version = "0.4.0" @@ -20,5 +225,200 @@ dependencies = [ name = "secp256k1-wasm" version = "0.0.0" dependencies = [ - "secp256k1-sys", + "secp256k1", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tinyvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585dcbf3483242f77b502864478ede62431baf3442b99367d3456ec20c1707b" +dependencies = [ + "base64", + "chunked_transfer", + "log", + "once_cell", + "rustls", + "url", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", ] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f8ec44822dd71f5f221a5847fb34acd9060535c1211b70a05844c0f6383b1" + +[[package]] +name = "web-sys" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec600b26223b2948cedfde2a0aa6756dcf1fef616f43d7b3097aaf53a6c4d92b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 4fb5ff4..1f4bdaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,7 @@ [workspace] members = [ + "secp256k1", + "secp256k1-node", "secp256k1-wasm", ] diff --git a/Makefile b/Makefile index 42f9de8..d1ecab6 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,58 @@ -build-wasm-location = lib/secp256k1.wasm -build-wasm-cp = mkdir -p lib && cp -f target/wasm32-unknown-unknown/$(1)/secp256k1_wasm.wasm $(build-wasm-location) - +.PHONY: build-node-% +build-node-%: export PAIR = $(subst +, ,$(subst build-node-,,$@)) +build-node-%: + cargo build --package secp256k1-node --target $(firstword $(PAIR)) --release + cp -f target/$(firstword $(PAIR))/release/libsecp256k1_node.so lib/secp256k1-$(lastword $(PAIR)).so + +.PHONY: build-node-debug +build-node-debug: + cargo build --package secp256k1-node + +.PHONY: build-node-debug-% +build-node-debug-%: export PAIR = $(subst +, ,$(subst build-node-debug-,,$@)) +build-node-debug-%: + cargo build --package secp256k1-node --target $(firstword $(PAIR)) + cp -f target/$(firstword $(PAIR))/debug/libsecp256k1_node.so lib/secp256k1-$(lastword $(PAIR)).so + +.PHONY: build-wasm build-wasm: - cargo build --target wasm32-unknown-unknown --release - $(call build-wasm-cp,release) - wasm-opt --strip-debug --strip-producers --output $(build-wasm-location) $(build-wasm-location) - node ./util/wasm-strip.js $(build-wasm-location) - wasm-opt -O4 --output $(build-wasm-location) $(build-wasm-location) + cargo build --package secp256k1-wasm --target wasm32-unknown-unknown --release + cp -f target/wasm32-unknown-unknown/release/secp256k1_wasm.wasm lib/secp256k1.wasm + wasm-opt --strip-debug --strip-producers --output lib/secp256k1.wasm lib/secp256k1.wasm + node util/wasm-strip.js lib/secp256k1.wasm + wasm-opt -O4 --output lib/secp256k1.wasm lib/secp256k1.wasm +.PHONY: build-wasm-debug build-wasm-debug: - cargo build --target wasm32-unknown-unknown - $(call build-wasm-cp,debug) + cargo build --package secp256k1-wasm --target wasm32-unknown-unknown + cp -f target/wasm32-unknown-unknown/debug/secp256k1_wasm.wasm lib/secp256k1.wasm +.PHONY: clean clean: - rm -rf target node_modules tests/browser + rm -rf lib/secp256k1* target node_modules tests/browser +.PHONY: format format: cargo-fmt npx prettier -w . +.PHONY: lint lint: cargo fmt -- --check cargo clippy --target wasm32-unknown-unknown npx prettier -c . +.PHONY: test test: test-browser test-node +.PHONY: test-browser-build test-browser-build: npx webpack build -c tests/browser.webpack.js +.PHONY: test-browser test-browser: build-wasm-debug test-browser-build cat tests/browser/index.js | npx browser-run --static tests/browser | npx tap-difflet -p -test-node: build-wasm-debug +.PHONY: test-node +test-node: build-node-debug build-wasm-debug node --experimental-json-modules tests/index.js | npx tap-difflet -p diff --git a/lib/addon.js b/lib/addon.js new file mode 100644 index 0000000..c6ef80f --- /dev/null +++ b/lib/addon.js @@ -0,0 +1,43 @@ +import { join } from "path"; +import createApi from "./api.js"; +import { generateSeed } from "./rand.js"; +import { throwError } from "./validate_error.js"; + +function getLibExt() { + switch (process.platform) { + case "darwin": + return "dylib"; + case "win32": + return "dll"; + case "linux": + case "freebsd": + case "openbsd": + case "android": + case "sunos": + return "so"; + } +} + +function getPrebuildLibLocation() { + const name = `secp256k1-${process.arch}-${process.platform}.${getLibExt()}`; + return new URL(name, import.meta.url).pathname; +} + +function getLocalBuildLibLocation(mode) { + const path = join("..", "target", mode, "libsecp256k1_node.so"); + return new URL(path, import.meta.url).pathname; +} + +function loadAddon(location) { + try { + const module = { exports: { throwError, generateSeed } }; + process.dlopen(module, location); + return createApi(module.exports); + } catch (_error) { + return null; + } +} + +export default loadAddon(getLocalBuildLibLocation("debug")) || + loadAddon(getLocalBuildLibLocation("release")) || + loadAddon(getPrebuildLibLocation()); diff --git a/lib/api.js b/lib/api.js new file mode 100644 index 0000000..b08ab13 --- /dev/null +++ b/lib/api.js @@ -0,0 +1,101 @@ +import * as validate from "./validate.js"; + +export default function createApi(secp256k1) { + function assumeCompression(compressed, p) { + return compressed === undefined + ? p.length + : compressed === true + ? validate.PUBLIC_KEY_COMPRESSED_SIZE + : validate.PUBLIC_KEY_UNCOMPRESSED_SIZE; + } + + function signWithEntropy(h, d, e) { + validate.validateHash(h); + validate.validatePrivate(d); + validate.validateExtraData(e); + return secp256k1.signWithEntropy(h, d, e); + } + + return { + __initializeContext() { + secp256k1.initializeContext(); + }, + + isPoint(p) { + return validate.isPoint(p) && secp256k1.isPoint(p); + }, + + isPointCompressed(p) { + return validate.isPointCompressed(p) && secp256k1.isPoint(p); + }, + + isPrivate(x) { + return validate.isPrivate(x); + }, + + pointAdd(pA, pB, compressed) { + validate.validatePoint(pA); + validate.validatePoint(pB); + const outputlen = assumeCompression(compressed, pA); + return secp256k1.pointAdd(pA, pB, outputlen); + }, + + pointAddScalar(p, tweak, compressed) { + validate.validatePoint(p); + validate.validateTweak(tweak); + const outputlen = assumeCompression(compressed, p); + return secp256k1.pointAddScalar(p, tweak, outputlen); + }, + + pointCompress(p, compressed) { + validate.validatePoint(p); + const outputlen = assumeCompression(compressed, p); + return secp256k1.pointCompress(p, outputlen); + }, + + pointFromScalar(d, compressed = true) { + validate.validatePrivate(d); + const outputlen = assumeCompression(compressed); + return secp256k1.pointFromScalar(d, outputlen); + }, + + pointMultiply(p, tweak, compressed) { + validate.validatePoint(p); + validate.validateTweak(tweak); + const outputlen = assumeCompression(compressed, p); + return secp256k1.pointMultiply(p, tweak, outputlen); + }, + + privateAdd(d, tweak) { + validate.validatePrivate(d); + validate.validateTweak(tweak); + return secp256k1.privateAdd(d, tweak); + }, + + privateSub(d, tweak) { + validate.validatePrivate(d); + validate.validateTweak(tweak); + + // We can not pass zero tweak to WASM, because WASM use `secp256k1_ec_seckey_negate` for tweak negate. + // (zero is not valid seckey) + if (validate.isZero(tweak)) { + return new Uint8Array(d); + } + + return secp256k1.privateSub(d, tweak); + }, + + sign(h, d) { + return signWithEntropy(h, d); + }, + + signWithEntropy, + + verify(h, Q, signature, strict = false) { + validate.validateHash(h); + validate.validatePoint(Q); + validate.validateSignature(signature); + return secp256k1.verify(h, Q, signature, strict === true ? 1 : 0); + }, + }; +} diff --git a/lib/index.browser.js b/lib/index.browser.js new file mode 100644 index 0000000..d9a6811 --- /dev/null +++ b/lib/index.browser.js @@ -0,0 +1,21 @@ +import wasm from "./wasm.js"; + +export const __addon = null; +export const __wasm = wasm; + +export const { + __initializeContext, + isPoint, + isPointCompressed, + isPrivate, + pointAdd, + pointAddScalar, + pointCompress, + pointFromScalar, + pointMultiply, + privateAdd, + privateSub, + sign, + signWithEntropy, + verify, +} = wasm; diff --git a/lib/index.js b/lib/index.js index 37e4c51..63de9c5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,228 +1,22 @@ -import * as validate from "./validate.js"; -import wasm from "./secp256k1.js"; - -export const __initializeContext = wasm.initializeContext; - -const WASM_BUFFER = new Uint8Array(wasm.memory.buffer); -const WASM_PRIVATE_KEY_PTR = wasm.PRIVATE_INPUT.value; -const WASM_PUBLIC_KEY_INPUT_PTR = wasm.PUBLIC_KEY_INPUT.value; -const WASM_PUBLIC_KEY_INPUT_PTR2 = wasm.PUBLIC_KEY_INPUT2.value; -const WASM_TWEAK_INPUT_PTR = wasm.TWEAK_INPUT.value; -const WASM_HASH_INPUT_PTR = wasm.HASH_INPUT.value; -const WASM_EXTRA_DATA_INPUT_PTR = wasm.EXTRA_DATA_INPUT.value; -const WASM_SIGNATURE_INPUT_PTR = wasm.SIGNATURE_INPUT.value; - -const PRIVATE_KEY_INPUT = WASM_BUFFER.subarray( - WASM_PRIVATE_KEY_PTR, - WASM_PRIVATE_KEY_PTR + validate.PRIVATE_KEY_SIZE -); -const PUBLIC_KEY_INPUT = WASM_BUFFER.subarray( - WASM_PUBLIC_KEY_INPUT_PTR, - WASM_PUBLIC_KEY_INPUT_PTR + validate.PUBLIC_KEY_UNCOMPRESSED_SIZE -); -const PUBLIC_KEY_INPUT2 = WASM_BUFFER.subarray( - WASM_PUBLIC_KEY_INPUT_PTR2, - WASM_PUBLIC_KEY_INPUT_PTR2 + validate.PUBLIC_KEY_UNCOMPRESSED_SIZE -); -const TWEAK_INPUT = WASM_BUFFER.subarray( - WASM_TWEAK_INPUT_PTR, - WASM_TWEAK_INPUT_PTR + validate.TWEAK_SIZE -); -const HASH_INPUT = WASM_BUFFER.subarray( - WASM_HASH_INPUT_PTR, - WASM_HASH_INPUT_PTR + validate.HASH_SIZE -); -const EXTRA_DATA_INPUT = WASM_BUFFER.subarray( - WASM_EXTRA_DATA_INPUT_PTR, - WASM_EXTRA_DATA_INPUT_PTR + validate.EXTRA_DATA_SIZE -); -const SIGNATURE_INPUT = WASM_BUFFER.subarray( - WASM_SIGNATURE_INPUT_PTR, - WASM_SIGNATURE_INPUT_PTR + validate.SIGNATURE_SIZE -); - -function assumeCompression(compressed, p) { - return compressed === undefined - ? p.length - : compressed === true - ? validate.PUBLIC_KEY_COMPRESSED_SIZE - : validate.PUBLIC_KEY_UNCOMPRESSED_SIZE; -} - -function __isPoint(p) { - try { - PUBLIC_KEY_INPUT.set(p); - return wasm.isPoint(p.length) === 1; - } finally { - PUBLIC_KEY_INPUT.fill(0); - } -} - -export function isPoint(p) { - return validate.isPoint(p) && __isPoint(p); -} - -export function isPointCompressed(p) { - return validate.isPointCompressed(p) && __isPoint(p); -} - -export function isPrivate(x) { - return validate.isPrivate(x); -} - -export function pointAdd(pA, pB, compressed) { - validate.validatePoint(pA); - validate.validatePoint(pB); - const outputlen = assumeCompression(compressed, pA); - - try { - PUBLIC_KEY_INPUT.set(pA); - PUBLIC_KEY_INPUT2.set(pB); - return wasm.pointAdd(pA.length, pB.length, outputlen) === 1 - ? PUBLIC_KEY_INPUT.slice(0, outputlen) - : null; - } finally { - PUBLIC_KEY_INPUT.fill(0); - PUBLIC_KEY_INPUT2.fill(0); - } -} - -export function pointAddScalar(p, tweak, compressed) { - validate.validatePoint(p); - validate.validateTweak(tweak); - let outputlen = assumeCompression(compressed, p); - - try { - PUBLIC_KEY_INPUT.set(p); - TWEAK_INPUT.set(tweak); - return wasm.pointAddScalar(p.length, outputlen) === 1 - ? PUBLIC_KEY_INPUT.slice(0, outputlen) - : null; - } finally { - PUBLIC_KEY_INPUT.fill(0); - TWEAK_INPUT.fill(0); - } -} - -export function pointCompress(p, compressed) { - validate.validatePoint(p); - const outputlen = assumeCompression(compressed, p); - - try { - PUBLIC_KEY_INPUT.set(p); - wasm.pointCompress(p.length, outputlen); - return PUBLIC_KEY_INPUT.slice(0, outputlen); - } finally { - PUBLIC_KEY_INPUT.fill(0); - } -} - -export function pointFromScalar(d, compressed = true) { - validate.validatePrivate(d); - const outputlen = assumeCompression(compressed); - - try { - PRIVATE_KEY_INPUT.set(d); - return wasm.pointFromScalar(outputlen) === 1 - ? PUBLIC_KEY_INPUT.slice(0, outputlen) - : null; - } finally { - PRIVATE_KEY_INPUT.fill(0); - PUBLIC_KEY_INPUT.fill(0); - } -} - -export function pointMultiply(p, tweak, compressed) { - validate.validatePoint(p); - validate.validateTweak(tweak); - let outputlen = assumeCompression(compressed, p); - - try { - PUBLIC_KEY_INPUT.set(p); - TWEAK_INPUT.set(tweak); - return wasm.pointMultiply(p.length, outputlen) === 1 - ? PUBLIC_KEY_INPUT.slice(0, outputlen) - : null; - } finally { - PUBLIC_KEY_INPUT.fill(0); - TWEAK_INPUT.fill(0); - } -} - -export function privateAdd(d, tweak) { - validate.validatePrivate(d); - validate.validateTweak(tweak); - - try { - PRIVATE_KEY_INPUT.set(d); - TWEAK_INPUT.set(tweak); - return wasm.privateAdd() === 1 - ? PRIVATE_KEY_INPUT.slice(0, validate.PRIVATE_KEY_SIZE) - : null; - } finally { - PRIVATE_KEY_INPUT.fill(0); - TWEAK_INPUT.fill(0); - } -} - -export function privateSub(d, tweak) { - validate.validatePrivate(d); - validate.validateTweak(tweak); - - // We can not pass zero tweak to WASM, because WASM use `secp256k1_ec_seckey_negate` for tweak negate. - // (zero is not valid seckey) - if (validate.isZero(tweak)) { - return new Uint8Array(d); - } - - try { - PRIVATE_KEY_INPUT.set(d); - TWEAK_INPUT.set(tweak); - return wasm.privateSub() === 1 - ? PRIVATE_KEY_INPUT.slice(0, validate.PRIVATE_KEY_SIZE) - : null; - } finally { - PRIVATE_KEY_INPUT.fill(0); - TWEAK_INPUT.fill(0); - } -} - -export function sign(h, d) { - return signWithEntropy(h, d); -} - -export function signWithEntropy(h, d, e) { - validate.validateHash(h); - validate.validatePrivate(d); - validate.validateExtraData(e); - - try { - HASH_INPUT.set(h); - PRIVATE_KEY_INPUT.set(d); - if (e !== undefined) EXTRA_DATA_INPUT.set(e); - wasm.sign(e === undefined ? 0 : 1); - return SIGNATURE_INPUT.slice(0, validate.SIGNATURE_SIZE); - } finally { - HASH_INPUT.fill(0); - PRIVATE_KEY_INPUT.fill(0); - if (e !== undefined) EXTRA_DATA_INPUT.fill(0); - SIGNATURE_INPUT.fill(0); - } -} - -export function verify(h, Q, signature, strict = false) { - validate.validateHash(h); - validate.validatePoint(Q); - validate.validateSignature(signature); - - try { - HASH_INPUT.set(h); - PUBLIC_KEY_INPUT.set(Q); - SIGNATURE_INPUT.set(signature); - return wasm.verify(Q.length, strict === true ? 1 : 0) === 1 ? true : false; - } finally { - HASH_INPUT.fill(0); - PUBLIC_KEY_INPUT.fill(0); - SIGNATURE_INPUT.fill(0); - } -} +import addon from "./addon.js"; +import wasm from "./wasm.js"; + +export const __addon = addon; +export const __wasm = wasm; + +export const { + __initializeContext, + isPoint, + isPointCompressed, + isPrivate, + pointAdd, + pointAddScalar, + pointCompress, + pointFromScalar, + pointMultiply, + privateAdd, + privateSub, + sign, + signWithEntropy, + verify, +} = addon || wasm; diff --git a/lib/wasm_rand.browser.js b/lib/rand.browser.js similarity index 100% rename from lib/wasm_rand.browser.js rename to lib/rand.browser.js diff --git a/lib/wasm_rand.js b/lib/rand.js similarity index 64% rename from lib/wasm_rand.js rename to lib/rand.js index 6ac3af1..0e92ffe 100644 --- a/lib/wasm_rand.js +++ b/lib/rand.js @@ -3,3 +3,7 @@ import { randomBytes } from "crypto"; export function generateInt32() { return randomBytes(4).readInt32BE(0); } + +export function generateSeed() { + return randomBytes(32); +} diff --git a/lib/validate.js b/lib/validate.js index 3031fdd..701335b 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -6,7 +6,7 @@ import { ERROR_BAD_HASH, ERROR_BAD_EXTRA_DATA, ERROR_BAD_SIGNATURE, -} from "./wasm_error.js"; +} from "./validate_error.js"; export const PRIVATE_KEY_SIZE = 32; export const PUBLIC_KEY_COMPRESSED_SIZE = 33; diff --git a/lib/wasm_error.js b/lib/validate_error.js similarity index 100% rename from lib/wasm_error.js rename to lib/validate_error.js diff --git a/lib/wasm.js b/lib/wasm.js new file mode 100644 index 0000000..9f4f2bf --- /dev/null +++ b/lib/wasm.js @@ -0,0 +1,167 @@ +import createApi from "./api.js"; +import * as validate from "./validate.js"; +import wasm from "./wasm_loader.js"; + +const WASM_BUFFER = new Uint8Array(wasm.memory.buffer); +const WASM_PRIVATE_KEY_PTR = wasm.PRIVATE_INPUT.value; +const WASM_PUBLIC_KEY_INPUT_PTR = wasm.PUBLIC_KEY_INPUT.value; +const WASM_PUBLIC_KEY_INPUT_PTR2 = wasm.PUBLIC_KEY_INPUT2.value; +const WASM_TWEAK_INPUT_PTR = wasm.TWEAK_INPUT.value; +const WASM_HASH_INPUT_PTR = wasm.HASH_INPUT.value; +const WASM_EXTRA_DATA_INPUT_PTR = wasm.EXTRA_DATA_INPUT.value; +const WASM_SIGNATURE_INPUT_PTR = wasm.SIGNATURE_INPUT.value; + +const PRIVATE_KEY_INPUT = WASM_BUFFER.subarray( + WASM_PRIVATE_KEY_PTR, + WASM_PRIVATE_KEY_PTR + validate.PRIVATE_KEY_SIZE +); +const PUBLIC_KEY_INPUT = WASM_BUFFER.subarray( + WASM_PUBLIC_KEY_INPUT_PTR, + WASM_PUBLIC_KEY_INPUT_PTR + validate.PUBLIC_KEY_UNCOMPRESSED_SIZE +); +const PUBLIC_KEY_INPUT2 = WASM_BUFFER.subarray( + WASM_PUBLIC_KEY_INPUT_PTR2, + WASM_PUBLIC_KEY_INPUT_PTR2 + validate.PUBLIC_KEY_UNCOMPRESSED_SIZE +); +const TWEAK_INPUT = WASM_BUFFER.subarray( + WASM_TWEAK_INPUT_PTR, + WASM_TWEAK_INPUT_PTR + validate.TWEAK_SIZE +); +const HASH_INPUT = WASM_BUFFER.subarray( + WASM_HASH_INPUT_PTR, + WASM_HASH_INPUT_PTR + validate.HASH_SIZE +); +const EXTRA_DATA_INPUT = WASM_BUFFER.subarray( + WASM_EXTRA_DATA_INPUT_PTR, + WASM_EXTRA_DATA_INPUT_PTR + validate.EXTRA_DATA_SIZE +); +const SIGNATURE_INPUT = WASM_BUFFER.subarray( + WASM_SIGNATURE_INPUT_PTR, + WASM_SIGNATURE_INPUT_PTR + validate.SIGNATURE_SIZE +); + +export default createApi({ + isPoint(p) { + try { + PUBLIC_KEY_INPUT.set(p); + return wasm.isPoint(p.length) === 1; + } finally { + PUBLIC_KEY_INPUT.fill(0); + } + }, + + pointAdd(pA, pB, outputlen) { + try { + PUBLIC_KEY_INPUT.set(pA); + PUBLIC_KEY_INPUT2.set(pB); + return wasm.pointAdd(pA.length, pB.length, outputlen) === 1 + ? PUBLIC_KEY_INPUT.slice(0, outputlen) + : null; + } finally { + PUBLIC_KEY_INPUT.fill(0); + PUBLIC_KEY_INPUT2.fill(0); + } + }, + + pointAddScalar(p, tweak, outputlen) { + try { + PUBLIC_KEY_INPUT.set(p); + TWEAK_INPUT.set(tweak); + return wasm.pointAddScalar(p.length, outputlen) === 1 + ? PUBLIC_KEY_INPUT.slice(0, outputlen) + : null; + } finally { + PUBLIC_KEY_INPUT.fill(0); + TWEAK_INPUT.fill(0); + } + }, + + pointCompress(p, outputlen) { + try { + PUBLIC_KEY_INPUT.set(p); + wasm.pointCompress(p.length, outputlen); + return PUBLIC_KEY_INPUT.slice(0, outputlen); + } finally { + PUBLIC_KEY_INPUT.fill(0); + } + }, + + pointFromScalar(d, outputlen) { + try { + PRIVATE_KEY_INPUT.set(d); + return wasm.pointFromScalar(outputlen) === 1 + ? PUBLIC_KEY_INPUT.slice(0, outputlen) + : null; + } finally { + PRIVATE_KEY_INPUT.fill(0); + PUBLIC_KEY_INPUT.fill(0); + } + }, + + pointMultiply(p, tweak, outputlen) { + try { + PUBLIC_KEY_INPUT.set(p); + TWEAK_INPUT.set(tweak); + return wasm.pointMultiply(p.length, outputlen) === 1 + ? PUBLIC_KEY_INPUT.slice(0, outputlen) + : null; + } finally { + PUBLIC_KEY_INPUT.fill(0); + TWEAK_INPUT.fill(0); + } + }, + + privateAdd(d, tweak) { + try { + PRIVATE_KEY_INPUT.set(d); + TWEAK_INPUT.set(tweak); + return wasm.privateAdd() === 1 + ? PRIVATE_KEY_INPUT.slice(0, validate.PRIVATE_KEY_SIZE) + : null; + } finally { + PRIVATE_KEY_INPUT.fill(0); + TWEAK_INPUT.fill(0); + } + }, + + privateSub(d, tweak) { + try { + PRIVATE_KEY_INPUT.set(d); + TWEAK_INPUT.set(tweak); + return wasm.privateSub() === 1 + ? PRIVATE_KEY_INPUT.slice(0, validate.PRIVATE_KEY_SIZE) + : null; + } finally { + PRIVATE_KEY_INPUT.fill(0); + TWEAK_INPUT.fill(0); + } + }, + + signWithEntropy(h, d, e) { + try { + HASH_INPUT.set(h); + PRIVATE_KEY_INPUT.set(d); + if (e !== undefined) EXTRA_DATA_INPUT.set(e); + wasm.sign(e === undefined ? 0 : 1); + return SIGNATURE_INPUT.slice(0, validate.SIGNATURE_SIZE); + } finally { + HASH_INPUT.fill(0); + PRIVATE_KEY_INPUT.fill(0); + if (e !== undefined) EXTRA_DATA_INPUT.fill(0); + SIGNATURE_INPUT.fill(0); + } + }, + + verify(h, Q, signature, strict) { + try { + HASH_INPUT.set(h); + PUBLIC_KEY_INPUT.set(Q); + SIGNATURE_INPUT.set(signature); + return wasm.verify(Q.length, strict) === 1 ? true : false; + } finally { + HASH_INPUT.fill(0); + PUBLIC_KEY_INPUT.fill(0); + SIGNATURE_INPUT.fill(0); + } + }, +}); diff --git a/lib/secp256k1.browser.js b/lib/wasm_loader.browser.js similarity index 100% rename from lib/secp256k1.browser.js rename to lib/wasm_loader.browser.js diff --git a/lib/secp256k1.js b/lib/wasm_loader.js similarity index 65% rename from lib/secp256k1.js rename to lib/wasm_loader.js index 0fb1c28..ce04658 100644 --- a/lib/secp256k1.js +++ b/lib/wasm_loader.js @@ -1,12 +1,12 @@ import { readFileSync } from "fs"; import { URL } from "url"; -import * as wasm_error from "./wasm_error.js"; -import * as wasm_rand from "./wasm_rand.js"; +import * as rand from "./rand.js"; +import * as validate_error from "./validate_error.js"; const binary = readFileSync(new URL("secp256k1.wasm", import.meta.url)); const imports = { - "./wasm_error.js": wasm_error, - "./wasm_rand.js": wasm_rand, + "./rand.js": rand, + "./validate_error.js": validate_error, }; const mod = new WebAssembly.Module(binary); diff --git a/package.json b/package.json index 22c5f4b..f0c93ab 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,9 @@ "type": "module", "main": "./lib/index.js", "browser": { - "./lib/secp256k1.js": "./lib/secp256k1.browser.js", - "./lib/wasm_rand.js": "./lib/wasm_rand.browser.js" + "./lib/index.js": "./lib/index.browser.js", + "./lib/rand.js": "./lib/rand.browser.js", + "./lib/wasm_loader.js": "./lib/wasm_loader.browser.js" }, "devDependencies": { "binaryen": "^100.0.0", diff --git a/secp256k1-node/Cargo.toml b/secp256k1-node/Cargo.toml new file mode 100644 index 0000000..88df211 --- /dev/null +++ b/secp256k1-node/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "secp256k1-node" +version = "0.0.0" +authors = ["Kirill Fomichev "] +edition = "2018" +description = "A Rust library for building tiny-secp256k1 Node.js addon." +license = "MIT" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +lazy_static = "1.4.0" +napi = "1.3.2" +napi-derive = "1.0" +secp256k1 = { path = "../secp256k1", features = ["std"] } + +[build-dependencies] +napi-build = "1.0" diff --git a/secp256k1-node/build.rs b/secp256k1-node/build.rs new file mode 100644 index 0000000..0f1b010 --- /dev/null +++ b/secp256k1-node/build.rs @@ -0,0 +1,3 @@ +fn main() { + napi_build::setup(); +} diff --git a/secp256k1-node/src/lib.rs b/secp256k1-node/src/lib.rs new file mode 100644 index 0000000..acda0b8 --- /dev/null +++ b/secp256k1-node/src/lib.rs @@ -0,0 +1,349 @@ +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate napi_derive; + +use napi::{ + CallContext, ContextlessResult, Env, Error as NapiError, JsArrayBufferValue, JsBoolean, + JsFunction, JsNumber, JsObject, JsTypedArray, JsTypedArrayValue, JsUnknown, Ref, + Result as NapiResult, Status, TypedArrayType, +}; +use secp256k1::{ + c_void, pubkey_parse, pubkey_serialize, secp256k1_context_no_precomp, + secp256k1_context_preallocated_create, secp256k1_context_preallocated_size, + secp256k1_context_randomize, secp256k1_ec_pubkey_combine, secp256k1_ec_pubkey_create, + secp256k1_ec_pubkey_tweak_add, secp256k1_ec_pubkey_tweak_mul, secp256k1_ec_seckey_negate, + secp256k1_ec_seckey_tweak_add, secp256k1_ecdsa_sign, secp256k1_ecdsa_signature_normalize, + secp256k1_ecdsa_signature_parse_compact, secp256k1_ecdsa_signature_serialize_compact, + secp256k1_ecdsa_verify, secp256k1_nonce_function_rfc6979, Context, PublicKey, Signature, + ERROR_BAD_SIGNATURE, SECP256K1_START_SIGN, SECP256K1_START_VERIFY, SIGNATURE_SIZE, +}; +use std::{ + alloc, + convert::TryFrom, + sync::{Mutex, Once}, +}; + +lazy_static! { + static ref THROW_ERROR_FUNC: Mutex>> = Mutex::new(None); + static ref GENERATE_SEED_FUNC: Mutex>> = Mutex::new(None); +} + +macro_rules! throw_error { + ($ctx:expr, $code:expr) => {{ + let env = $ctx.env; + let throw_error_locked = THROW_ERROR_FUNC.lock().unwrap(); + let throw_error_ref = throw_error_locked.as_ref().expect("should be defined"); + let throw_error = env.get_reference_value::(throw_error_ref)?; + let code = env.create_uint32($code as u32)?.into_unknown(); + return match throw_error.call(None, &[code]) { + Ok(result) => { + let value_type = result.get_type()?; + let reason = format!("Invalid function result: {}", value_type); + Err(NapiError::from_reason(reason)) + } + Err(error) if error.status == Status::PendingException => { + Ok(env.get_undefined().unwrap().into_unknown()) + } + Err(error) => { + let reason = format!("Invalid function status: {}", error.status); + Err(NapiError::from_reason(reason)) + } + }; + }}; +} + +macro_rules! get_pubkey { + ($ctx:expr, $index:expr) => {{ + let p = get_typed_array(&$ctx, $index)?; + match unsafe { pubkey_parse(p.as_ptr(), p.len()) } { + Ok(value) => value, + Err(code) => { + throw_error!($ctx, code) + } + } + }}; +} + +fn get_typed_array(ctx: &CallContext, index: usize) -> NapiResult { + ctx.get::(index)?.into_value() +} + +fn get_number(ctx: &CallContext, index: usize) -> NapiResult { + Ok(ctx.get::(index)?.get_uint32()? as usize) +} + +fn get_output_buffer(ctx: &CallContext, index: usize) -> NapiResult { + let outputlen = get_number(ctx, index)?; + ctx.env.create_arraybuffer_with_data(vec![0; outputlen]) +} + +fn buffer2unknown(buffer: JsArrayBufferValue) -> NapiResult { + let length = buffer.len(); + let buffer = buffer.into_raw(); + let typedarray = buffer.into_typedarray(TypedArrayType::Uint8, length, 0)?; + Ok(typedarray.into_unknown()) +} + +fn get_context(env: &Env) -> *const Context { + static mut CONTEXT: Option<*const Context> = None; + static ONCE: Once = Once::new(); + ONCE.call_once(|| unsafe { + // Create + let flags = SECP256K1_START_SIGN | SECP256K1_START_VERIFY; + let size = secp256k1_context_preallocated_size(flags); + let layout = alloc::Layout::from_size_align(size, 16).unwrap(); + let ptr = alloc::alloc(layout); + let ctx = secp256k1_context_preallocated_create(ptr as *mut c_void, flags); + // Randomize + let generate_seed_locked = GENERATE_SEED_FUNC.lock().unwrap(); + let generate_seed_ref = generate_seed_locked.as_ref().expect("should be defined"); + let generated_seed = env + .get_reference_value::(generate_seed_ref) + .expect("wrong type") + .call(None, &[]) + .expect("failed to generate seed"); + let seed = JsTypedArray::try_from(generated_seed) + .expect("generated seed is not a TypedArray") + .into_value() + .expect("failed to get seed bytes"); + assert_eq!(secp256k1_context_randomize(ctx, seed.as_ptr()), 1); + // Save + CONTEXT = Some(ctx); + }); + unsafe { *CONTEXT.as_ref().expect("should be initialized") } +} + +#[module_exports] +fn init(mut exports: JsObject, env: Env) -> NapiResult<()> { + let prop = exports.get_named_property::("throwError")?; + let prop_ref = env.create_reference(prop)?; + THROW_ERROR_FUNC.lock().unwrap().replace(prop_ref); + + let prop = exports.get_named_property::("generateSeed")?; + let prop_ref = env.create_reference(prop)?; + GENERATE_SEED_FUNC.lock().unwrap().replace(prop_ref); + + exports.create_named_method("initializeContext", initialize_context)?; + exports.create_named_method("isPoint", is_point)?; + exports.create_named_method("pointAdd", point_add)?; + exports.create_named_method("pointAddScalar", point_add_scalar)?; + exports.create_named_method("pointCompress", point_compress)?; + exports.create_named_method("pointFromScalar", point_from_scalar)?; + exports.create_named_method("pointMultiply", point_multiply)?; + exports.create_named_method("privateAdd", private_add)?; + exports.create_named_method("privateSub", private_sub)?; + exports.create_named_method("signWithEntropy", sign_with_entropy)?; + exports.create_named_method("verify", verify)?; + + Ok(()) +} + +#[contextless_function] +fn initialize_context(env: Env) -> ContextlessResult { + get_context(&env); + Ok(None) +} + +#[js_function(1)] +fn is_point(ctx: CallContext) -> NapiResult { + let p = get_typed_array(&ctx, 0)?; + let is_ok = unsafe { pubkey_parse(p.as_ptr(), p.len()).is_ok() }; + ctx.env.get_boolean(is_ok) +} + +#[js_function(3)] +fn point_add(ctx: CallContext) -> NapiResult { + let pk1 = get_pubkey!(ctx, 0); + let pk2 = get_pubkey!(ctx, 1); + + unsafe { + let mut pk = PublicKey::new(); + let ptrs = [pk1.as_ptr(), pk2.as_ptr()]; + if secp256k1_ec_pubkey_combine( + secp256k1_context_no_precomp, + &mut pk, + ptrs.as_ptr() as *const *const PublicKey, + ptrs.len() as i32, + ) == 1 + { + let mut output = get_output_buffer(&ctx, 2)?; + pubkey_serialize(&pk, output.as_mut_ptr(), output.len()); + buffer2unknown(output) + } else { + Ok(ctx.env.get_null()?.into_unknown()) + } + } +} + +#[js_function(3)] +fn point_add_scalar(ctx: CallContext) -> NapiResult { + let mut pk = get_pubkey!(ctx, 0); + let tweak = get_typed_array(&ctx, 1)?; + unsafe { + if secp256k1_ec_pubkey_tweak_add( + get_context(&ctx.env), + pk.as_mut_ptr() as *mut PublicKey, + tweak.as_ptr(), + ) == 1 + { + let mut output = get_output_buffer(&ctx, 2)?; + pubkey_serialize(&pk, output.as_mut_ptr(), output.len()); + buffer2unknown(output) + } else { + Ok(ctx.env.get_null()?.into_unknown()) + } + } +} + +#[js_function(2)] +fn point_compress(ctx: CallContext) -> NapiResult { + let pk = get_pubkey!(ctx, 0); + let mut output = get_output_buffer(&ctx, 1)?; + unsafe { + pubkey_serialize(&pk, output.as_mut_ptr(), output.len()); + } + buffer2unknown(output) +} + +#[js_function(2)] +fn point_from_scalar(ctx: CallContext) -> NapiResult { + let d = get_typed_array(&ctx, 0)?; + unsafe { + let mut pk = PublicKey::new(); + if secp256k1_ec_pubkey_create(get_context(&ctx.env), &mut pk, d.as_ptr()) == 1 { + let mut output = get_output_buffer(&ctx, 1)?; + pubkey_serialize(&pk, output.as_mut_ptr(), output.len()); + buffer2unknown(output) + } else { + Ok(ctx.env.get_null()?.into_unknown()) + } + } +} + +#[js_function(3)] +fn point_multiply(ctx: CallContext) -> NapiResult { + let mut pk = get_pubkey!(ctx, 0); + let tweak = get_typed_array(&ctx, 1)?; + unsafe { + if secp256k1_ec_pubkey_tweak_mul(get_context(&ctx.env), &mut pk, tweak.as_ptr()) == 1 { + let mut output = get_output_buffer(&ctx, 2)?; + pubkey_serialize(&pk, output.as_mut_ptr(), output.len()); + buffer2unknown(output) + } else { + Ok(ctx.env.get_null()?.into_unknown()) + } + } +} + +#[js_function(2)] +fn private_add(ctx: CallContext) -> NapiResult { + let d = get_typed_array(&ctx, 0)?; + let tweak = get_typed_array(&ctx, 1)?; + let mut d = ctx.env.create_arraybuffer_with_data(d.to_vec())?; + unsafe { + if secp256k1_ec_seckey_tweak_add( + secp256k1_context_no_precomp, + d.as_mut_ptr(), + tweak.as_ptr(), + ) == 1 + { + buffer2unknown(d) + } else { + Ok(ctx.env.get_null()?.into_unknown()) + } + } +} + +#[js_function(2)] +fn private_sub(ctx: CallContext) -> NapiResult { + let d = get_typed_array(&ctx, 0)?; + let tweak = get_typed_array(&ctx, 1)?; + let mut d = ctx.env.create_arraybuffer_with_data(d.to_vec())?; + let mut tweak = ctx.env.create_arraybuffer_with_data(tweak.to_vec())?; + unsafe { + assert_eq!( + secp256k1_ec_seckey_negate(secp256k1_context_no_precomp, tweak.as_mut_ptr()), + 1 + ); + if secp256k1_ec_seckey_tweak_add( + secp256k1_context_no_precomp, + d.as_mut_ptr(), + tweak.as_ptr(), + ) == 1 + { + buffer2unknown(d) + } else { + Ok(ctx.env.get_null()?.into_unknown()) + } + } +} + +#[js_function(3)] +fn sign_with_entropy(ctx: CallContext) -> NapiResult { + let h = get_typed_array(&ctx, 0)?; + let d = get_typed_array(&ctx, 1)?; + let e = ctx.get::(2)?; + + unsafe { + let mut sig = Signature::new(); + let noncedata = match JsTypedArray::try_from(e) { + Ok(e) => e.into_value()?.as_ptr(), + Err(_) => core::ptr::null(), + } as *const c_void; + + assert_eq!( + secp256k1_ecdsa_sign( + get_context(&ctx.env), + &mut sig, + h.as_ptr(), + d.as_ptr(), + secp256k1_nonce_function_rfc6979, + noncedata + ), + 1 + ); + + let data = vec![0; SIGNATURE_SIZE]; + let mut signature = ctx.env.create_arraybuffer_with_data(data)?; + + assert_eq!( + secp256k1_ecdsa_signature_serialize_compact( + secp256k1_context_no_precomp, + signature.as_mut_ptr(), + &sig, + ), + 1 + ); + + let buffer = signature.into_raw(); + buffer.into_typedarray(TypedArrayType::Uint8, SIGNATURE_SIZE, 0) + } +} + +#[js_function(4)] +fn verify(ctx: CallContext) -> NapiResult { + let h = get_typed_array(&ctx, 0)?; + let pk = get_pubkey!(ctx, 1); + let signature = get_typed_array(&ctx, 2)?; + let strict = get_number(&ctx, 3)?; + + unsafe { + let mut sig = Signature::new(); + if secp256k1_ecdsa_signature_parse_compact( + secp256k1_context_no_precomp, + &mut sig, + signature.as_ptr(), + ) == 0 + { + throw_error!(ctx, ERROR_BAD_SIGNATURE); + } + + if strict == 0 { + secp256k1_ecdsa_signature_normalize(secp256k1_context_no_precomp, &mut sig, &sig); + } + + let retcode = secp256k1_ecdsa_verify(get_context(&ctx.env), &sig, h.as_ptr(), &pk); + Ok(ctx.env.get_boolean(retcode == 1)?.into_unknown()) + } +} diff --git a/secp256k1-wasm/Cargo.toml b/secp256k1-wasm/Cargo.toml index b6c3ba2..97f00f3 100644 --- a/secp256k1-wasm/Cargo.toml +++ b/secp256k1-wasm/Cargo.toml @@ -11,5 +11,4 @@ publish = false crate-type = ["cdylib"] [dependencies] -# `[patch.crates-io]` is not working :( -secp256k1-sys = { version = "0.4.0", default-features = false, git = "https://github.com/fanatid/rust-secp256k1", branch = "more-features" } +secp256k1 = { path = "../secp256k1" } diff --git a/secp256k1-wasm/src/lib.rs b/secp256k1-wasm/src/lib.rs index 3bba7ae..52d8405 100644 --- a/secp256k1-wasm/src/lib.rs +++ b/secp256k1-wasm/src/lib.rs @@ -1,53 +1,33 @@ #![no_std] -#![feature(core_intrinsics)] - -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - core::intrinsics::abort() -} #[cfg(not(target_arch = "wasm32"))] compile_error!("Only `wasm32` target_arch is supported."); -use secp256k1_sys::{ - secp256k1_context_no_precomp, secp256k1_context_preallocated_create, - secp256k1_context_preallocated_size, secp256k1_context_randomize, secp256k1_ec_pubkey_combine, - secp256k1_ec_pubkey_create, secp256k1_ec_pubkey_parse, secp256k1_ec_pubkey_serialize, +use secp256k1::{ + c_void, pubkey_parse, pubkey_serialize, secp256k1_context_no_precomp, + secp256k1_context_preallocated_create, secp256k1_context_preallocated_size, + secp256k1_context_randomize, secp256k1_ec_pubkey_combine, secp256k1_ec_pubkey_create, secp256k1_ec_pubkey_tweak_add, secp256k1_ec_pubkey_tweak_mul, secp256k1_ec_seckey_negate, secp256k1_ec_seckey_tweak_add, secp256k1_ecdsa_sign, secp256k1_ecdsa_signature_normalize, secp256k1_ecdsa_signature_parse_compact, secp256k1_ecdsa_signature_serialize_compact, - secp256k1_ecdsa_verify, secp256k1_nonce_function_rfc6979, types::c_void, Context, PublicKey, - Signature, SECP256K1_SER_COMPRESSED, SECP256K1_SER_UNCOMPRESSED, SECP256K1_START_SIGN, - SECP256K1_START_VERIFY, + secp256k1_ecdsa_verify, secp256k1_nonce_function_rfc6979, Context, PublicKey, Signature, + ERROR_BAD_SIGNATURE, EXTRA_DATA_SIZE, HASH_SIZE, PRIVATE_KEY_SIZE, + PUBLIC_KEY_UNCOMPRESSED_SIZE, SECP256K1_START_SIGN, SECP256K1_START_VERIFY, SIGNATURE_SIZE, + TWEAK_SIZE, }; -#[link(wasm_import_module = "./wasm_error.js")] +#[link(wasm_import_module = "./validate_error.js")] extern "C" { #[link_name = "throwError"] fn throw_error(errcode: usize); } -#[link(wasm_import_module = "./wasm_rand.js")] +#[link(wasm_import_module = "./rand.js")] extern "C" { #[link_name = "generateInt32"] fn generate_int32() -> i32; } -const PRIVATE_KEY_SIZE: usize = 32; -const PUBLIC_KEY_COMPRESSED_SIZE: usize = 33; -const PUBLIC_KEY_UNCOMPRESSED_SIZE: usize = 65; -const TWEAK_SIZE: usize = 32; -const HASH_SIZE: usize = 32; -const EXTRA_DATA_SIZE: usize = 32; -const SIGNATURE_SIZE: usize = 64; - -// const ERROR_BAD_PRIVATE: usize = 0; -const ERROR_BAD_POINT: usize = 1; -// const ERROR_BAD_TWEAK: usize = 2; -// const ERROR_BAD_HASH: usize = 3; -const ERROR_BAD_SIGNATURE: usize = 4; -// const ERROR_BAD_EXTRA_DATA: usize = 5; - static CONTEXT_BUFFER: [u8; 1114320] = [0; 1114320]; static mut CONTEXT_SEED: [u8; 32] = [0; 32]; @@ -68,12 +48,7 @@ pub static EXTRA_DATA_INPUT: [u8; EXTRA_DATA_SIZE] = [0; EXTRA_DATA_SIZE]; #[no_mangle] pub static mut SIGNATURE_INPUT: [u8; SIGNATURE_SIZE] = [0; SIGNATURE_SIZE]; -type InvalidInputResult = Result; - macro_rules! jstry { - ($value:expr) => { - jstry!($value, ()) - }; ($value:expr, $ret:expr) => { match $value { Ok(value) => value, @@ -117,33 +92,6 @@ fn get_context() -> *const Context { } } -unsafe fn pubkey_parse(input: *const u8, inputlen: usize) -> InvalidInputResult { - let mut pk = PublicKey::new(); - if secp256k1_ec_pubkey_parse(secp256k1_context_no_precomp, &mut pk, input, inputlen) == 1 { - Ok(pk) - } else { - Err(ERROR_BAD_POINT) - } -} - -unsafe fn pubkey_serialize(pk: &PublicKey, output: *mut u8, mut outputlen: usize) { - let flags = if outputlen == PUBLIC_KEY_COMPRESSED_SIZE { - SECP256K1_SER_COMPRESSED - } else { - SECP256K1_SER_UNCOMPRESSED - }; - assert_eq!( - secp256k1_ec_pubkey_serialize( - secp256k1_context_no_precomp, - output, - &mut outputlen, - pk.as_ptr() as *const PublicKey, - flags, - ), - 1 - ); -} - #[no_mangle] #[export_name = "initializeContext"] pub extern "C" fn initialize_context() { @@ -202,7 +150,7 @@ pub extern "C" fn point_add_scalar(inputlen: usize, outputlen: usize) -> i32 { #[export_name = "pointCompress"] pub extern "C" fn point_compress(inputlen: usize, outputlen: usize) { unsafe { - let pk = jstry!(pubkey_parse(PUBLIC_KEY_INPUT.as_ptr(), inputlen)); + let pk = jstry!(pubkey_parse(PUBLIC_KEY_INPUT.as_ptr(), inputlen), ()); pubkey_serialize(&pk, PUBLIC_KEY_INPUT.as_mut_ptr(), outputlen); } } diff --git a/secp256k1/Cargo.toml b/secp256k1/Cargo.toml new file mode 100644 index 0000000..5e1fa19 --- /dev/null +++ b/secp256k1/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "secp256k1" +version = "0.0.0" +authors = ["Kirill Fomichev "] +edition = "2018" +description = "A secp256k1-sys wrapper for building tiny-secp256k1." +license = "MIT" +publish = false + +[dependencies] +# `[patch.crates-io]` is not working :( +# `more-features` branch add features: ecdh, extrakeys, schnorrsig. +# This reuqired because LTO do not strip not used functions in resulted WASM file. +# secp256k1-sys PR: https://github.com/rust-bitcoin/rust-secp256k1/pull/287 +secp256k1-sys = { version = "0.4.0", default-features = false, git = "https://github.com/fanatid/rust-secp256k1", branch = "more-features" } + +[features] +std = [] diff --git a/secp256k1/src/lib.rs b/secp256k1/src/lib.rs new file mode 100644 index 0000000..e59e190 --- /dev/null +++ b/secp256k1/src/lib.rs @@ -0,0 +1,64 @@ +#![cfg_attr(not(any(test, feature = "std")), no_std)] +#![feature(core_intrinsics)] + +#[cfg(not(any(test, feature = "std")))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + core::intrinsics::abort() +} + +pub use secp256k1_sys::{ + secp256k1_context_no_precomp, secp256k1_context_preallocated_create, + secp256k1_context_preallocated_size, secp256k1_context_randomize, secp256k1_ec_pubkey_combine, + secp256k1_ec_pubkey_create, secp256k1_ec_pubkey_parse, secp256k1_ec_pubkey_serialize, + secp256k1_ec_pubkey_tweak_add, secp256k1_ec_pubkey_tweak_mul, secp256k1_ec_seckey_negate, + secp256k1_ec_seckey_tweak_add, secp256k1_ecdsa_sign, secp256k1_ecdsa_signature_normalize, + secp256k1_ecdsa_signature_parse_compact, secp256k1_ecdsa_signature_serialize_compact, + secp256k1_ecdsa_verify, secp256k1_nonce_function_rfc6979, types::c_void, Context, PublicKey, + Signature, SECP256K1_START_SIGN, SECP256K1_START_VERIFY, +}; +use secp256k1_sys::{SECP256K1_SER_COMPRESSED, SECP256K1_SER_UNCOMPRESSED}; + +pub const PRIVATE_KEY_SIZE: usize = 32; +pub const PUBLIC_KEY_COMPRESSED_SIZE: usize = 33; +pub const PUBLIC_KEY_UNCOMPRESSED_SIZE: usize = 65; +pub const TWEAK_SIZE: usize = 32; +pub const HASH_SIZE: usize = 32; +pub const EXTRA_DATA_SIZE: usize = 32; +pub const SIGNATURE_SIZE: usize = 64; + +// pub const ERROR_BAD_PRIVATE: usize = 0; +pub const ERROR_BAD_POINT: usize = 1; +// pub const ERROR_BAD_TWEAK: usize = 2; +// pub const ERROR_BAD_HASH: usize = 3; +pub const ERROR_BAD_SIGNATURE: usize = 4; +// pub const ERROR_BAD_EXTRA_DATA: usize = 5; + +type InvalidInputResult = Result; + +pub unsafe fn pubkey_parse(input: *const u8, inputlen: usize) -> InvalidInputResult { + let mut pk = PublicKey::new(); + if secp256k1_ec_pubkey_parse(secp256k1_context_no_precomp, &mut pk, input, inputlen) == 1 { + Ok(pk) + } else { + Err(ERROR_BAD_POINT) + } +} + +pub unsafe fn pubkey_serialize(pk: &PublicKey, output: *mut u8, mut outputlen: usize) { + let flags = if outputlen == PUBLIC_KEY_COMPRESSED_SIZE { + SECP256K1_SER_COMPRESSED + } else { + SECP256K1_SER_UNCOMPRESSED + }; + assert_eq!( + secp256k1_ec_pubkey_serialize( + secp256k1_context_no_precomp, + output, + &mut outputlen, + pk.as_ptr() as *const PublicKey, + flags, + ), + 1 + ); +} diff --git a/tests/browser.webpack.js b/tests/browser.webpack.js index 223abd4..e9a2578 100644 --- a/tests/browser.webpack.js +++ b/tests/browser.webpack.js @@ -17,7 +17,7 @@ export default { }, plugins: [ new webpack.ProvidePlugin({ - process: "process/browser", + process: "process/browser.js", }), ], resolve: { diff --git a/tests/ecdsa.js b/tests/ecdsa.js index a4dd866..710f22b 100644 --- a/tests/ecdsa.js +++ b/tests/ecdsa.js @@ -29,7 +29,7 @@ function corrupt(x) { return x; } -export default function (binding) { +export default function (secp256k1) { test("sign", (t) => { for (const f of fecdsa.valid) { const d = fromHex(f.d); @@ -37,7 +37,7 @@ export default function (binding) { const expected = fromHex(f.signature); t.same( - binding.sign(m, d), + secp256k1.sign(m, d), expected, `sign(${f.m}, ...) == ${f.signature}` ); @@ -53,14 +53,14 @@ export default function (binding) { const expectedExtraEntropyN = fromHex(f.extraEntropyN); const expectedExtraEntropyMax = fromHex(f.extraEntropyMax); - const sig = binding.sign(m, d); + const sig = secp256k1.sign(m, d); - const extraEntropyUndefined = binding.signWithEntropy(m, d, undefined); - const extraEntropy0 = binding.signWithEntropy(m, d, buf1); - const extraEntropy1 = binding.signWithEntropy(m, d, buf2); - const extraEntropyRand = binding.signWithEntropy(m, d, buf3); - const extraEntropyN = binding.signWithEntropy(m, d, buf4); - const extraEntropyMax = binding.signWithEntropy(m, d, buf5); + const extraEntropyUndefined = secp256k1.signWithEntropy(m, d, undefined); + const extraEntropy0 = secp256k1.signWithEntropy(m, d, buf1); + const extraEntropy1 = secp256k1.signWithEntropy(m, d, buf2); + const extraEntropyRand = secp256k1.signWithEntropy(m, d, buf3); + const extraEntropyN = secp256k1.signWithEntropy(m, d, buf4); + const extraEntropyMax = secp256k1.signWithEntropy(m, d, buf5); t.same(sig, expectedSig, `sign(${f.m}, ...) == ${f.signature}`); t.same( @@ -101,7 +101,7 @@ export default function (binding) { t.throws( () => { - binding.sign(m, d); + secp256k1.sign(m, d); }, new RegExp(f.exception), `${f.description} throws ${f.exception}` @@ -114,29 +114,29 @@ export default function (binding) { test("verify", (t) => { for (const f of fecdsa.valid) { const d = fromHex(f.d); - const Q = binding.pointFromScalar(d, true); - const Qu = binding.pointFromScalar(d, false); + const Q = secp256k1.pointFromScalar(d, true); + const Qu = secp256k1.pointFromScalar(d, false); const m = fromHex(f.m); const signature = fromHex(f.signature); const bad = corrupt(signature); t.equal( - binding.verify(m, Q, signature), + secp256k1.verify(m, Q, signature), true, `verify(${f.signature}) is OK` ); t.equal( - binding.verify(m, Q, bad), + secp256k1.verify(m, Q, bad), false, `verify(${toHex(bad)}) is rejected` ); t.equal( - binding.verify(m, Qu, signature), + secp256k1.verify(m, Qu, signature), true, `verify(${f.signature}) is OK` ); t.equal( - binding.verify(m, Qu, bad), + secp256k1.verify(m, Qu, bad), false, `verify(${toHex(bad)}) is rejected` ); @@ -150,20 +150,20 @@ export default function (binding) { if (f.exception) { t.throws( () => { - binding.verify(m, Q, signature); + secp256k1.verify(m, Q, signature); }, new RegExp(f.exception), `${f.description} throws ${f.exception}` ); } else { t.equal( - binding.verify(m, Q, signature, f.strict), + secp256k1.verify(m, Q, signature, f.strict), false, `verify(${f.signature}) is rejected` ); if (f.strict === true) { t.equal( - binding.verify(m, Q, signature, false), + secp256k1.verify(m, Q, signature, false), true, `verify(${f.signature}) is OK without strict` ); diff --git a/tests/index.js b/tests/index.js index 2e0c00d..e5a48f4 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,17 +1,51 @@ import { test } from "tape"; -import * as wasm from "../lib/index.js"; +import * as secp256k1 from "../lib/index.js"; import test_ecdsa from "./ecdsa.js"; import test_points from "./points.js"; import test_privates from "./privates.js"; -test_ecdsa(wasm); -test_points(wasm); -test_privates(wasm); - // Closing browser if launched through `browser-run`. test.onFinish(() => { - try { + if (process.browser) { window.close(); - } catch (_err) {} + } +}); + +function runTests(secp256k1) { + if (secp256k1 !== null) { + test_ecdsa(secp256k1); + test_points(secp256k1); + test_privates(secp256k1); + } +} + +runTests(secp256k1.__addon); +runTests(secp256k1.__wasm); + +test("functions exported properly", (t) => { + const fnList = [ + "isPoint", + "isPointCompressed", + "isPrivate", + "pointAdd", + "pointAddScalar", + "pointCompress", + "pointFromScalar", + "pointMultiply", + "privateAdd", + "privateSub", + "sign", + "signWithEntropy", + "verify", + ]; + const source = + secp256k1.__initializeContext === secp256k1.__wasm.__initializeContext + ? secp256k1.__wasm + : secp256k1.__addon; + for (const fnName of fnList) { + t.equal(secp256k1[fnName], source[fnName]); + } + + t.end(); }); diff --git a/tests/points.js b/tests/points.js index 027f4d4..d5ec2a9 100644 --- a/tests/points.js +++ b/tests/points.js @@ -2,12 +2,12 @@ import { test } from "tape"; import { fromHex } from "./util.js"; import fpoints from "./fixtures/points.json"; -export default function (binding) { +export default function (secp256k1) { test("isPoint", (t) => { for (const f of fpoints.valid.isPoint) { const p = fromHex(f.P); t.equal( - binding.isPoint(p), + secp256k1.isPoint(p), f.expected, `${f.P} is ${f.expected ? "OK" : "rejected"}` ); @@ -22,7 +22,7 @@ export default function (binding) { const p = fromHex(f.P); const e = p.length === 33; t.equal( - binding.isPointCompressed(p), + secp256k1.isPointCompressed(p), e, `${f.P} is ${e ? "compressed" : "uncompressed"}` ); @@ -38,16 +38,16 @@ export default function (binding) { const expected = f.expected ? fromHex(f.expected) : null; let description = `${f.P} + ${f.Q} = ${f.expected ? f.expected : null}`; if (f.description) description += ` (${f.description})`; - t.same(binding.pointAdd(p, q), expected, description); + t.same(secp256k1.pointAdd(p, q), expected, description); if (expected === null) continue; t.same( - binding.pointAdd(p, q, true), - binding.pointCompress(expected, true), + secp256k1.pointAdd(p, q, true), + secp256k1.pointCompress(expected, true), description ); t.same( - binding.pointAdd(p, q, false), - binding.pointCompress(expected, false), + secp256k1.pointAdd(p, q, false), + secp256k1.pointCompress(expected, false), description ); } @@ -57,7 +57,7 @@ export default function (binding) { const q = fromHex(f.Q); t.throws( () => { - binding.pointAdd(p, q); + secp256k1.pointAdd(p, q); }, new RegExp(f.exception), `${f.description} throws ${f.exception}` @@ -74,16 +74,16 @@ export default function (binding) { const expected = f.expected ? fromHex(f.expected) : null; let description = `${f.P} + ${f.d} = ${f.expected ? f.expected : null}`; if (f.description) description += ` (${f.description})`; - t.same(binding.pointAddScalar(p, d), expected, description); + t.same(secp256k1.pointAddScalar(p, d), expected, description); if (expected === null) continue; t.same( - binding.pointAddScalar(p, d, true), - binding.pointCompress(expected, true), + secp256k1.pointAddScalar(p, d, true), + secp256k1.pointCompress(expected, true), description ); t.same( - binding.pointAddScalar(p, d, false), - binding.pointCompress(expected, false), + secp256k1.pointAddScalar(p, d, false), + secp256k1.pointCompress(expected, false), description ); } @@ -93,7 +93,7 @@ export default function (binding) { const d = fromHex(f.d); t.throws( () => { - binding.pointAddScalar(p, d); + secp256k1.pointAddScalar(p, d); }, new RegExp(f.exception), `${f.description} throws ${f.exception}` @@ -108,9 +108,9 @@ export default function (binding) { const p = fromHex(f.P); const expected = fromHex(f.expected); if (f.noarg) { - t.same(binding.pointCompress(p), expected); + t.same(secp256k1.pointCompress(p), expected); } else { - t.same(binding.pointCompress(p, f.compress), expected); + t.same(secp256k1.pointCompress(p, f.compress), expected); } } @@ -118,7 +118,7 @@ export default function (binding) { const p = fromHex(f.P); t.throws( () => { - binding.pointCompress(p); + secp256k1.pointCompress(p); }, new RegExp(f.exception), `${f.description} throws ${f.exception}` @@ -134,16 +134,16 @@ export default function (binding) { const expected = fromHex(f.expected); let description = `${f.d} * G = ${f.expected}`; if (f.description) description += ` (${f.description})`; - t.same(binding.pointFromScalar(d), expected, description); + t.same(secp256k1.pointFromScalar(d), expected, description); if (expected === null) continue; t.same( - binding.pointFromScalar(d, true), - binding.pointCompress(expected, true), + secp256k1.pointFromScalar(d, true), + secp256k1.pointCompress(expected, true), description ); t.same( - binding.pointFromScalar(d, false), - binding.pointCompress(expected, false), + secp256k1.pointFromScalar(d, false), + secp256k1.pointCompress(expected, false), description ); } @@ -152,7 +152,7 @@ export default function (binding) { const d = fromHex(f.d); t.throws( () => { - binding.pointFromScalar(d); + secp256k1.pointFromScalar(d); }, new RegExp(f.exception), `${f.description} throws ${f.exception}` @@ -169,16 +169,16 @@ export default function (binding) { const expected = f.expected ? fromHex(f.expected) : null; let description = `${f.P} * ${f.d} = ${f.expected ? f.expected : null}`; if (f.description) description += ` (${f.description})`; - t.same(binding.pointMultiply(p, d), expected, description); + t.same(secp256k1.pointMultiply(p, d), expected, description); if (expected === null) continue; t.same( - binding.pointMultiply(p, d, true), - binding.pointCompress(expected, true), + secp256k1.pointMultiply(p, d, true), + secp256k1.pointCompress(expected, true), description ); t.same( - binding.pointMultiply(p, d, false), - binding.pointCompress(expected, false), + secp256k1.pointMultiply(p, d, false), + secp256k1.pointCompress(expected, false), description ); } @@ -188,7 +188,7 @@ export default function (binding) { const d = fromHex(f.d); t.throws( () => { - binding.pointMultiply(p, d); + secp256k1.pointMultiply(p, d); }, new RegExp(f.exception), `${f.description} throws ${f.exception}` diff --git a/tests/privates.js b/tests/privates.js index 8d3d382..7c1d1e1 100644 --- a/tests/privates.js +++ b/tests/privates.js @@ -2,13 +2,13 @@ import { test } from "tape"; import { fromHex } from "./util.js"; import fprivates from "./fixtures/privates.json"; -export default function (binding) { +export default function (secp256k1) { test("isPrivate", (t) => { for (const f of fprivates.valid.isPrivate) { const d = fromHex(f.d); t.equal( - binding.isPrivate(d), + secp256k1.isPrivate(d), f.expected, `${f.d} is ${f.expected ? "OK" : "rejected"}` ); @@ -27,7 +27,7 @@ export default function (binding) { }`; if (f.description) description += ` (${f.description})`; - t.same(binding.privateAdd(d, tweak), expected, description); + t.same(secp256k1.privateAdd(d, tweak), expected, description); } for (const f of fprivates.invalid.privateAdd) { @@ -36,7 +36,7 @@ export default function (binding) { t.throws( () => { - binding.privateAdd(d, tweak); + secp256k1.privateAdd(d, tweak); }, new RegExp(f.exception), `${f.description} throws ${f.exception}` @@ -56,7 +56,7 @@ export default function (binding) { }`; if (f.description) description += ` (${f.description})`; - t.same(binding.privateSub(d, tweak), expected, description); + t.same(secp256k1.privateSub(d, tweak), expected, description); } for (const f of fprivates.invalid.privateSub) { @@ -65,7 +65,7 @@ export default function (binding) { t.throws( () => { - binding.privateSub(d, tweak); + secp256k1.privateSub(d, tweak); }, new RegExp(f.exception), `${f.description} throws ${f.exception}`