WebAssembly port of Ed25519 signatures and X25519 key exchange
npm i @hazae41/berith
Node Package π¦ β’ Deno Module π¦ β’ Next.js CodeSandbox πͺ£
- Ed25519 from Dalek (ed25519-dalek)
- X25519 from Dalek (ed25519-dalek)
- Reproducible building
- Pre-bundled and streamed
- Zero-copy memory slices
git clone https://github.com/hazae41/berith && cd berith && npm i && npm run bench:deno
cpu: Apple M1 Max
runtime: deno 1.30.3 (aarch64-apple-darwin)
file:///src/deno/bench/mod.bench.ts
benchmark time (avg) (min β¦ max) p75 p99 p995
---------------------------------------------------------------------- -----------------------------
@hazae41/berith (unserialized) 325.78 Β΅s/iter (316.04 Β΅s β¦ 491.04 Β΅s) 326.21 Β΅s 348.62 Β΅s 364.54 Β΅s
@hazae41/berith (serialized) 368.3 Β΅s/iter (359.12 Β΅s β¦ 537.71 Β΅s) 368.79 Β΅s 399.92 Β΅s 406.54 Β΅s
@noble/curves 0.7.0 1.9 ms/iter (1.73 ms β¦ 2.3 ms) 1.96 ms 2.26 ms 2.28 ms
summary
@hazae41/berith (unserialized)
1.13x faster than @hazae41/berith (serialized)
5.85x faster than @noble/curves 0.7.0
git clone https://github.com/hazae41/berith && cd berith && npm i && npm run bench:node
cpu: Apple M1 Max
runtime: node v18.12.1 (aarch64-apple-darwin)
ββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββ¬ββββββββββββββ
β (index) β average β minimum β maximum β
ββββββββββββββββββββββββββββββββββΌβββββββββββββββββββΌββββββββββββββΌββββββββββββββ€
β @hazae41/berith (unserialized) β '281.68 ΞΌs/iter' β '273.83 ΞΌs' β '875.92 ΞΌs' β
β @hazae41/berith (serialized) β '318.67 ΞΌs/iter' β '311.29 ΞΌs' β '938.87 ΞΌs' β
β @noble/curves 0.7.0 β '1.99 ms/iter' β '1.82 ms' β '5.91 ms' β
β supercop.wasm 5.0.1 β '187.96 ΞΌs/iter' β '179.21 ΞΌs' β '734.29 ΞΌs' β
β node:crypto (unserialized) β '152.67 ΞΌs/iter' β '144.96 ΞΌs' β '2.86 ms' β
β node:crypto (serialized) β '555.61 ΞΌs/iter' β '549.42 ΞΌs' β '1.20 ms' β
ββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββ΄ββββββββββββββ΄ββββββββββββββ
Summary
- @hazae41/berith (unserialized) is 1.13x faster than @hazae41/berith (serialized)
- @hazae41/berith (unserialized) is 7.06x faster than @noble/curves 0.7.0
- @hazae41/berith (unserialized) is 0.67x faster than supercop.wasm 5.0.1
- @hazae41/berith (unserialized) is 0.54x faster than node:crypto (unserialized)
- @hazae41/berith (unserialized) is 1.97x faster than node:crypto (serialized)
import { Berith, Ed25519SigningKey } from "@hazae41/berith"
// import { Berith, Ed25519SigningKey } from "https://deno.land/x/berith/src/deno/mod.ts"
// Wait for WASM to load
await Berith.initBundledOnce();
// Generate random private key
using privateKey = Ed25519SigningKey.random() // Ed25519SigningKey
// Get public key
using publicKey = privateKey.public() // Ed25519VerifyingKey
// Encode some message to sign as UTF-8
const data = new TextEncoder().encode("hello world") // Uint8Array
// Put data in memory
using mdata = new Berith.Memory(data) // Berith.Memory
// Sign data with private key
using signature = privateKey.sign(mdata) // Ed25519Signature
// Verify signature with public key
const verified = publicKey.verify(mdata, signature) // boolean
import { Berith, X25519StaticSecret } from "@hazae41/berith"
// import { Berith, X25519StaticSecret } from "https://deno.land/x/berith/src/deno/mod.ts"
// Wait for WASM to load
await Berith.initBundledOnce()
// Generate Alice's random private key
using alicePrivateKey = new X25519StaticSecret()
// Get Alice's public key
using alicePublicKey = alicePrivateKey.to_public()
// Generate Bob's random private key
using bobPrivateKey = new X25519StaticSecret()
// Get Bob's public key
using bobPublicKey = bobPrivateKey.to_public()
// Derive Alice's shared key from Bob's public key
using aliceSharedKey = alicePrivateKey.diffie_hellman(bobPublicKey)
// Derive Bob's shared key from Alice's public key
using bobSharedKey = bobPrivateKey.diffie_hellman(alicePublicKey)
You have to wrap Uint8Array into Memory in order to pass them to WebAssembly
function exampleFromBytes(bytes: Uint8Array) {
using memory = new Berith.Memory(bytes)
Berith.example(memory)
}
You have to get Uint8Array from Memory via either copy or view
function exampleWithCopy() {
const bytes = Berith.example().copyAndDispose() // Uint8Array
}
function exampleWithView() {
using memory = Berith.example() // X.Memory
const bytes = memory.bytes // Uint8Array
}
Don't forget to free memory with using
keyword, .free()
method, or .freeNextTick()
method
function exampleWithUsing() {
using memory = Berith.example()
// Do stuff with `memory` or `memory.bytes`
...
// Memory is automatically freed by `using` keyword
}
function exampleWithFree() {
const memory = Berith.example()
// Do stuff with `memory` or `memory.bytes`
...
memory.free()
}
function exampleWithFreeNextTick() {
const memory = Berith.example().freeNextTick()
// Do synchronous stuff with `memory` or `memory.bytes`
...
// Memory is automatically freed by `.freeNextTick()` method
}
You can serialize and deserialize almost any type to and from Memory (and Uint8Array)
// Generate a private key
using privateKey = Ed25519SigningKey.random()
// Extract private key into Memory bytes
using privateKeyMemory = privateKey.to_bytes() // Berith.Memory
...
// Get back private key from Memory bytes
using privateKey2 = Ed25519SigningKey.from_bytes(privateKeyMemory)
// Generate a private key
using privateKey = Ed25519SigningKey.random()
// Extract private key into JavaScript bytes
const privateKeyBytes = privateKey.to_bytes().copyAndDispose()
...
// Wrap JavaScript bytes into Memory bytes
using privateKeyMemory = new Berith.Memory(privateKeyBytes)
// Get back private key from Memory bytes
using privateKey2 = Ed25519SigningKey.from_bytes(privateKeyMemory)
You need to install Rust
Then, install wasm-pack
cargo install wasm-pack
Finally, do a clean install and build
npm ci && npm run build
You can build the exact same bytecode using Docker, just be sure you're on a linux/amd64
host
docker compose up --build
Then check that all the files are the same using git status
git status --porcelain
If the output is empty then the bytecode is the same as the one I commited
Each time I commit to the repository, the GitHub's CI does the following:
- Clone the repository
- Reproduce the build using
docker compose up --build
- Throw an error if the
git status --porcelain
output is not empty
Each time I release a new version tag on GitHub, the GitHub's CI does the following:
- Clone the repository
- Do not reproduce the build, as it's already checked by the task above
- Throw an error if there is a
npm diff
between the cloned repository and the same version tag on NPM
If a version is present on NPM but not on GitHub, do not use!