Berith
WebAssembly port of Dalekβs Ed25519 and X25519, Rust implementations of Ed25519 signatures and X25519 key exchange.
npm i @hazae41/berith
Node Package π¦ β’ Deno Module π¦ β’ Next.js CodeSandbox πͺ£
Benchmark
Deno
git clone https://github.com/hazae41/berith && cd berith && npm i && npm run bench:deno
cpu: Apple M1 Max
runtime: deno 1.30.0 (aarch64-apple-darwin)
file:///src/deno/bench/mod.bench.ts
benchmark time (avg) (min β¦ max) p75 p99 p995
---------------------------------------------------------------------- -----------------------------
@hazae41/berith (unserialized) 313.7 Β΅s/iter (305.79 Β΅s β¦ 493.42 Β΅s) 314.12 Β΅s 337.92 Β΅s 345.25 Β΅s
@hazae41/berith (serialized) 357.31 Β΅s/iter (347.12 Β΅s β¦ 803.42 Β΅s) 358.04 Β΅s 385.5 Β΅s 390.46 Β΅s
@noble/ed25519 1.7.1 1.85 ms/iter (1.65 ms β¦ 2.27 ms) 1.89 ms 2.07 ms 2.08 ms
summary
@hazae41/berith (unserialized)
1.14x faster than @hazae41/berith (serialized)
5.9x faster than @noble/ed25519 1.7.1
Node
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) β '270.19 ΞΌs/iter' β '262.00 ΞΌs' β '731.42 ΞΌs' β
β @hazae41/berith (serialized) β '306.35 ΞΌs/iter' β '297.67 ΞΌs' β '611.46 ΞΌs' β
β @noble/ed25519 1.7.1 β '1.89 ms/iter' β '1.70 ms' β '31.04 ms' β
β supercop.wasm 5.0.1 β '173.95 ΞΌs/iter' β '166.25 ΞΌs' β '989.75 ΞΌs' β
β node:crypto (unserialized) β '151.05 ΞΌs/iter' β '142.42 ΞΌs' β '5.73 ms' β
β node:crypto (serialized) β '557.00 ΞΌs/iter' β '545.04 ΞΌs' β '7.72 ms' β
ββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββ΄ββββββββββββββ΄ββββββββββββββ
Summary
- @hazae41/berith (unserialized) is 1.13x faster than @hazae41/berith (serialized)
- @hazae41/berith (unserialized) is 7.01x faster than @noble/ed25519 1.7.1
- @hazae41/berith (unserialized) is 0.64x faster than supercop.wasm 5.0.1
- @hazae41/berith (unserialized) is 0.56x faster than node:crypto (unserialized)
- @hazae41/berith (unserialized) is 2.06x faster than node:crypto (serialized)
Usage
Ed25519 (EdDSA over Curve25519)
import { Berith, Ed25519Keypair } from "@hazae41/berith";
// Wait for WASM to load
Berith.initSyncBundledOnce();
// Generate an identity
const keypair = new Ed25519Keypair();
const identity = keypair.public(); // Ed25519PublicKey
// Define bytes to sign
const bytes = new TextEncoder().encode("hello world"); // Uint8Array
// Sign and verify
const proof = keypair.sign(bytes); // Ed25519Signature
const verified = identity.verify(bytes, proof); // boolean
You can serialize and deserialize to Uint8Array
const bytes = new Ed25519Keypair().to_bytes();
const keypair = Ed25519Keypair.from_bytes(bytes);
const bytes = keypair.public().to_bytes();
const identity = Ed25519PublicKey.from_bytes(bytes);
const bytes = keypair.sign(input).to_bytes();
const proof = Ed25519Signature.from_bytes(bytes);
X25519 (ECDH over Curve25519)
import { Berith, X25519StaticSecret } from "@hazae41/berith";
// Wait for WASM to load
Berith.initSyncBundledOnce();
// Generate secret x for Alice
const secretx = new X25519StaticSecret()
// Generate secret y for Bob
const secrety = new X25519StaticSecret()
// Get public X for Alice to send to Bob
const publicx = secretx.to_public()
// Get public Y for Bob to send to Alice
const publicy = secrety.to_public()
// Alice computes the shared key S from x and Y
const sharedx = secretx.diffie_hellman(publicy)
// Bob computes the shared key S from y and X
const sharedy = secrety.diffie_hellman(publicx)
// S is the same for Alice and Bob
console.log("S (Alice)", sharedx.to_bytes())
console.log("S (Bob", sharedy.to_bytes())
Building
Unreproducible building
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
Reproducible building
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
Automated checks
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!