first chrome implementation

This commit is contained in:
DEV Sam Hayes
2025-01-10 19:37:10 +01:00
parent dc7a980dc5
commit a652718bc7
175 changed files with 18526 additions and 610 deletions

View File

@@ -0,0 +1,93 @@
import { Buffer } from 'buffer';
export class CryptoHelper {
/**
* Generate a base64 encoded IV.
*/
static generateIV(): string {
const iv = crypto.getRandomValues(new Uint8Array(12));
return Buffer.from(iv).toString('base64');
}
/**
* Hash (SHA-256) a text string.
*/
static async hash(text: string): Promise<string> {
const textUint8 = new TextEncoder().encode(text); // encode as (utf-8) Uint8Array
const hashBuffer = await crypto.subtle.digest('SHA-256', textUint8); // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, '0'))
.join(''); // convert bytes to hex string
return hashHex;
}
static v4(): string {
return crypto.randomUUID();
}
static async deriveKey(password: string): Promise<CryptoKey> {
const algo = {
name: 'PBKDF2',
hash: 'SHA-256',
salt: new TextEncoder().encode('3e7cdebd-3b4c-4125-a18c-05750cad8ec3'),
iterations: 1000,
};
return crypto.subtle.deriveKey(
algo,
await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(password),
{
name: algo.name,
},
false,
['deriveKey']
),
{
name: 'AES-GCM',
length: 256,
},
false,
['encrypt', 'decrypt']
);
}
static async encrypt(
text: string,
ivBase64String: string,
password: string
): Promise<string> {
const algo = {
name: 'AES-GCM',
length: 256,
iv: Buffer.from(ivBase64String, 'base64'),
};
const cipherText = await crypto.subtle.encrypt(
algo,
await CryptoHelper.deriveKey(password),
new TextEncoder().encode(text)
);
return Buffer.from(cipherText).toString('base64');
}
static async decrypt(
encryptedBase64String: string,
ivBase64String: string,
password: string
): Promise<string> {
const algo = {
name: 'AES-GCM',
length: 256,
iv: Buffer.from(ivBase64String, 'base64'),
};
return new TextDecoder().decode(
await crypto.subtle.decrypt(
algo,
await CryptoHelper.deriveKey(password),
Buffer.from(encryptedBase64String, 'base64')
)
);
}
}

View File

@@ -0,0 +1,10 @@
export class DateHelper {
static dateToISOLikeButLocal(date: Date): string {
const offsetMs = date.getTimezoneOffset() * 60 * 1000;
const msLocal = date.getTime() - offsetMs;
const dateLocal = new Date(msLocal);
const iso = dateLocal.toISOString();
const isoLocal = iso.slice(0, 19).replace('T', ' ').replaceAll(':', '.');
return isoLocal;
}
}

View File

@@ -0,0 +1,128 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { bech32 } from '@scure/base';
import * as utils from '@noble/curves/abstract/utils';
import { getPublicKey } from 'nostr-tools';
export interface NostrHexObject {
represents: string;
hex: string;
}
export interface NostrPubkeyObject {
hex: string;
npub: string;
}
export interface NostrPrivkeyObject {
hex: string;
nsec: string;
}
export class NostrHelper {
static getNostrPrivkeyObject(nsec_OR_hex: string): NostrPrivkeyObject {
// 1. Assume we got an nsec.
// Try to generate hex value.
try {
const hexObject = this.#nSomething2hexObject(nsec_OR_hex);
if (hexObject.represents !== 'nsec') {
throw new Error('The provided string is NOT an nsec.');
}
// Everything is fine. The provided string IS an nsec.
return {
hex: hexObject.hex,
nsec: nsec_OR_hex,
};
} catch (error) {
// Continue.
}
// 2. Assume we got an hex.
// Try to generate the nsec.
try {
const nsec = NostrHelper.privkey2nsec(nsec_OR_hex);
return {
hex: nsec_OR_hex,
nsec,
};
} catch (error) {
// Continue;
}
throw new Error('Could not convert the provided string into nsec/hex.');
}
static getNostrPubkeyObject(npub_OR_hex: string): NostrPubkeyObject {
// 1. Assume we got an npub.
// Try to generate hex value.
try {
const hexObject = this.#nSomething2hexObject(npub_OR_hex);
if (hexObject.represents !== 'npub') {
throw new Error('The provided string is NOT an npub.');
}
// Everything is fine. The provided string IS an npub.
return {
hex: hexObject.hex,
npub: npub_OR_hex,
};
} catch (error) {
// Continue.
}
// 2. Assume we got an hex.
// Try to generate the npub.
try {
const npub = NostrHelper.pubkey2npub(npub_OR_hex);
return {
hex: npub_OR_hex,
npub,
};
} catch (error) {
// Continue;
}
throw new Error('Could not convert the provided string into npub/hex.');
}
static pubkey2npub(hex: string): string {
const data = utils.hexToBytes(hex);
const words = bech32.toWords(data);
return bech32.encode('npub', words, 5000);
}
static privkey2nsec(hex: string): string {
const data = utils.hexToBytes(hex);
const words = bech32.toWords(data);
return bech32.encode('nsec', words, 5000);
}
static pubkeyFromPrivkey(hex: string): string {
const privkeyBytes = utils.hexToBytes(hex);
return getPublicKey(privkeyBytes);
}
static hex2bytes(hex: string): Uint8Array {
return utils.hexToBytes(hex);
}
static splitKey(text: string, first: number, last: number): string {
const part1 = text.slice(0, first);
const part2 = '...';
const part3 = text.slice(-last);
return `${part1}${part2}${part3}`;
}
static #nSomething2hexObject(nSomething: string): NostrHexObject {
const { prefix, words } = bech32.decode(
nSomething as `${string}1${string}`,
5000
);
const data = new Uint8Array(bech32.fromWords(words));
return {
represents: prefix,
hex: utils.bytesToHex(data),
};
}
}

View File

@@ -0,0 +1,8 @@
export class TextHelper {
/**
* Takes a string returns something like "\<first-x-chars>...\<last-y-chars>""
*/
static split(text: string, first: number, last: number): string {
return `${text.slice(0, first)}...${text.slice(-last)}`;
}
}