feat: migrate NIP-51 list encryption from NIP-04 to NIP-44
NIP-04 encryption is deprecated due to security vulnerabilities. This migrates MuteList (kind 10000) and PinnedUsers (kind 10010) private entries to use NIP-44 encryption, with backward compatibility for reading existing NIP-04 encrypted content. When NIP-04 content is detected, it is automatically re-encrypted with NIP-44 and republished to gradually migrate users. Co-Authored-By: captain-stacks <201298974+captain-stacks@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4fb40e81b3
commit
2efc884e01
9 changed files with 191 additions and 32 deletions
|
|
@ -68,6 +68,20 @@ export class BunkerSigner implements ISigner {
|
|||
return await this.signer.nip04Decrypt(pubkey, cipherText)
|
||||
}
|
||||
|
||||
async nip44Encrypt(pubkey: string, plainText: string) {
|
||||
if (!this.signer) {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
return await this.signer.nip44Encrypt(pubkey, plainText)
|
||||
}
|
||||
|
||||
async nip44Decrypt(pubkey: string, cipherText: string) {
|
||||
if (!this.signer) {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
return await this.signer.nip44Decrypt(pubkey, cipherText)
|
||||
}
|
||||
|
||||
getClientSecretKey() {
|
||||
return bytesToHex(this.clientSecretKey)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ type TNostrContext = {
|
|||
signEvent: (draftEvent: TDraftEvent) => Promise<VerifiedEvent>
|
||||
nip04Encrypt: (pubkey: string, plainText: string) => Promise<string>
|
||||
nip04Decrypt: (pubkey: string, cipherText: string) => Promise<string>
|
||||
nip44Encrypt: (pubkey: string, plainText: string) => Promise<string>
|
||||
nip44Decrypt: (pubkey: string, cipherText: string) => Promise<string>
|
||||
startLogin: () => void
|
||||
checkLogin: <T>(cb?: () => T) => Promise<T | void>
|
||||
updateRelayListEvent: (relayListEvent: Event) => Promise<void>
|
||||
|
|
@ -730,6 +732,14 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||
return signer?.nip04Decrypt(pubkey, cipherText) ?? ''
|
||||
}
|
||||
|
||||
const nip44Encrypt = async (pubkey: string, plainText: string) => {
|
||||
return signer?.nip44Encrypt(pubkey, plainText) ?? ''
|
||||
}
|
||||
|
||||
const nip44Decrypt = async (pubkey: string, cipherText: string) => {
|
||||
return signer?.nip44Decrypt(pubkey, cipherText) ?? ''
|
||||
}
|
||||
|
||||
const checkLogin = async <T,>(cb?: () => T): Promise<T | void> => {
|
||||
if (signer) {
|
||||
return cb && cb()
|
||||
|
|
@ -857,6 +867,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
|
|||
signHttpAuth,
|
||||
nip04Encrypt,
|
||||
nip04Decrypt,
|
||||
nip44Encrypt,
|
||||
nip44Decrypt,
|
||||
startLogin: () => setOpenLoginDialog(true),
|
||||
checkLogin,
|
||||
signEvent,
|
||||
|
|
|
|||
|
|
@ -57,4 +57,24 @@ export class Nip07Signer implements ISigner {
|
|||
}
|
||||
return await this.signer.nip04.decrypt(pubkey, cipherText)
|
||||
}
|
||||
|
||||
async nip44Encrypt(pubkey: string, plainText: string) {
|
||||
if (!this.signer) {
|
||||
throw new Error('Should call init() first')
|
||||
}
|
||||
if (!this.signer.nip44?.encrypt) {
|
||||
throw new Error('The extension you are using does not support nip44 encryption')
|
||||
}
|
||||
return await this.signer.nip44.encrypt(pubkey, plainText)
|
||||
}
|
||||
|
||||
async nip44Decrypt(pubkey: string, cipherText: string) {
|
||||
if (!this.signer) {
|
||||
throw new Error('Should call init() first')
|
||||
}
|
||||
if (!this.signer.nip44?.decrypt) {
|
||||
throw new Error('The extension you are using does not support nip44 decryption')
|
||||
}
|
||||
return await this.signer.nip44.decrypt(pubkey, cipherText)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,20 @@ export class NostrConnectionSigner implements ISigner {
|
|||
return await this.signer.nip04Decrypt(pubkey, cipherText)
|
||||
}
|
||||
|
||||
async nip44Encrypt(pubkey: string, plainText: string) {
|
||||
if (!this.signer) {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
return await this.signer.nip44Encrypt(pubkey, plainText)
|
||||
}
|
||||
|
||||
async nip44Decrypt(pubkey: string, cipherText: string) {
|
||||
if (!this.signer) {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
return await this.signer.nip44Decrypt(pubkey, cipherText)
|
||||
}
|
||||
|
||||
getClientSecretKey() {
|
||||
return bytesToHex(this.clientSecretKey)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,4 +31,12 @@ export class NpubSigner implements ISigner {
|
|||
async nip04Decrypt(): Promise<any> {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
|
||||
async nip44Encrypt(): Promise<any> {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
|
||||
async nip44Decrypt(): Promise<any> {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { ISigner, TDraftEvent } from '@/types'
|
||||
import { finalizeEvent, getPublicKey as nGetPublicKey, nip04, nip19 } from 'nostr-tools'
|
||||
import { v2 as nip44 } from 'nostr-tools/nip44'
|
||||
|
||||
export class NsecSigner implements ISigner {
|
||||
private privkey: Uint8Array | null = null
|
||||
|
|
@ -50,4 +51,20 @@ export class NsecSigner implements ISigner {
|
|||
}
|
||||
return nip04.decrypt(this.privkey, pubkey, cipherText)
|
||||
}
|
||||
|
||||
async nip44Encrypt(pubkey: string, plainText: string) {
|
||||
if (!this.privkey) {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
const conversationKey = nip44.utils.getConversationKey(this.privkey, pubkey)
|
||||
return nip44.encrypt(plainText, conversationKey)
|
||||
}
|
||||
|
||||
async nip44Decrypt(pubkey: string, cipherText: string) {
|
||||
if (!this.privkey) {
|
||||
throw new Error('Not logged in')
|
||||
}
|
||||
const conversationKey = nip44.utils.getConversationKey(this.privkey, pubkey)
|
||||
return nip44.decrypt(cipherText, conversationKey)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue