mirror of
https://github.com/sussy-code/smov.git
synced 2026-04-20 08:02:09 +00:00
131 lines
3.2 KiB
TypeScript
131 lines
3.2 KiB
TypeScript
import { pbkdf2Async } from "@noble/hashes/pbkdf2";
|
|
import { sha256 } from "@noble/hashes/sha256";
|
|
import { generateMnemonic, validateMnemonic } from "@scure/bip39";
|
|
import { wordlist } from "@scure/bip39/wordlists/english";
|
|
import forge from "node-forge";
|
|
|
|
type Keys = {
|
|
privateKey: Uint8Array;
|
|
publicKey: Uint8Array;
|
|
seed: Uint8Array;
|
|
};
|
|
|
|
async function seedFromMnemonic(mnemonic: string) {
|
|
return pbkdf2Async(sha256, mnemonic, "mnemonic", {
|
|
c: 2048,
|
|
dkLen: 32,
|
|
});
|
|
}
|
|
|
|
export function verifyValidMnemonic(mnemonic: string) {
|
|
return validateMnemonic(mnemonic, wordlist);
|
|
}
|
|
|
|
export async function keysFromMnemonic(mnemonic: string): Promise<Keys> {
|
|
const seed = await seedFromMnemonic(mnemonic);
|
|
|
|
const { privateKey, publicKey } = forge.pki.ed25519.generateKeyPair({
|
|
seed,
|
|
});
|
|
|
|
return {
|
|
privateKey,
|
|
publicKey,
|
|
seed,
|
|
};
|
|
}
|
|
|
|
export function genMnemonic(): string {
|
|
return generateMnemonic(wordlist);
|
|
}
|
|
|
|
export async function signCode(
|
|
code: string,
|
|
privateKey: Uint8Array
|
|
): Promise<Uint8Array> {
|
|
return forge.pki.ed25519.sign({
|
|
encoding: "utf8",
|
|
message: code,
|
|
privateKey,
|
|
});
|
|
}
|
|
|
|
export function bytesToBase64(bytes: Uint8Array) {
|
|
return forge.util.encode64(String.fromCodePoint(...bytes));
|
|
}
|
|
|
|
export function bytesToBase64Url(bytes: Uint8Array): string {
|
|
return bytesToBase64(bytes)
|
|
.replace(/\//g, "_")
|
|
.replace(/\+/g, "-")
|
|
.replace(/=+$/, "");
|
|
}
|
|
|
|
export async function signChallenge(keys: Keys, challengeCode: string) {
|
|
const signature = await signCode(challengeCode, keys.privateKey);
|
|
return bytesToBase64Url(signature);
|
|
}
|
|
|
|
export function base64ToBuffer(data: string) {
|
|
return forge.util.binary.base64.decode(data);
|
|
}
|
|
|
|
export function base64ToStringBugger(data: string) {
|
|
return forge.util.createBuffer(base64ToBuffer(data));
|
|
}
|
|
|
|
export function stringBufferToBase64(buffer: forge.util.ByteStringBuffer) {
|
|
return forge.util.encode64(buffer.getBytes());
|
|
}
|
|
|
|
export async function encryptData(data: string, secret: Uint8Array) {
|
|
if (secret.byteLength !== 32)
|
|
throw new Error("Secret must be at least 256-bit");
|
|
|
|
const iv = await new Promise<string>((resolve, reject) => {
|
|
forge.random.getBytes(16, (err, bytes) => {
|
|
if (err) reject(err);
|
|
resolve(bytes);
|
|
});
|
|
});
|
|
|
|
const cipher = forge.cipher.createCipher(
|
|
"AES-GCM",
|
|
forge.util.createBuffer(secret)
|
|
);
|
|
cipher.start({
|
|
iv,
|
|
tagLength: 128,
|
|
});
|
|
cipher.update(forge.util.createBuffer(data));
|
|
cipher.finish();
|
|
|
|
const encryptedData = cipher.output;
|
|
const tag = cipher.mode.tag;
|
|
|
|
return `${forge.util.encode64(iv)}.${stringBufferToBase64(
|
|
encryptedData
|
|
)}.${stringBufferToBase64(tag)}` as const;
|
|
}
|
|
|
|
export function decryptData(data: string, secret: Uint8Array) {
|
|
if (secret.byteLength !== 32) throw new Error("Secret must be 256-bit");
|
|
|
|
const [iv, encryptedData, tag] = data.split(".");
|
|
|
|
const decipher = forge.cipher.createDecipher(
|
|
"AES-GCM",
|
|
forge.util.createBuffer(secret)
|
|
);
|
|
decipher.start({
|
|
iv: base64ToStringBugger(iv),
|
|
tag: base64ToStringBugger(tag),
|
|
tagLength: 128,
|
|
});
|
|
decipher.update(base64ToStringBugger(encryptedData));
|
|
const pass = decipher.finish();
|
|
|
|
if (!pass) throw new Error("Error decrypting data");
|
|
|
|
return decipher.output.toString();
|
|
}
|