First commit in rust
This commit is contained in:
69
backchannel-web/src/crypto.ts
Normal file
69
backchannel-web/src/crypto.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { chacha20poly1305 } from "@noble/ciphers/chacha";
|
||||
import { x25519 } from "@noble/curves/ed25519";
|
||||
import { hkdf } from "@noble/hashes/hkdf";
|
||||
import { sha256 } from "@noble/hashes/sha256";
|
||||
import { randomBytes } from "@noble/hashes/utils";
|
||||
|
||||
const HKDF_SALT = new TextEncoder().encode("backchannel-dm-salt-v1");
|
||||
const HKDF_INFO = new TextEncoder().encode("backchannel-dm-key-v1");
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
export function bytesToBase64(input: Uint8Array): string {
|
||||
let bin = "";
|
||||
for (const b of input) {
|
||||
bin += String.fromCharCode(b);
|
||||
}
|
||||
return btoa(bin);
|
||||
}
|
||||
|
||||
export function base64ToBytes(input: string): Uint8Array {
|
||||
const bin = atob(input);
|
||||
const bytes = new Uint8Array(bin.length);
|
||||
for (let i = 0; i < bin.length; i += 1) {
|
||||
bytes[i] = bin.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export function getOrCreateIdentityKey(): string {
|
||||
const current = localStorage.getItem("bc.identity_key");
|
||||
if (current) {
|
||||
return current;
|
||||
}
|
||||
|
||||
const key = bytesToBase64(randomBytes(32));
|
||||
localStorage.setItem("bc.identity_key", key);
|
||||
return key;
|
||||
}
|
||||
|
||||
export function createEphemeralKeypair(): { privateKey: Uint8Array; publicKeyB64: string } {
|
||||
const privateKey = x25519.utils.randomPrivateKey();
|
||||
const publicKey = x25519.getPublicKey(privateKey);
|
||||
return { privateKey, publicKeyB64: bytesToBase64(publicKey) };
|
||||
}
|
||||
|
||||
export function deriveDmKey(privateKey: Uint8Array, theirPublicKeyB64: string): Uint8Array {
|
||||
const theirPublicKey = base64ToBytes(theirPublicKeyB64);
|
||||
const sharedSecret = x25519.getSharedSecret(privateKey, theirPublicKey);
|
||||
return hkdf(sha256, sharedSecret, HKDF_SALT, HKDF_INFO, 32);
|
||||
}
|
||||
|
||||
export function encryptDm(key: Uint8Array, plaintext: string): { nonce: string; ciphertext: string } {
|
||||
const nonceBytes = randomBytes(12);
|
||||
const cipher = chacha20poly1305(key, nonceBytes);
|
||||
const ciphertext = cipher.encrypt(encoder.encode(plaintext));
|
||||
return {
|
||||
nonce: bytesToBase64(nonceBytes),
|
||||
ciphertext: bytesToBase64(ciphertext),
|
||||
};
|
||||
}
|
||||
|
||||
export function decryptDm(key: Uint8Array, nonceB64: string, ciphertextB64: string): string {
|
||||
const nonceBytes = base64ToBytes(nonceB64);
|
||||
const ciphertext = base64ToBytes(ciphertextB64);
|
||||
const cipher = chacha20poly1305(key, nonceBytes);
|
||||
const plaintext = cipher.decrypt(ciphertext);
|
||||
return decoder.decode(plaintext);
|
||||
}
|
||||
Reference in New Issue
Block a user