89 lines
2.8 KiB
TypeScript
89 lines
2.8 KiB
TypeScript
import { randomBytes } from '@noble/hashes/utils'
|
|
import { secp256k1 } from '@noble/curves/secp256k1'
|
|
import { base64 } from '@scure/base'
|
|
import { getPublicKey } from 'nostr-tools'
|
|
|
|
export const utf8Decoder = new TextDecoder('utf-8')
|
|
export const utf8Encoder = new TextEncoder()
|
|
|
|
function toBase64(uInt8Array: Uint8Array) {
|
|
let strChunks = new Array(uInt8Array.length)
|
|
let i = 0
|
|
// @ts-ignore
|
|
for (let byte of uInt8Array) {
|
|
strChunks[i] = String.fromCharCode(byte) // bytes to utf16 string
|
|
i++
|
|
}
|
|
return btoa(strChunks.join(''))
|
|
}
|
|
|
|
function fromBase64(base64String: string) {
|
|
const binaryString = atob(base64String)
|
|
const length = binaryString.length
|
|
const bytes = new Uint8Array(length)
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
bytes[i] = binaryString.charCodeAt(i)
|
|
}
|
|
return bytes
|
|
}
|
|
|
|
function getNormalizedX(key: Uint8Array): Uint8Array {
|
|
return key.slice(1, 33)
|
|
}
|
|
|
|
export class Nip04 {
|
|
private cache = new Map<string, CryptoKey>()
|
|
|
|
public createKey(privkey: string, pubkey: string) {
|
|
const key = secp256k1.getSharedSecret(privkey, '02' + pubkey)
|
|
const normalizedKey = getNormalizedX(key)
|
|
return normalizedKey
|
|
}
|
|
|
|
private async getKey(privkey: string, pubkey: string, extractable?: boolean) {
|
|
const id = getPublicKey(privkey) + pubkey
|
|
let cryptoKey = this.cache.get(id)
|
|
if (cryptoKey) return cryptoKey
|
|
|
|
const key = this.createKey(privkey, pubkey)
|
|
cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'AES-CBC' }, !!extractable, ['encrypt', 'decrypt'])
|
|
this.cache.set(id, cryptoKey)
|
|
return cryptoKey
|
|
}
|
|
|
|
public async encrypt(privkey: string, pubkey: string, text: string): Promise<string> {
|
|
const t1 = Date.now()
|
|
const cryptoKey = await this.getKey(privkey, pubkey)
|
|
const t2 = Date.now()
|
|
let iv = Uint8Array.from(randomBytes(16))
|
|
let plaintext = utf8Encoder.encode(text)
|
|
let ciphertext = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, cryptoKey, plaintext)
|
|
const t3 = Date.now()
|
|
let ctb64 = base64.encode(new Uint8Array(ciphertext))
|
|
let ivb64 = base64.encode(new Uint8Array(iv.buffer))
|
|
// let ctb64 = toBase64(new Uint8Array(ciphertext))
|
|
// let ivb64 = toBase64(new Uint8Array(iv.buffer))
|
|
|
|
console.log('nip04_encrypt', text, 't1', t2 - t1, 't2', t3 - t2, 't3', Date.now() - t3)
|
|
|
|
return `${ctb64}?iv=${ivb64}`
|
|
}
|
|
|
|
public async decrypt(privkey: string, pubkey: string, data: string): Promise<string> {
|
|
let [ctb64, ivb64] = data.split('?iv=')
|
|
|
|
const cryptoKey = await this.getKey(privkey, pubkey)
|
|
|
|
let ciphertext = base64.decode(ctb64)
|
|
let iv = base64.decode(ivb64)
|
|
// let ciphertext = fromBase64(ctb64)
|
|
// let iv = fromBase64(ivb64)
|
|
|
|
let plaintext = await crypto.subtle.decrypt({ name: 'AES-CBC', iv }, cryptoKey, ciphertext)
|
|
|
|
let text = utf8Decoder.decode(plaintext)
|
|
return text
|
|
}
|
|
}
|