Damusの実装からNostrの技術を追う
https://github.com/GigaBitcoin/secp256k1.swift は楕円曲線「secp256k1」のためのライブラリで、鍵の生成と「シュノア署名」の実装が含まれている。実態はC言語で組まれたlibsecp256k1のSwiftラッパーである。SwiftのAPIはCryptoKitに似ている。
// 秘密鍵の生成
let privateKey = try! secp256k1.Signing.PrivateKey()
// 秘密鍵から公開鍵を取得
let publicKey = privateKey.publicKey
// 秘密鍵でデータを署名
let data = Data([1, 2, 3, 4])
let signature = try! privateKey.schnorr.signature(for: data)
// 公開鍵で署名を検証
publicKey.schnorr.isValidSignature(signature, for: data) // true
上記のコードのように、秘密鍵があればそこから公開鍵を計算して取り出すことができる。反対に、公開鍵から秘密鍵を計算することは非常に困難であり、その性質が楕円曲線暗号の根幹となっている。
楕円曲線暗号における秘密鍵と公開鍵の生成に関しては以下の記事がわかりやすい。
Nostrでは公開鍵として「x-only Public Key」が使われている。secp256k1の場合、秘密鍵は32バイトであるのに対して、公開鍵は非圧縮形式で65バイト、圧縮形式で33バイトとなるが、x座標の情報だけを使うことで32バイトにすることができる。その方式の公開鍵をx-only Public Keyという。
Damusでもx-only Public Keyを使っていることは、一番最初に取り上げたコードのこの部分でわかる。
生成された秘密鍵と公開鍵は、Nostrのプロトコル上では32バイトの情報としてそのまま使われるが、人間の目に触れる部分では「Bech32」という形式で表現される。
Bech32のエンコードをしている箇所
Bech32のデコードをしている箇所
Bech32によるエンコードを簡単にいうと、バイト列を先頭から5ビットずつ取り出して、それぞれを32種類(=5ビット)の文字列に置き換える処理。先頭に任意の文字列とセパレータを意味する1
が、最後にチェックサムがつく。大文字小文字は区別されない。
NostrではNIP-19によって秘密鍵はnsec
、公開鍵はnpub
のプレフィックスではじまるように定義されており、文字列から鍵の種類を判別できるようになっている。