|
|
@ -4,6 +4,12 @@ import { generateMnemonic, validateMnemonic } from "@scure/bip39"; |
|
|
|
import { wordlist } from "@scure/bip39/wordlists/english"; |
|
|
|
import { wordlist } from "@scure/bip39/wordlists/english"; |
|
|
|
import forge from "node-forge"; |
|
|
|
import forge from "node-forge"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type Keys = { |
|
|
|
|
|
|
|
privateKey: Uint8Array; |
|
|
|
|
|
|
|
publicKey: Uint8Array; |
|
|
|
|
|
|
|
seed: Uint8Array; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
async function seedFromMnemonic(mnemonic: string) { |
|
|
|
async function seedFromMnemonic(mnemonic: string) { |
|
|
|
return pbkdf2Async(sha256, mnemonic, "mnemonic", { |
|
|
|
return pbkdf2Async(sha256, mnemonic, "mnemonic", { |
|
|
|
c: 2048, |
|
|
|
c: 2048, |
|
|
@ -15,7 +21,7 @@ export function verifyValidMnemonic(mnemonic: string) { |
|
|
|
return validateMnemonic(mnemonic, wordlist); |
|
|
|
return validateMnemonic(mnemonic, wordlist); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export async function keysFromMnemonic(mnemonic: string) { |
|
|
|
export async function keysFromMnemonic(mnemonic: string): Promise<Keys> { |
|
|
|
const seed = await seedFromMnemonic(mnemonic); |
|
|
|
const seed = await seedFromMnemonic(mnemonic); |
|
|
|
|
|
|
|
|
|
|
|
const { privateKey, publicKey } = forge.pki.ed25519.generateKeyPair({ |
|
|
|
const { privateKey, publicKey } = forge.pki.ed25519.generateKeyPair({ |
|
|
@ -25,6 +31,7 @@ export async function keysFromMnemonic(mnemonic: string) { |
|
|
|
return { |
|
|
|
return { |
|
|
|
privateKey, |
|
|
|
privateKey, |
|
|
|
publicKey, |
|
|
|
publicKey, |
|
|
|
|
|
|
|
seed, |
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -34,8 +41,8 @@ export function genMnemonic(): string { |
|
|
|
|
|
|
|
|
|
|
|
export async function signCode( |
|
|
|
export async function signCode( |
|
|
|
code: string, |
|
|
|
code: string, |
|
|
|
privateKey: forge.pki.ed25519.NativeBuffer |
|
|
|
privateKey: Uint8Array |
|
|
|
): Promise<forge.pki.ed25519.NativeBuffer> { |
|
|
|
): Promise<Uint8Array> { |
|
|
|
return forge.pki.ed25519.sign({ |
|
|
|
return forge.pki.ed25519.sign({ |
|
|
|
encoding: "utf8", |
|
|
|
encoding: "utf8", |
|
|
|
message: code, |
|
|
|
message: code, |
|
|
@ -43,18 +50,85 @@ export async function signCode( |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function bytesToBase64(bytes: Uint8Array) { |
|
|
|
|
|
|
|
return forge.util.encode64(String.fromCodePoint(...bytes)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function bytesToBase64Url(bytes: Uint8Array): string { |
|
|
|
export function bytesToBase64Url(bytes: Uint8Array): string { |
|
|
|
return btoa(String.fromCodePoint(...bytes)) |
|
|
|
return bytesToBase64(bytes) |
|
|
|
.replace(/\//g, "_") |
|
|
|
.replace(/\//g, "_") |
|
|
|
.replace(/\+/g, "-") |
|
|
|
.replace(/\+/g, "-") |
|
|
|
.replace(/=+$/, ""); |
|
|
|
.replace(/=+$/, ""); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export async function signChallenge(mnemonic: string, challengeCode: string) { |
|
|
|
export async function signChallenge(keys: Keys, challengeCode: string) { |
|
|
|
const keys = await keysFromMnemonic(mnemonic); |
|
|
|
|
|
|
|
const signature = await signCode(challengeCode, keys.privateKey); |
|
|
|
const signature = await signCode(challengeCode, keys.privateKey); |
|
|
|
return { |
|
|
|
return bytesToBase64Url(signature); |
|
|
|
publicKey: bytesToBase64Url(keys.publicKey), |
|
|
|
} |
|
|
|
signature: 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 async function decryptData( |
|
|
|
|
|
|
|
data: `${string}.${string}.${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(); |
|
|
|
} |
|
|
|
} |
|
|
|