113 lines
3.6 KiB
TypeScript
113 lines
3.6 KiB
TypeScript
import { exists } from "jsr:@std/fs";
|
|
import * as nostrTools from "@nostr/tools";
|
|
import * as nip06 from "@nostr/tools/nip06";
|
|
import { decodeBase64, encodeBase64 } from "jsr:@std/encoding@0.224/base64";
|
|
import { getEveFilePath } from "./utils/files.ts";
|
|
import {
|
|
decryptUint8Array,
|
|
encryptionKey,
|
|
encryptUint8Array,
|
|
} from "./utils/encryption.ts";
|
|
import { NSec } from "@nostr/tools/nip19";
|
|
|
|
export function isLocalhost(req: Request): boolean {
|
|
const url = new URL(req.url);
|
|
const hostname = url.hostname;
|
|
return (
|
|
hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost"
|
|
);
|
|
}
|
|
|
|
export function isValidJSON(str: string) {
|
|
try {
|
|
JSON.parse(str);
|
|
} catch {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function isArray<T>(obj: unknown): obj is T[] {
|
|
return Array.isArray(obj);
|
|
}
|
|
|
|
export function randomTimeUpTo2DaysInThePast() {
|
|
const now = Date.now();
|
|
const twoDaysAgo = now - 2 * 24 * 60 * 60 * 1000 - 3600 * 1000; // 1 hour buffer in case of clock skew
|
|
return Math.floor(
|
|
(Math.floor(Math.random() * (now - twoDaysAgo)) + twoDaysAgo) / 1000,
|
|
);
|
|
}
|
|
|
|
export async function getCCNPubkey(): Promise<string> {
|
|
const ccnPubPath = await getEveFilePath("ccn.pub");
|
|
const seedPath = await getEveFilePath("ccn.seed");
|
|
const doWeHaveKey = await exists(ccnPubPath);
|
|
if (doWeHaveKey) return Deno.readTextFileSync(ccnPubPath);
|
|
const ccnSeed = Deno.env.get("CCN_SEED") ||
|
|
((await exists(seedPath))
|
|
? Deno.readTextFileSync(seedPath)
|
|
: nip06.generateSeedWords());
|
|
const ccnPrivateKey = nip06.privateKeyFromSeedWords(ccnSeed);
|
|
const ccnPublicKey = nostrTools.getPublicKey(ccnPrivateKey);
|
|
const encryptedPrivateKey = encryptUint8Array(ccnPrivateKey, encryptionKey);
|
|
|
|
Deno.writeTextFileSync(ccnPubPath, ccnPublicKey);
|
|
Deno.writeTextFileSync(
|
|
await getEveFilePath("ccn.priv"),
|
|
encodeBase64(encryptedPrivateKey),
|
|
);
|
|
Deno.writeTextFileSync(seedPath, ccnSeed);
|
|
|
|
return ccnPublicKey;
|
|
}
|
|
|
|
export async function getMLSPrivateKey(): Promise<NSec> {
|
|
const mlsPrivPath = await getEveFilePath("mls.priv");
|
|
const doWeHaveKey = await exists(mlsPrivPath);
|
|
if (doWeHaveKey) {
|
|
const encryptedPrivateKey = Deno.readTextFileSync(mlsPrivPath);
|
|
const decryptedPrivateKey = decryptUint8Array(
|
|
decodeBase64(encryptedPrivateKey),
|
|
encryptionKey,
|
|
);
|
|
return nostrTools.nip19.nsecEncode(decryptedPrivateKey);
|
|
}
|
|
const mlsPrivateKey = nostrTools.generateSecretKey();
|
|
const encryptedPrivateKey = encryptUint8Array(mlsPrivateKey, encryptionKey);
|
|
Deno.writeTextFileSync(mlsPrivPath, encodeBase64(encryptedPrivateKey));
|
|
return nostrTools.nip19.nsecEncode(mlsPrivateKey);
|
|
}
|
|
|
|
export async function getCCNPrivateKey(): Promise<Uint8Array> {
|
|
const encryptedPrivateKey = Deno.readTextFileSync(
|
|
await getEveFilePath("ccn.priv"),
|
|
);
|
|
return decryptUint8Array(decodeBase64(encryptedPrivateKey), encryptionKey);
|
|
}
|
|
|
|
/**
|
|
* Compares two byte-like objects in a constant-time manner to prevent timing attacks.
|
|
*
|
|
* @param a - First byte-like object to compare
|
|
* @param b - Second byte-like object to compare
|
|
* @returns boolean indicating whether the inputs contain identical bytes
|
|
*/
|
|
export function bytesEqual<
|
|
T extends Uint8Array | number[] | string,
|
|
>(a: T, b: T): boolean {
|
|
const aLength = a.length;
|
|
const bLength = b.length;
|
|
let result = aLength !== bLength ? 1 : 0;
|
|
const maxLength = Math.max(aLength, bLength);
|
|
for (let i = 0; i < maxLength; i++) {
|
|
const aVal = i < aLength
|
|
? (typeof a === "string" ? a.charCodeAt(i) : a[i])
|
|
: 0;
|
|
const bVal = i < bLength
|
|
? (typeof b === "string" ? b.charCodeAt(i) : b[i])
|
|
: 0;
|
|
result |= aVal ^ bVal;
|
|
}
|
|
return result === 0;
|
|
}
|