remote-signer-key
is introduced, passed in bunker url, clients must differentiate between remote-signer-pubkey
and user-pubkey
, must call get_public_key
after connect, nip05 login is removed, create_account moved to another NIP.
秘密鍵は、システムアプリ、オペレーティングシステム、デバイスなど、システムが増えるごとに攻撃対象が増えるため、できるだけ少ないシステムに公開されるべきである。
このNIPはリモート署名器と通常のNostrクライアント間の双方向通信の方法について記述している。リモート署名器は、例えばNostrイベントに署名する専用のハードウェアデバイスで、クライアントは通常のNostrクライアントである。
- ユーザー: Nostrを使用しようとしている人。
- クライント: _ユーザー_が見たりボタンをクリックしたりするユーザー向けアプリケーション。
- リモート署名器: _クライアント_からのリクエストに応答するデーモンまたはサーバー。
- クライアント鍵ペア/公開鍵: _client_によって生成された鍵。コンテンツを暗号化し、_リモート署名器_と通信するために使用される。
- リモート署名器鍵ペア/公開鍵: _リモート署名器_がコンテンツを暗号化し、_クライアント_と通信するために使用される鍵。この鍵ペアは_ユーザー鍵ペア/公開鍵_と同じであっても良い (MAY) が、必ずしもそうである必要はない。
- ユーザー鍵ペア/公開鍵: _ユーザー_を表す実際の鍵。(例えば、
sign_event
リクエストに応じてイベントへ署名するために使用される。) 通常、_リモート署名器_がこれらの鍵を管理する。
このNIPで指定されたすべての公開鍵は16進数形式である。
- _クライアント_が
client-keypair
を生成する。この鍵ペアは主に使い捨てであるため_ユーザー_に伝える必要はない。クライアントはそれをローカルに保存することを選択できるが、ログアウト時に削除するべきである。 - 接続が確立され、 (下記参照) _リモート署名器_は
client-pubkey
を得て、_クライアント_はremote-signer-pubkey
を得る。 - _クライアント_は
client-keypair
を使用してremote-signer-pubkey
へp
タグを付け、暗号化して_リモート署名器_にリクエストを送信する。 - _リモート署名器_は
client-pubkey
へp
タグを付け、暗号化して クライアント に応答する。 - _クライアント_は
user-pubkey
を得るためにget_public_key
をリクエストする。
接続を開始するには、2つの方法がある。
_リモート署名器_は、接続トークンを以下の形式で提供する:
bunker://<remote-signer-pubkey>?relay=<wss://relay-to-connect-on>&relay=<wss://another-relay-to-connect-on>&secret=<optional-secret-value>
このトークンはユーザーによって_クライアント_に渡され、クライアントは指定されたリレーを介してリモート署名器にconnect
リクエストを送る。オプションのシークレットは、正常な1回の接続確立にのみ使用でき、_リモート署名者_は古いシークレットを用いて新たに接続を確立しようとする試みを無視するべきだ (SHOULD)。
クライアント はプロトコルとしてnostrconnect://
、オリジンとしてclient-pubkey
を使用する接続トークンを提供する。追加情報はクエリパラメータとして渡す必要がある:
relay
(必須) - クライアント が リモート署名器 からの応答を待ち受ける1以上のリレーURL。secret
(必須) - リモート署名器 が応答のresult
フィールドとして返す短いランダムな文字列。perms
(任意) - クライアント が リモート署名器 による承認を要求している権限のカンマ区切りのリスト。name
(任意) - クライアント アプリケーションの名前url
(任意) - クライアント アプリケーションの正規URLimage
(任意) - クライアント アプリケーションを表す小さな画像
以下に例を示す。
nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay1.example.com&perms=nip44_encrypt%2Cnip44_decrypt%2Csign_event%3A13%2Csign_event%3A14%2Csign_event%3A1059&name=My+Client&secret=0s8j2djs&relay=wss%3A%2F%2Frelay2.example2.com
このトークンはユーザーによって_リモート署名器_に渡され、リモート署名器は指定されたリレーを介してclient-pubkey
にconnect
応答イベントを送る。クライアントは接続応答の作成者からremote-signer-pubkey
を探す。接続相手のなりすましを防ぐためにsecret
値を指定する必要があり (MUST) _クライアント_はconnect
応答によって得られたsecret
を検証する必要がある (MUST)。
{
"kind": 24133,
"pubkey": <local_keypair_pubkey>,
"content": <nip04(<request>)>,
"tags": [["p", <remote-signer-pubkey>]],
}
The content
field is a JSON-RPC-like message that is NIP-04 encrypted and has the following structure:
{
"id": <random_string>,
"method": <method_name>,
"params": [array_of_strings]
}
id
is a random string that is a request ID. This same ID will be sent back in the response payload.method
is the name of the method/command (detailed below).params
is a positional array of string parameters.
Each of the following are methods that the client sends to the remote-signer.
Command | Params | Result |
---|---|---|
connect |
[<remote-signer-pubkey>, <optional_secret>, <optional_requested_permissions>] |
"ack" OR <required-secret-value> |
sign_event |
[<{kind, content, tags, created_at}>] |
json_stringified(<signed_event>) |
ping |
[] |
"pong" |
get_relays |
[] |
json_stringified({<relay_url>: {read: <boolean>, write: <boolean>}}) |
get_public_key |
[] |
<user-pubkey> |
nip04_encrypt |
[<third_party_pubkey>, <plaintext_to_encrypt>] |
<nip04_ciphertext> |
nip04_decrypt |
[<third_party_pubkey>, <nip04_ciphertext_to_decrypt>] |
<plaintext> |
nip44_encrypt |
[<third_party_pubkey>, <plaintext_to_encrypt>] |
<nip44_ciphertext> |
nip44_decrypt |
[<third_party_pubkey>, <nip44_ciphertext_to_decrypt>] |
<plaintext> |
The connect
method may be provided with optional_requested_permissions
for user convenience. The permissions are a comma-separated list of method[:params]
, i.e. nip04_encrypt,sign_event:4
meaning permissions to call nip04_encrypt
and to call sign_event
with kind:4
. Optional parameter for sign_event
is the kind number, parameters for other methods are to be defined later. Same permission format may be used for perms
field of metadata
in nostrconnect://
string.
{
"id": <id>,
"kind": 24133,
"pubkey": <remote-signer-pubkey>,
"content": <nip04(<response>)>,
"tags": [["p", <client-pubkey>]],
"created_at": <unix timestamp in seconds>
}
The content
field is a JSON-RPC-like message that is NIP-04 encrypted and has the following structure:
{
"id": <request_id>,
"result": <results_string>,
"error": <optional_error_string>
}
id
is the request ID that this response is for.results
is a string of the result of the call (this can be either a string or a JSON stringified object)error
, optionally, it is an error in string form, if any. Its presence indicates an error with the request.
remote-signer-pubkey
isfa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52
user-pubkey
is alsofa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52
client-pubkey
iseff37350d839ce3707332348af4549a96051bd695d3223af4aabce4993531d86
{
"kind": 24133,
"pubkey": "eff37350d839ce3707332348af4549a96051bd695d3223af4aabce4993531d86",
"content": nip04({
"id": <random_string>,
"method": "sign_event",
"params": [json_stringified(<{
content: "Hello, I'm signing remotely",
kind: 1,
tags: [],
created_at: 1714078911
}>)]
}),
"tags": [["p", "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52"]], // p-tags the remote-signer-pubkey
}
{
"kind": 24133,
"pubkey": "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52",
"content": nip04({
"id": <random_string>,
"result": json_stringified(<signed-event>)
}),
"tags": [["p", "eff37350d839ce3707332348af4549a96051bd695d3223af4aabce4993531d86"]], // p-tags the client-pubkey
}
An Auth Challenge is a response that a remote-signer can send back when it needs the user to authenticate via other means. The response content
object will take the following form:
{
"id": <request_id>,
"result": "auth_url",
"error": <URL_to_display_to_end_user>
}
client should display (in a popup or new tab) the URL from the error
field and then subscribe/listen for another response from the remote-signer (reusing the same request ID). This event will be sent once the user authenticates in the other window (or will never arrive if the user doesn't authenticate).
remote-signer MAY publish it's metadata by using NIP-05 and NIP-89. With NIP-05, a request to <remote-signer>/.well-known/nostr.json?name=_
MAY return this:
{
"names":{
"_": <remote-signer-app-pubkey>,
},
"nip46": {
"relays": ["wss://relay1","wss://relay2"...],
"nostrconnect_url": "https://remote-signer-domain.com/<nostrconnect>"
}
}
The <remote-signer-app-pubkey>
MAY be used to verify the domain from remote-signer's NIP-89 event (see below). relays
SHOULD be used to construct a more precise nostrconnect://
string for the specific remote-signer
. nostrconnect_url
template MAY be used to redirect users to remote-signer's connection flow by replacing <nostrconnect>
placeholder with an actual nostrconnect://
string.
remote-signer MAY publish a NIP-89 kind: 31990
event with k
tag of 24133
, which MAY also include one or more relay
tags and MAY include nostrconnect_url
tag. The semantics of relay
and nostrconnect_url
tags are the same as in the section above.
client MAY improve UX by discovering remote-signers using their kind: 31990
events. client MAY then pre-generate nostrconnect://
strings for the remote-signers, and SHOULD in that case verify that kind: 31990
event's author is mentioned in signer's nostr.json?name=_
file as <remote-signer-app-pubkey>
.