Compare commits

...

8 Commits

Author SHA1 Message Date
fiatjaf
63ccc8b4c8 v2.3.1 2024-02-19 18:54:40 -03:00
fiatjaf
7cf7df88db nip46: skip duplicates on fetchBunkerProviders (prev fetchCustodialBunkers). 2024-02-19 18:54:18 -03:00
fiatjaf
bded539122 nip46: fix messages being ignored after auth_url. 2024-02-19 18:53:48 -03:00
fiatjaf
3647bbd68a get rid of the last vestiges of webcrypto dependencies. 2024-02-17 18:29:01 -03:00
fiatjaf
fb085ffdf7 v2.3.0 2024-02-17 18:19:52 -03:00
fiatjaf
280d483ef4 adjust expected value in nip11 test. 2024-02-17 18:19:09 -03:00
fiatjaf
54b55b98f1 nip44: get rid of ensureBytes() since it was removed from upstream library. 2024-02-17 18:18:24 -03:00
fiatjaf
84f9881812 use @noble/ciphers instead of webcrypto on nip04. 2024-02-17 18:15:42 -03:00
8 changed files with 30 additions and 47 deletions

View File

@@ -1,18 +1,9 @@
import { test, expect } from 'bun:test'
import crypto from 'node:crypto'
import { encrypt, decrypt } from './nip04.ts'
import { getPublicKey, generateSecretKey } from './pure.ts'
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
try {
// @ts-ignore
// eslint-disable-next-line no-undef
globalThis.crypto = crypto
} catch (err) {
/***/
}
test('encrypt and decrypt message', async () => {
let sk1 = generateSecretKey()
let sk2 = generateSecretKey()

View File

@@ -1,15 +1,10 @@
import { bytesToHex, randomBytes } from '@noble/hashes/utils'
import { secp256k1 } from '@noble/curves/secp256k1'
import { cbc } from '@noble/ciphers/aes'
import { base64 } from '@scure/base'
import { utf8Decoder, utf8Encoder } from './utils.ts'
// @ts-ignore
if (typeof crypto !== 'undefined' && !crypto.subtle && crypto.webcrypto) {
// @ts-ignore
crypto.subtle = crypto.webcrypto.subtle
}
export async function encrypt(secretKey: string | Uint8Array, pubkey: string, text: string): Promise<string> {
const privkey: string = secretKey instanceof Uint8Array ? bytesToHex(secretKey) : secretKey
const key = secp256k1.getSharedSecret(privkey, '02' + pubkey)
@@ -17,8 +12,9 @@ export async function encrypt(secretKey: string | Uint8Array, pubkey: string, te
let iv = Uint8Array.from(randomBytes(16))
let plaintext = utf8Encoder.encode(text)
let cryptoKey = await crypto.subtle.importKey('raw', normalizedKey, { name: 'AES-CBC' }, false, ['encrypt'])
let ciphertext = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, cryptoKey, plaintext)
let ciphertext = cbc(normalizedKey, iv).encrypt(plaintext)
let ctb64 = base64.encode(new Uint8Array(ciphertext))
let ivb64 = base64.encode(new Uint8Array(iv.buffer))
@@ -31,14 +27,12 @@ export async function decrypt(secretKey: string | Uint8Array, pubkey: string, da
let key = secp256k1.getSharedSecret(privkey, '02' + pubkey)
let normalizedKey = getNormalizedX(key)
let cryptoKey = await crypto.subtle.importKey('raw', normalizedKey, { name: 'AES-CBC' }, false, ['decrypt'])
let ciphertext = base64.decode(ctb64)
let iv = base64.decode(ivb64)
let ciphertext = base64.decode(ctb64)
let plaintext = await crypto.subtle.decrypt({ name: 'AES-CBC', iv }, cryptoKey, ciphertext)
let plaintext = cbc(normalizedKey, iv).decrypt(ciphertext)
let text = utf8Decoder.decode(plaintext)
return text
return utf8Decoder.decode(plaintext)
}
function getNormalizedX(key: Uint8Array): Uint8Array {

View File

@@ -9,7 +9,7 @@ describe('requesting relay as for NIP11', () => {
test('testing a relay', async () => {
const info = await fetchRelayInformation('wss://atlas.nostr.land')
expect(info.name).toEqual('nostr.land')
expect(info.description).toEqual('nostr.land family of relays (us-or-01)')
expect(info.description).toContain('nostr.land family')
expect(info.fees).toBeTruthy()
expect(info.supported_nips).toEqual([1, 2, 4, 9, 11, 12, 16, 20, 22, 28, 33, 40])
expect(info.software).toEqual('custom')

View File

@@ -1,5 +1,5 @@
import { chacha20 } from '@noble/ciphers/chacha'
import { ensureBytes, equalBytes } from '@noble/ciphers/utils'
import { equalBytes } from '@noble/ciphers/utils'
import { secp256k1 } from '@noble/curves/secp256k1'
import { extract as hkdf_extract, expand as hkdf_expand } from '@noble/hashes/hkdf'
import { hmac } from '@noble/hashes/hmac'
@@ -23,8 +23,6 @@ const u = {
},
getMessageKeys(conversationKey: Uint8Array, nonce: Uint8Array) {
ensureBytes(conversationKey, 32)
ensureBytes(nonce, 32)
const keys = hkdf_expand(sha256, conversationKey, nonce, 76)
return {
chacha_key: keys.subarray(0, 32),

View File

@@ -118,7 +118,7 @@ export class BunkerSigner {
const { id, result, error } = JSON.parse(await decrypt(clientSecretKey, event.pubkey, event.content))
if (result === 'auth_url' && waitingForAuth[id]) {
delete listeners[id]
delete waitingForAuth[id]
if (params.onauth) {
params.onauth(error)
@@ -279,22 +279,35 @@ export async function createAccount(
return rpc
}
// @deprecated use fetchBunkerProviders instead
export const fetchCustodialBunkers = fetchBunkerProviders
/**
* Fetches info on available providers that announce themselves using NIP-89 events.
* @returns A promise that resolves to an array of available bunker objects.
*/
export async function fetchCustodialBunkers(pool: AbstractSimplePool, relays: string[]): Promise<BunkerProfile[]> {
export async function fetchBunkerProviders(pool: AbstractSimplePool, relays: string[]): Promise<BunkerProfile[]> {
const events = await pool.querySync(relays, {
kinds: [Handlerinformation],
'#k': [NostrConnect.toString()],
})
events.sort((a, b) => b.created_at - a.created_at)
// validate bunkers by checking their NIP-05 and pubkey
// map to a more useful object
const validatedBunkers = await Promise.all(
events.map(async event => {
events.map(async (event, i) => {
try {
const content = JSON.parse(event.content)
// skip duplicates
try {
if (events.findIndex(ev => JSON.parse(ev.content).nip05 === content.nip05) !== i) return undefined
} catch (err) {
/***/
}
const bp = await queryBunkerProfile(content.nip05)
if (bp && bp.pubkey === event.pubkey && bp.relays.length) {
return {

View File

@@ -1,14 +1,9 @@
import crypto from 'node:crypto'
import { describe, test, expect } from 'bun:test'
import { hexToBytes } from '@noble/hashes/utils'
import { makeNwcRequestEvent, parseConnectionString } from './nip47'
import { decrypt } from './nip04.ts'
import { NWCWalletRequest } from './kinds.ts'
// @ts-ignore
// eslint-disable-next-line no-undef
globalThis.crypto = crypto
describe('parseConnectionString', () => {
test('returns pubkey, relay, and secret if connection string is valid', () => {
const connectionString =

View File

@@ -1,5 +1,7 @@
import { sha256 } from '@noble/hashes/sha256'
import { EventTemplate } from './core'
import { FileServerPreference } from './kinds'
import { bytesToHex } from '@noble/hashes/utils'
/**
* Represents the configuration for a server compliant with NIP-96.
@@ -576,15 +578,5 @@ export function generateFSPEventTemplate(serverUrls: string[]): EventTemplate {
* @returns A promise that resolves to the SHA-256 hash of the file.
*/
export async function calculateFileHash(file: Blob): Promise<string> {
// Read the file as an ArrayBuffer
const buffer = await file.arrayBuffer()
// Calculate the SHA-256 hash of the file
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer)
// Convert the hash to a hexadecimal string
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
return hashHex
return bytesToHex(sha256(new Uint8Array(await file.arrayBuffer())))
}

View File

@@ -1,7 +1,7 @@
{
"type": "module",
"name": "nostr-tools",
"version": "2.2.1",
"version": "2.3.1",
"description": "Tools for making a Nostr client.",
"repository": {
"type": "git",
@@ -208,7 +208,7 @@
},
"license": "Unlicense",
"dependencies": {
"@noble/ciphers": "0.2.0",
"@noble/ciphers": "^0.5.1",
"@noble/curves": "1.2.0",
"@noble/hashes": "1.3.1",
"@scure/base": "1.1.1",