Arken 推出使用 secp256k1 金鑰的無密碼認證中介軟體
Show HN: Arken – Passwordless auth middleware using secp256k1 keys
Arken 是一套使用 secp256k1 金鑰的無密碼認證中介軟體,讓 Web 伺服器不再儲存密碼或 Session。
- 核心機制:使用者持有私鑰,伺服器只傳送一次性 nonce,客戶端簽名後回傳,伺服器驗證簽名並比對 DID。
此方案消除傳統密碼與 session,提供無縫且抗釣魚的身份驗證,並以 secp256k1 DID 為核心,開源且可直接整合至 Express 或 FastAPI 應用,降低開發與維運成本,同時保持高安全性與跨平臺可攜性。
Arken Auth
Passwordless authentication for web servers. No passwords. No sessions. Nothing to breach.
Live Demo → · npm · PyPI
The problem
Every auth system today asks your server to hold a secret — a password hash, a session token, an API key. That secret is a liability. It gets breached. It gets phished. It gets rotated on a schedule that nobody follows.
Arken Auth flips the model: the user holds the secret, the server holds nothing.
How it works
Client Server
│ │
├── GET /protected ────────────────▶│
│ │ (no auth header)
│◀── 401 { challenge: { nonce } } ──┤
│ │
│ sign(nonce, privateKey) │
│ │
├── GET /protected ────────────────▶│
│ Authorization: Signature │
│ did="did:key:z...", │
│ nonce="...", │
│ signature="0x..." │
│ │ recover signer from sig
│ │ compare to DID pubkey
│◀── 200 { did: "did:key:z..." } ───┤
- Client requests a protected resource — server issues a one-time nonce
- Client signs the nonce with their private key (never leaves their device)
- Client sends the signature + their DID back to the server
- Server recovers the signer address from the signature and checks it matches the DID
- No password ever transmitted. No session stored. No database touched.
The cryptographic core is written in Rust and called via FFI from Node.js or Python.
Quickstart — Node.js
npm install arken-auth
const express = require('express'); const { arkenAuth } = require('arken-auth'); const app = express(); app.get('/protected', arkenAuth(), (req, res) => { res.json({ message: 'hello', did: req.user.did }); });
That's it. arkenAuth() handles the entire challenge-response cycle.
Quickstart — Python
pip install arken-auth
from fastapi import FastAPI, Depends from arken_auth import ArkenAuth app = FastAPI() arken = ArkenAuth() @app.get("/protected") async def protected(did: str = Depends(arken)): return {"message": "hello", "did": did}
Client-side (browser or CLI)
Any client that can sign an Ethereum message works. In the browser with ethers.js:
// Generate a DID from a wallet const wallet = ethers.Wallet.createRandom(); const did = walletToDid(wallet); // did:key:z... // Step 1: get a challenge const r1 = await fetch('/protected'); const { challenge: { nonce } } = await r1.json(); // 401 // Step 2: sign the nonce const signature = await wallet.signMessage(ethers.utils.toUtf8Bytes(nonce)); // Step 3: authenticate const r2 = await fetch('/protected', { headers: { Authorization: `Signature did="${did}",nonce="${nonce}",signature="${signature}"` } });
What makes it different
| Username/Password | JWT / OAuth | WebAuthn | Arken Auth | |
|---|---|---|---|---|
| Server stores secrets | ✗ (hash) | ✗ (secret) | ✓ | ✓ |
| Works without accounts | ✗ | ✗ | ✗ | ✓ |
| Portable identity | ✗ | ✗ | ✗ | ✓ |
| No JS SDK required | ✓ | ✓ | ✗ | ✓ |
| Language agnostic | ✓ | ✓ | ✓ | ✓ |
| Phishing resistant | ✗ | ✗ | ✓ | ✓ |
The key difference from WebAuthn: your identity (did:key:z...) is portable. It travels with you across servers, apps, and devices. A WebAuthn credential is bound to a single origin. A DID is bound to nothing but your key.
Architecture
packages/
arken-auth/ Node.js middleware (Express)
arken-auth-py/ Python middleware (FastAPI)
arken-phase1-docker/workspace/
rust-core/ Cryptographic engine (Rust)
src/lib.rs Challenge generation + signature verification
node-wrapper/ Node.js FFI bindings (koffi)
python-wrapper/ Python FFI bindings (ctypes)
public/ Interactive demo UI
The Rust core exposes two functions over FFI:
generate_challenge_into— generates a random nonce, stores it server-side with a 60s TTL, returns JSONverify_request_into— parses the auth header, validates the nonce, recovers the signer, checks against the DID
DID format
Arken uses did:key with secp256k1 keys (multicodec 0xe7 0x01), encoded as multibase base58btc (z prefix):
did:key:z[base58btc(0xe7 0x01 || compressed_pubkey)]
This is the W3C DID Key Method spec.
Requirements
- Rust (for building the native library):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Node.js ≥ 18 (for the npm package)
- Python ≥ 3.10 (for the pip package)
Status
Phase 1 — working middleware for Express and FastAPI. The cryptographic core is complete and tested.
What's next:
- Prebuilt binaries (no Rust required to install)
- Session/token layer (optional, for stateful apps)
did:webanddid:pkhsupport- Browser extension wallet
License
MIT