Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/hazae41/berith

WebAssembly port of Ed25519 signatures and X25519 key exchange
https://github.com/hazae41/berith

browser csprng curve25519 deno diffie-hellman ecdh ecdsa ed25519 eddsa javascript prng rust signature typescript wasm web webassembly webcrypto x25519

Last synced: about 1 month ago
JSON representation

WebAssembly port of Ed25519 signatures and X25519 key exchange

Awesome Lists containing this project

README

        




# Berith

WebAssembly port of Ed25519 signatures and X25519 key exchange

```bash
npm i @hazae41/berith
```

[**Node Package ๐Ÿ“ฆ**](https://www.npmjs.com/package/@hazae41/berith) โ€ข [**Deno Module ๐Ÿฆ–**](https://deno.land/x/berith) โ€ข [**Next.js CodeSandbox ๐Ÿชฃ**](https://codesandbox.io/p/github/hazae41/berith-example-next)

## Algorithms
- Ed25519 from Dalek (ed25519-dalek)
- X25519 from Dalek (ed25519-dalek)

## Features
- Reproducible building
- Pre-bundled and streamed
- Zero-copy memory slices

## Benchmark

### Deno

```bash
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
```

### Node

```bash
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)
```

## Usage

### Ed25519 (EdDSA over Curve25519)

```ts
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
```

### X25519 (ECDH over Curve25519)

```ts
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)
```

### Memory

You have to wrap Uint8Array into Memory in order to pass them to WebAssembly

```typescript
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

```typescript
function exampleWithCopy() {
const bytes = Berith.example().copyAndDispose() // Uint8Array
}
```

```typescript
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

```typescript
function exampleWithUsing() {
using memory = Berith.example()

// Do stuff with `memory` or `memory.bytes`
...

// Memory is automatically freed by `using` keyword
}
```

```typescript
function exampleWithFree() {
const memory = Berith.example()

// Do stuff with `memory` or `memory.bytes`
...

memory.free()
}
```

```typescript
function exampleWithFreeNextTick() {
const memory = Berith.example().freeNextTick()

// Do synchronous stuff with `memory` or `memory.bytes`
...

// Memory is automatically freed by `.freeNextTick()` method
}
```

### Serialization

You can serialize and deserialize almost any type to and from Memory (and Uint8Array)

```typescript
// 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)
```

```typescript
// 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)
```

## Building

### Unreproducible building

You need to install [Rust](https://www.rust-lang.org/tools/install)

Then, install [wasm-pack](https://github.com/rustwasm/wasm-pack)

```bash
cargo install wasm-pack
```

Finally, do a clean install and build

```bash
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

```bash
docker compose up --build
```

Then check that all the files are the same using `git status`

```bash
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!