進階
NIP-19 bech32 編碼
深入了解 Nostr 的 bech32 編碼標準,包括 npub、nsec、note、nprofile、nevent、naddr 等實體編碼格式。
10 分鐘
什麼是 NIP-19?
NIP-19 定義了 Nostr 中各種實體的 bech32 編碼格式。這些格式讓用戶可以方便地分享公鑰、 事件 ID 和其他資訊,同時包含校驗碼防止輸入錯誤。所有 NIP-19 編碼都以特定前綴開頭, 讓應用程式可以識別其類型。
Bech32 優勢: 採用人類可讀的前綴、不區分大小寫、內建錯誤檢測, 且不會混淆相似字元(如 0/O、1/l)。
基本編碼類型
| 前綴 | 全名 | 內容 | 用途 |
|---|---|---|---|
| npub | Nostr Public Key | 32 bytes 公鑰 | 分享個人資料 |
| nsec | Nostr Secret Key | 32 bytes 私鑰 | 備份/導入(勿分享) |
| note | Note ID | 32 bytes 事件 ID | 分享單一貼文 |
安全警告: nsec 是你的私鑰,絕對不要分享給任何人! 如果有人取得你的 nsec,他們可以完全控制你的帳號。
TLV 編碼類型
進階編碼使用 TLV(Type-Length-Value)格式,可以包含額外的中繼資料:
| 前綴 | 包含資訊 | 用途 |
|---|---|---|
| nprofile | 公鑰 + 中繼器列表 | 確保能找到用戶 |
| nevent | 事件 ID + 中繼器 + 作者 | 確保能找到事件 |
| naddr | kind + 作者 + d-tag + 中繼器 | 可替換事件地址 |
| nrelay | 中繼器 URL | 分享中繼器 |
TLV 欄位類型
// TLV 類型定義
0: special // 主要資料(pubkey 或 event id)
1: relay // 中繼器 URL(可多個)
2: author // 作者公鑰(用於 nevent)
3: kind // 事件類型(用於 naddr)
// nprofile 結構
{
type: 0, data: <32 bytes pubkey>
type: 1, data: "wss://relay1.example.com"
type: 1, data: "wss://relay2.example.com"
}
// nevent 結構
{
type: 0, data: <32 bytes event id>
type: 1, data: "wss://relay.example.com"
type: 2, data: <32 bytes author pubkey>
}
// naddr 結構(可替換事件)
{
type: 0, data: "article-slug" // d-tag
type: 1, data: "wss://relay.example.com"
type: 2, data: <32 bytes author pubkey>
type: 3, data: <4 bytes kind, big-endian>
} 程式碼範例
編碼與解碼
import { nip19 } from 'nostr-tools'
// 編碼公鑰為 npub
const pubkey = '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'
const npub = nip19.npubEncode(pubkey)
// npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6
// 解碼 npub
const decoded = nip19.decode(npub)
// { type: 'npub', data: '3bf0c63f...' }
// 編碼事件 ID 為 note
const eventId = 'e0b0f76c...'
const note = nip19.noteEncode(eventId)
// 編碼私鑰為 nsec(謹慎處理!)
const privkey = '...'
const nsec = nip19.nsecEncode(privkey) TLV 編碼
import { nip19 } from 'nostr-tools'
// 建立 nprofile(帶中繼器資訊的用戶資料)
const nprofile = nip19.nprofileEncode({
pubkey: '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d',
relays: [
'wss://relay.damus.io',
'wss://nos.lol'
]
})
// 建立 nevent(帶中繼器資訊的事件)
const nevent = nip19.neventEncode({
id: 'e0b0f76c...',
relays: ['wss://relay.example.com'],
author: '3bf0c63f...'
})
// 建立 naddr(可替換事件地址)
const naddr = nip19.naddrEncode({
identifier: 'my-article-slug', // d-tag
pubkey: '3bf0c63f...',
kind: 30023, // long-form content
relays: ['wss://relay.example.com']
})
// 解碼任何 NIP-19 字串
const result = nip19.decode(nprofile)
// {
// type: 'nprofile',
// data: {
// pubkey: '3bf0c63f...',
// relays: ['wss://relay.damus.io', 'wss://nos.lol']
// }
// } 通用解碼器
import { nip19 } from 'nostr-tools'
function parseNostrEntity(input) {
try {
const decoded = nip19.decode(input)
switch (decoded.type) {
case 'npub':
return { type: 'user', pubkey: decoded.data }
case 'nprofile':
return {
type: 'user',
pubkey: decoded.data.pubkey,
relays: decoded.data.relays
}
case 'note':
return { type: 'event', id: decoded.data }
case 'nevent':
return {
type: 'event',
id: decoded.data.id,
relays: decoded.data.relays,
author: decoded.data.author
}
case 'naddr':
return {
type: 'address',
kind: decoded.data.kind,
pubkey: decoded.data.pubkey,
identifier: decoded.data.identifier,
relays: decoded.data.relays
}
case 'nsec':
console.warn('處理私鑰!請謹慎!')
return { type: 'privkey', data: decoded.data }
default:
return { type: 'unknown', data: decoded }
}
} catch (e) {
return { type: 'invalid', error: e.message }
}
}
// 使用
const entity = parseNostrEntity('npub1...')
if (entity.type === 'user') {
console.log('公鑰:', entity.pubkey)
} 範例編碼
// npub 範例(fiatjaf)
npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6
// 對應的 hex 公鑰
3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d
// nprofile 範例(同一用戶,帶中繼器)
nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p
// nevent 範例
nevent1qqst8cujky046negxgwwm5ynqwn53t8aqjr6afd8g59nfqwxpdhylpcpzamhxue69uhhyetvv9ujuetd9ehx7um5wge5wqhh5...
// naddr 範例(長文章)
naddr1qqxnzdesxqmnxvpexqunzvpcqy28wumn8ghj7un9d3shjtnyv9kh2uewd9hj7qg4waehxw309ahx7um5wghx6at5d9h8jampd3kx2apwvdhk6... 使用場景對比
| 場景 | 推薦格式 | 原因 |
|---|---|---|
| 分享個人資料 | nprofile | 包含中繼器,更容易找到 |
| 簡單顯示公鑰 | npub | 簡短易讀 |
| 分享貼文連結 | nevent | 包含中繼器和作者 |
| 分享文章連結 | naddr | 可替換事件需要 naddr |
| 應用內部使用 | hex | 無需編碼開銷 |
最佳實踐
建議做法
- • 對外分享時使用 nprofile/nevent
- • 顯示時優先使用 bech32 格式
- • 解碼時處理所有可能的類型
- • 驗證編碼的校驗碼
避免做法
- • 永遠不要分享 nsec
- • 不要在日誌中記錄私鑰
- • 不要假設編碼類型
- • 不要忽略解碼錯誤
提示: 大多數 Nostr 客戶端會自動解析貼文中的 NIP-19 編碼並轉換為可點擊的連結。 例如輸入 npub1... 會自動變成用戶資料連結。
已複製連結