feat: add TS3 identity generation with Ed25519 keypairs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.6 committed 2026-03-30 00:28:12 +08:00
1 parent bc89c553b7
commit 2b6e81119b
4 files changed
+73

No files matched your search

+7
View File
@@ -12,6 +12,7 @@
"better-sqlite3": "^12.8.0", "better-sqlite3": "^12.8.0",
"express": "^5.2.1", "express": "^5.2.1",
"pino": "^10.3.1", "pino": "^10.3.1",
"tweetnacl": "^1.0.3",
"ws": "^8.20.0" "ws": "^8.20.0"
}, },
"devDependencies": { "devDependencies": {
@@ -3080,6 +3081,12 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"license": "Unlicense"
},
"node_modules/type-is": { "node_modules/type-is": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+1
View File
@@ -15,6 +15,7 @@
"better-sqlite3": "^12.8.0", "better-sqlite3": "^12.8.0",
"express": "^5.2.1", "express": "^5.2.1",
"pino": "^10.3.1", "pino": "^10.3.1",
"tweetnacl": "^1.0.3",
"ws": "^8.20.0" "ws": "^8.20.0"
}, },
"devDependencies": { "devDependencies": {
+28
View File
@@ -0,0 +1,28 @@
import { describe, it, expect } from "vitest";
import { generateIdentity, exportIdentity, importIdentity } from "./identity.js";
describe("TS3 Identity", () => {
it("generates a valid identity with keypair", () => {
const identity = generateIdentity();
expect(identity.publicKey).toBeInstanceOf(Uint8Array);
expect(identity.privateKey).toBeInstanceOf(Uint8Array);
expect(identity.publicKey.length).toBe(32);
expect(identity.privateKey.length).toBe(64);
expect(identity.uid).toBeTruthy();
expect(typeof identity.uid).toBe("string");
});
it("exports and imports identity consistently", () => {
const identity = generateIdentity();
const exported = exportIdentity(identity);
const imported = importIdentity(exported);
expect(imported.uid).toBe(identity.uid);
expect(imported.publicKey).toEqual(identity.publicKey);
});
it("generates unique identities each time", () => {
const id1 = generateIdentity();
const id2 = generateIdentity();
expect(id1.uid).not.toBe(id2.uid);
});
});
+37
View File
@@ -0,0 +1,37 @@
import nacl from "tweetnacl";
import crypto from "node:crypto";
export interface TS3Identity {
publicKey: Uint8Array;
privateKey: Uint8Array;
uid: string; // base64-encoded SHA1 of public key
}
export function generateIdentity(): TS3Identity {
const keypair = nacl.sign.keyPair();
const uid = computeUid(keypair.publicKey);
return {
publicKey: keypair.publicKey,
privateKey: keypair.secretKey,
uid,
};
}
export function computeUid(publicKey: Uint8Array): string {
return crypto.createHash("sha1").update(publicKey).digest("base64");
}
export function exportIdentity(identity: TS3Identity): string {
return JSON.stringify({
publicKey: Buffer.from(identity.publicKey).toString("base64"),
privateKey: Buffer.from(identity.privateKey).toString("base64"),
});
}
export function importIdentity(data: string): TS3Identity {
const parsed = JSON.parse(data) as { publicKey: string; privateKey: string };
const publicKey = new Uint8Array(Buffer.from(parsed.publicKey, "base64"));
const privateKey = new Uint8Array(Buffer.from(parsed.privateKey, "base64"));
const uid = computeUid(publicKey);
return { publicKey, privateKey, uid };
}