入門
NIP-07 瀏覽器擴充
深入了解 Nostr 的瀏覽器擴充簽名標準,window.nostr API 和安全密鑰管理。
10 分鐘
什麼是 NIP-07?
NIP-07 定義了瀏覽器擴充功能與網頁應用之間的標準介面(window.nostr)。 這讓網頁應用可以請求簽名和加密操作,而不需要直接存取用戶的私鑰, 大幅提升安全性。
安全優勢: 私鑰永遠不會暴露給網頁應用。所有簽名操作都在擴充功能內部完成, 用戶可以在授權前審核每個請求。
window.nostr API
符合 NIP-07 的擴充功能會在 window 物件上注入 nostr 屬性:
interface Nostr {
// 取得公鑰
getPublicKey(): Promise<string>
// 簽署事件
signEvent(event: UnsignedEvent): Promise<SignedEvent>
// NIP-04 加密(可選)
nip04?: {
encrypt(pubkey: string, plaintext: string): Promise<string>
decrypt(pubkey: string, ciphertext: string): Promise<string>
}
// NIP-44 加密(可選)
nip44?: {
encrypt(pubkey: string, plaintext: string): Promise<string>
decrypt(pubkey: string, ciphertext: string): Promise<string>
}
// 取得中繼器列表(可選)
getRelays?(): Promise<RelayMap>
} 核心方法
getPublicKey()
取得用戶的公鑰(hex 格式):
// 檢查擴充功能是否存在
if (!window.nostr) {
alert('請安裝 Nostr 瀏覽器擴充功能')
return
}
// 取得公鑰
const pubkey = await window.nostr.getPublicKey()
console.log('公鑰:', pubkey)
// => "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" signEvent()
簽署未簽名的事件:
// 建立未簽名事件
const unsignedEvent = {
kind: 1,
created_at: Math.floor(Date.now() / 1000),
tags: [],
content: 'Hello, Nostr!'
// 注意:不包含 id, pubkey, sig
}
// 請求擴充功能簽名
const signedEvent = await window.nostr.signEvent(unsignedEvent)
console.log(signedEvent)
// {
// id: "abc123...",
// pubkey: "3bf0c63f...",
// created_at: 1704067200,
// kind: 1,
// tags: [],
// content: "Hello, Nostr!",
// sig: "sig123..."
// } 加密方法
NIP-04 加密
// 加密訊息
const recipientPubkey = "npub1..."
const encrypted = await window.nostr.nip04.encrypt(
recipientPubkey,
"這是一條私密訊息"
)
// => "encrypted_content?iv=..."
// 解密訊息
const decrypted = await window.nostr.nip04.decrypt(
senderPubkey,
encrypted
)
// => "這是一條私密訊息" NIP-44 加密(推薦)
// 檢查是否支援 NIP-44
if (window.nostr.nip44) {
const encrypted = await window.nostr.nip44.encrypt(
recipientPubkey,
"這是一條私密訊息"
)
const decrypted = await window.nostr.nip44.decrypt(
senderPubkey,
encrypted
)
} 完整使用範例
async function postNote(content) {
// 1. 檢查擴充功能
if (!window.nostr) {
throw new Error('請安裝 Nostr 擴充功能')
}
// 2. 取得公鑰
const pubkey = await window.nostr.getPublicKey()
// 3. 建立事件
const event = {
kind: 1,
created_at: Math.floor(Date.now() / 1000),
tags: [],
content
}
// 4. 簽名
const signedEvent = await window.nostr.signEvent(event)
// 5. 發送到中繼器
const relay = new WebSocket('wss://relay.damus.io')
relay.onopen = () => {
relay.send(JSON.stringify(['EVENT', signedEvent]))
}
return signedEvent
}
// 使用
postNote('Hello from my web app!')
.then(event => console.log('已發布:', event.id))
.catch(err => console.error('錯誤:', err)) 熱門擴充功能
Alby
支援 Lightning 和 Nostr
Chrome, Firefox, Safari
nos2x
輕量級 Nostr 簽名器
Chrome
Flamingo
Safari 專用擴充
Safari
Nostore
iOS Safari 擴充
iOS Safari
Spring
多帳號管理
Chrome, Firefox
Keys.band
密鑰管理器
Chrome
錯誤處理
async function safeGetPublicKey() {
try {
// 檢查擴充功能
if (typeof window.nostr === 'undefined') {
return { error: 'NO_EXTENSION', message: '未安裝擴充功能' }
}
// 請求公鑰(用戶可能拒絕)
const pubkey = await window.nostr.getPublicKey()
return { pubkey }
} catch (err) {
// 用戶拒絕授權
if (err.message?.includes('rejected') || err.message?.includes('denied')) {
return { error: 'USER_REJECTED', message: '用戶拒絕授權' }
}
// 其他錯誤
return { error: 'UNKNOWN', message: err.message }
}
}
// 使用
const result = await safeGetPublicKey()
if (result.error) {
console.log('錯誤:', result.message)
} else {
console.log('公鑰:', result.pubkey)
} TypeScript 類型定義
// types/nostr.d.ts
interface Window {
nostr?: Nostr
}
interface Nostr {
getPublicKey(): Promise<string>
signEvent(event: UnsignedEvent): Promise<Event>
getRelays?(): Promise<Record<string, RelayPolicy>>
nip04?: {
encrypt(pubkey: string, plaintext: string): Promise<string>
decrypt(pubkey: string, ciphertext: string): Promise<string>
}
nip44?: {
encrypt(pubkey: string, plaintext: string): Promise<string>
decrypt(pubkey: string, ciphertext: string): Promise<string>
}
}
interface UnsignedEvent {
kind: number
created_at: number
tags: string[][]
content: string
}
interface Event extends UnsignedEvent {
id: string
pubkey: string
sig: string
}
interface RelayPolicy {
read: boolean
write: boolean
} 安全最佳實踐
應該做
- • 總是檢查 window.nostr 是否存在
- • 處理用戶拒絕授權的情況
- • 使用 try-catch 包裝所有呼叫
- • 清楚告知用戶將簽名的內容
不應該做
- • 要求用戶輸入私鑰
- • 假設擴充功能一定存在
- • 忽略錯誤處理
- • 批量簽名而不讓用戶確認
功能偵測
function detectNostrCapabilities() {
if (!window.nostr) {
return { available: false }
}
return {
available: true,
getPublicKey: true,
signEvent: true,
nip04: !!window.nostr.nip04,
nip44: !!window.nostr.nip44,
getRelays: !!window.nostr.getRelays
}
}
const caps = detectNostrCapabilities()
console.log('Nostr 功能:', caps)
// {
// available: true,
// getPublicKey: true,
// signEvent: true,
// nip04: true,
// nip44: true,
// getRelays: true
// } 開發提示: 在開發時可以使用 nos2x 進行測試,因為它是開源且輕量的。 正式環境建議支援多種擴充功能,提供更好的用戶體驗。
已複製連結