syasym
v1.1.2
Published
Non-Custodial Authentication Protocol, A Hybrid Symmetric-Asymmetric Approach & Perpetual Forward Secrecy
Readme
Syasym
Non-Custodial Authentication Protocol, A Hybrid Symmetric-Asymmetric Approach & Perpetual Forward Secrecy
Syasym (Symmetric-Asymmetric) is a hybrid authentication protocol designed to bridge the gap between Web2 usability and Web3 security. It features Perpetual Key Rotation (Forward Secrecy) and Client-Side Encryption, ensuring the server never holds user credentials or raw private keys.
Features
- 🔐 Non-Custodial: Server acts as a "blind vault." It stores encrypted keys (
epKey) but cannot read them. - 🔄 Perpetual Key Rotation: Cryptographic identity is regenerated on every successful login. Stolen keys become obsolete instantly.
- 📝 Sessionless Auth: No cookies or JWTs. Every HTTP request is digitally signed (
EdDSA). - 🛡️ Breach Immunity: A compromised database yields only useless encrypted blobs.
- 📱 Portable: Works on any device via browser (Web Crypto API).
Installation
npm install syasymUsage
1. Client-Side (Browser / Frontend)
Use SyasymClient to handle registration, login (solving challenges), and signing requests.
TypeScript
import { SyasymClient } from 'syasym';
const { generateChallenge, solveChallenge, signRequest } = SyasymClient;
// --- A. REGISTER ---
// Generate initial keys. Send `pubKey` and `epKey` to your server.
const challenge = await generateChallenge(username.value, password.value)
const res = await fetch('http://localhost:4000', {
method: 'POST',
headers: {
"Content-Type": 'application/json',
},
body: JSON.stringify({
username: username.value,
pubKey: challenge.pubKey,
epKey: challenge.epKey
})
})
const data = await res.json() as { message: string }
// --- B. LOGIN ---
// 1. Get challenge from server (contains your old epKey)
const res = await fetch('/get-challenge?username='+username.value)
const data = await res.json() as {
message: string
username: string
pubKey: string
epKey: EpKey
} | {}
// 2. Solve challenge (Decrypt old key -> Sign -> Rotate new key)
const challenge = await solveChallenge(username.value, data.message, data.pubKey, data.epKey, password.value, trust.checked)
if(!challenge) return alert('invalid password')
else{
// 3. Send signature & NEW keys to server
const res2 = await fetch('http://localhost:4000/login', {
method: 'POST',
headers: {
"Content-Type": 'application/json',
},
body: JSON.stringify({
username: username.value,
message: data.message,
signature: challenge.signature,
newPubKey: challenge.newChallenge.pubKey,
newEpKey: challenge.newChallenge.epKey
})
})
const data2 = await res2.json() as { message: string }
console.log(data2)
if(data2.message == 'User logged in successfully!'){
location.href = '/'
}
else{
alert(data2.message)
}
}
// --- C. AUTHENTICATED REQUEST ---
// Sign a request (GET/POST/PUT/DELETE)
// This automatically adds X-User-ID, X-Timestamp, and X-Signature headers
const headers = await signRequest('POST', '/api/protected-data', JSON.stringify({ foo: 'bar' }));
await fetch('/api/protected-data', {
method: 'POST',
headers: headers,
body: JSON.stringify({ foo: 'bar' })
});
// --- D. CHANGE PASSWORD ---
// Here is interesting parts, you can use /login endpoint to change your password
// logic is same as login, but change the challenge like this
const challenge = await solveChallengeAndChangePassword(username.value, data.message, data.pubKey, data.epKey, oldPassword.value, newPassword.value)
2. Server-Side (Node.js / Express)
Use SyasymServer to verify challenges and validate signed requests.
TypeScript
import express from 'express';
import { SyasymServer } from 'syasym';
const { generateChallenge, verifyChallenge, verifyRequest } = SyasymServer
const app = express();
// --- A. GENERATE CHALLENGE
app.get("/get-challenge", async (req, res) => {
const { username } = req.query;
const user = await getUser(username as string)
if(user){
const challenge = generateChallenge(user.username, user.pubKey, user.epKey)
res.json(challenge);
}
else{
res.status(404).json({ message: "User not found!" });
}
})
// --- B. VERIFY LOGIN ---
app.post('/login', async (req, res) => {
const { username, signature, newPubKey, newEpKey } = req.body;
if(!await hasUser(username)) res.status(404).json({ message: "User not found!" });
else{
const isValid = verifyChallenge(username, signature)
if(!isValid) res.status(401).json({ message: "Invalid signature!" });
else{
const succes = await updateSecurity(username, newPubKey, newEpKey)
if(succes){
console.log("User logged in: "+username)
res.json({ message: "User logged in successfully!" });
}
}
}
});
// --- C. VERIFY REQUEST ---
app.post('/api/protected-data', async (req, res) => {
const username = req.headers['x-user-id'];
// Get current public key from DB
const user = await getUser(username);
// Verify request signature
// Returns: 0 (Expired), 1 (Invalid), 2 (Valid)
const validation = verifyRequest(user.pubKey, req);
if (validation === 2) {
res.json({ data: "This is secret data" });
} else {
res.status(401).json({ error: "Unauthorized" });
}
});You can check compelete example on (Syasym-Authentication)[https://codeberg.org/nbrthx/syasym-authentication]
Protocol Spec
Cryptography Stack
- KDF: Argon2id (Memory: 64MB, Iterations: 4)
- Encryption: AES-GCM (256-bit)
- Signing: EdDSA (ed25519)
- Hashing: SHA-256
Data Flow
- Register: Argon2.id(Password) -> Generate PrivKey -> AES Encrypt PrivKey with Password -> epKey.
- Login: Server sends epKey -> Client decrypts epKey -> Signs Challenge -> Generates NewKeyPair.
License GPL v3 © Niberthix
