跳至主要內容
進階

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... 會自動變成用戶資料連結。

已複製連結
已複製到剪貼簿