12<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>NIP-55 - Android Signer Application — Nostr Instruction Manual</title>7<meta name="description" content="NIP-55: Android signer applications. Intents, Content Resolver, and web flows that let a client request signatures without holding the private key. Complete manual page.">8<link rel="preload" href="../assets/fonts/michroma-400-latin.woff2" as="font" type="font/woff2" crossorigin>9<link rel="preload" href="../assets/fonts/exo2-var-latin.woff2" as="font" type="font/woff2" crossorigin>10<link rel="preload" href="../assets/fonts/sairacondensed-600-latin.woff2" as="font" type="font/woff2" crossorigin>11<link rel="preload" href="../assets/fonts/sairacondensed-700-latin.woff2" as="font" type="font/woff2" crossorigin>12<link rel="preload" href="../assets/fonts/sairacondensed-500-latin.woff2" as="font" type="font/woff2" crossorigin>13<link rel="preload" href="../assets/fonts/sharetechmono-400-latin.woff2" as="font" type="font/woff2" crossorigin>14<link rel="preload" href="../assets/fonts/exo2-400i-latin.woff2" as="font" type="font/woff2" crossorigin>15<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">16<link rel="stylesheet" href="../assets/manual.css">17</head>18<body>1920<svg width="0" height="0" style="position:absolute">21 <defs>22 <marker id="arr" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">23 <path d="M1,1 L8,4.5 L1,8 Z" fill="#f26a00"/>24 </marker>25 <marker id="arrb" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">26 <path d="M1,1 L8,4.5 L1,8 Z" fill="#2e5f9e"/>27 </marker>28 </defs>29</svg>3031<div class="page">32 <span class="staple s1"></span><span class="staple s2"></span>3334 <header class="band">35 <a class="logo" href="../index.html">NOSTR<b>▸</b>PROTOCOL MANUAL</a>36 <span class="booklet-note">Chapter 55 - Key Security</span>37 </header>38 <nav class="tabs">39 <a href="../index.html">Contents</a>40 <a href="#overview" class="on">Overview</a>41 <a href="#spec">Spec</a>42 <a href="#examples">Examples</a>43 <a href="#history">History</a>44 </nav>4546 <div class="chapter">47 <div class="nipno" data-n="55">55</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 55</div>50 <h1>Android Signer Application</h1>51 <p class="sub">A dedicated Android app holds the private key. Clients on the same device ask it to sign, encrypt, and decrypt — and never see the key.</p>52 </div>53 </div>5455 <main class="spread">56 <div class="main">5758 <h2 class="bar" id="overview"><span class="num">1</span> Overview</h2>59 <p>Every Nostr action needs a signature. If every client app stores the private key, one bad app leaks it. This NIP moves the key into one trusted app: the <strong>signer</strong>.</p>60 <p>The signer is an Android application. It holds the user's private key and answers requests from a <strong>client</strong>. The client is a user-facing application that sends signing requests. It may be another Android application or a web page. The two apps run on the same device and talk over standard Android mechanisms. The client never handles the private key.</p>61 <p>Communication is 2-way. The client sends a request with a method name and a payload. The signer asks the user for approval, or answers silently if the user pre-approved the method. Then it returns a result to the client.</p>6263 <div class="box hint">64 <span class="tag">Hint!</span>65 <p>This chapter is the local, same-device cousin of <a href="46.html">NIP-46</a> remote signing and the <a href="07.html">NIP-07</a> browser extension. Same idea, different transport: Android Intents instead of relays or <code>window.nostr</code>.</p>66 </div>6768 <figure class="diagram">69 <div class="frame">70 <svg viewBox="0 0 560 210" role="img" aria-label="An Android client reaches the signer by Intent or Content Resolver; a web page reaches it by a nostrsigner URL; results return by extras, cursor, or callback URL">71 <rect class="d-actor" x="10" y="25" width="140" height="50" rx="8"/>72 <text class="d-actor-label" x="80" y="54" text-anchor="middle">Android Client</text>73 <rect class="d-actor" x="10" y="135" width="140" height="50" rx="8"/>74 <text class="d-actor-label" x="80" y="164" text-anchor="middle">Web Page</text>75 <rect class="d-actor" x="415" y="80" width="135" height="50" rx="8"/>76 <text class="d-actor-label" x="482" y="103" text-anchor="middle">Signer</text>77 <text class="d-note" x="482" y="120" text-anchor="middle">holds the key</text>78 <path class="d-msg d-flow" d="M152,40 C280,30 340,60 413,88"/>79 <path class="d-msg d-flow" d="M152,62 C260,70 330,85 413,98"/>80 <path class="d-msg d-flow" d="M152,160 C280,175 340,140 413,115"/>81 <path class="d-msg blue" d="M415,124 C330,160 250,180 152,172"/>82 <text class="d-label" x="270" y="26" text-anchor="middle">Intent (user approves)</text>83 <text class="d-label" x="270" y="82" text-anchor="middle">Content Resolver (background)</text>84 <text class="d-label" x="270" y="132" text-anchor="middle">nostrsigner: URL</text>85 <text class="d-label" x="270" y="200" text-anchor="middle">callbackUrl / clipboard</text>86 <circle r="4" fill="#f26a00">87 <animateMotion dur="2.6s" repeatCount="indefinite" path="M152,40 C280,30 340,60 413,88"/>88 </circle>89 <circle r="4" fill="#2e5f9e">90 <animateMotion dur="2.6s" begin="1.3s" repeatCount="indefinite" path="M415,124 C330,160 250,180 152,172"/>91 </circle>92 </svg>93 </div>94 <figcaption>Fig. 55-1 — <b>Three transports, one signer.</b> Android clients use Intents or the Content Resolver. Web pages use a <code>nostrsigner:</code> URL and get the result back by callback or clipboard.</figcaption>95 </figure>9697 <h2 class="bar" id="spec"><span class="num">2</span> Roles and Terms</h2>98 <p>The spec uses five terms. Use them exactly as defined here. All public keys in this NIP are hex strings.</p>99 <div class="tscroll"><table class="ref">100 <caption>Terminology</caption>101 <tr><th>Term</th><th>Meaning</th></tr>102 <tr><td class="btn">user</td><td>A person trying to use Nostr.</td></tr>103 <tr><td class="btn">client</td><td>A user-facing application that sends signing requests.</td></tr>104 <tr><td class="btn">signer</td><td>An Android application that holds the user's private key and answers requests from the client.</td></tr>105 <tr><td class="btn">user-pubkey</td><td>The public key that represents the user. The signer returns it from <code>get_public_key</code>.</td></tr>106 <tr><td class="btn">package name</td><td>The Android package name of the signer, for example <code>com.example.signer</code>. The client uses it to address all later requests.</td></tr>107 </table></div>108109 <p>A client can talk to the signer in three ways:</p>110 <ul>111 <li><strong>Intents</strong> — for Android clients. The signer opens. The user approves or rejects the request manually. The result returns to the client.</li>112 <li><strong>Content Resolver</strong> — for Android clients. The request runs in the background and the signer does not open. This works only for permissions the user chose to remember.</li>113 <li><strong>Web</strong> — for web clients. The signer returns the result through a <code>callbackUrl</code>, or copies it to the clipboard.</li>114 </ul>115116 <h2 class="bar" id="setup"><span class="num">3</span> Setup and Connection</h2>117 <p>An Android client must declare the <code>nostrsigner</code> scheme in its <code>AndroidManifest.xml</code>. Without this declaration the client cannot detect or call the signer.</p>118 <div class="crt">119 <div class="titlebar">AndroidManifest.xml - required queries block</div>120<pre><<span class="k">queries</span>>121 <<span class="k">intent</span>>122 <<span class="k">action</span> android:name=<span class="s">"android.intent.action.VIEW"</span> />123 <<span class="k">category</span> android:name=<span class="s">"android.intent.category.BROWSABLE"</span> />124 <<span class="k">data</span> android:scheme=<span class="s">"nostrsigner"</span> />125 </<span class="k">intent</span>>126</<span class="k">queries</span>></pre>127 </div>128 <p>The client can then check for an installed signer. Query the package manager for activities that handle a <code>nostrsigner:</code> view intent:</p>129 <div class="crt">130 <div class="titlebar">Kotlin - signer detection</div>131<pre><span class="k">fun</span> isExternalSignerInstalled(context: Context): Boolean {132 <span class="k">val</span> intent = Intent(Intent.ACTION_VIEW, Uri.parse(<span class="s">"nostrsigner:"</span>))133 <span class="k">return</span> context.packageManager.queryIntentActivities(intent, <span class="c">0</span>).isNotEmpty()134}</pre>135 </div>136137 <h3>Initiating a connection</h3>138 <p>The connection handshake has four steps:</p>139 <ol>140 <li>The client sends a <code>get_public_key</code> request.</li>141 <li>The signer responds with the <code>user-pubkey</code> and its <code>package name</code>.</li>142 <li>The client stores both. It SHOULD NOT call <code>get_public_key</code> again while the user stays logged in.</li>143 <li>The client addresses all further requests to that <code>package name</code>.</li>144 </ol>145 <p>The client MAY pass a list of <code>permissions</code> with <code>get_public_key</code>. The user can then pre-authorize methods in one step. The Content Resolver uses these remembered permissions to answer in the background. Each permission is an object with a <code>type</code> (a method name) and, for <code>sign_event</code>, an optional <code>kind</code>:</p>146 <div class="crt">147 <div class="titlebar">Permissions list - example</div>148<pre>[149 { <span class="k">"type"</span>: <span class="s">"sign_event"</span>, <span class="k">"kind"</span>: <span class="c">22242</span> },150 { <span class="k">"type"</span>: <span class="s">"nip44_decrypt"</span> }151]</pre>152 </div>153154 <h2 class="bar" id="methods"><span class="num">4</span> Methods</h2>155 <p>Every request has a <code>type</code> (the method name) and a payload. Each transport passes the payload differently, but the meaning is shared. Seven methods exist:</p>156 <div class="tscroll"><table class="ref">157 <caption>Signer methods</caption>158 <tr><th>Method</th><th>Payload</th><th>Extra params</th><th>Result</th></tr>159 <tr><td class="btn">get_public_key</td><td>(empty)</td><td><code>permissions</code></td><td>the <code>user-pubkey</code></td></tr>160 <tr><td class="btn">sign_event</td><td>event JSON</td><td><code>current_user</code></td><td>the signature, and the signed event</td></tr>161 <tr><td class="btn">nip04_encrypt</td><td>plaintext</td><td><code>pubkey</code>, <code>current_user</code></td><td>the ciphertext</td></tr>162 <tr><td class="btn">nip44_encrypt</td><td>plaintext</td><td><code>pubkey</code>, <code>current_user</code></td><td>the ciphertext</td></tr>163 <tr><td class="btn">nip04_decrypt</td><td>ciphertext</td><td><code>pubkey</code>, <code>current_user</code></td><td>the plaintext</td></tr>164 <tr><td class="btn">nip44_decrypt</td><td>ciphertext</td><td><code>pubkey</code>, <code>current_user</code></td><td>the plaintext</td></tr>165 <tr><td class="btn">decrypt_zap_event</td><td>event JSON</td><td><code>current_user</code></td><td>the decrypted event JSON</td></tr>166 </table></div>167 <p>Three shared parameters appear across methods:</p>168 <ul>169 <li><code>current_user</code> — the <code>user-pubkey</code> currently logged in to the client, in hex format.</li>170 <li><code>pubkey</code> — the public key of the other party in encryption or decryption, in hex format.</li>171 <li><code>id</code> — an optional client-chosen string. The signer echoes it back in the result. Use it to match responses when you send several requests without waiting.</li>172 </ul>173 <p>The encryption and decryption methods implement <a href="04.html">NIP-04</a> and <a href="44.html">NIP-44</a>. The <code>decrypt_zap_event</code> method decrypts a private zap (<a href="57.html">NIP-57</a>).</p>174175 <h2 class="bar" id="intents"><span class="num">5</span> Intent Flow</h2>176 <p>In the Intent flow, the payload is the data of a <code>nostrsigner:</code> URI. All other parameters travel as intent extras. The client launches the intent and receives the result through <code>registerForActivityResult</code> or <code>rememberLauncherForActivityResult</code>.</p>177 <p>Set the intent's <code>package</code> to the stored signer package name. Omit it only for <code>get_public_key</code>, before the client knows the package name.</p>178 <div class="crt">179 <div class="titlebar">Kotlin - sending a request</div>180<pre><span class="k">val</span> intent = Intent(Intent.ACTION_VIEW, Uri.parse(<span class="s">"nostrsigner:$payload"</span>)).apply {181 `package` = signerPackageName <span class="c">// omit only for get_public_key</span>182 putExtra(<span class="s">"type"</span>, <span class="s">"sign_event"</span>)183 putExtra(<span class="s">"id"</span>, event.id) <span class="c">// optional, echoed back</span>184 putExtra(<span class="s">"current_user"</span>, userPubkey)185}186launcher.launch(intent)</pre>187 </div>188189 <p>The signer answers through the returned intent's extras:</p>190 <div class="tscroll"><table class="ref">191 <caption>Result extras</caption>192 <tr><th>Extra</th><th>Description</th></tr>193 <tr><td class="btn">result</td><td>The method result: pubkey, signature, ciphertext, plaintext, or event JSON.</td></tr>194 <tr><td class="btn">id</td><td>The <code>id</code> sent in the request, echoed back.</td></tr>195 <tr><td class="btn">event</td><td>The signed event JSON. Present for <code>sign_event</code> only.</td></tr>196 <tr><td class="btn">package</td><td>The signer package name. Present for <code>get_public_key</code> only.</td></tr>197 <tr><td class="btn">rejected</td><td><code>true</code> when the user rejected the request.</td></tr>198 </table></div>199200 <div class="box warn">201 <span class="tag">Warning!</span>202 <p>Do not read the <code>resultCode</code> as a rejection. A <code>resultCode</code> other than <code>Activity.RESULT_OK</code> means the signer failed — for example, it crashed. When the user rejects, the signer returns <code>RESULT_OK</code> with the <code>rejected</code> extra set to <code>true</code>. Check both.</p>203 </div>204205 <div class="crt">206 <div class="titlebar">Kotlin - reading the result</div>207<pre>onResult = { result ->208 <span class="k">if</span> (result.resultCode != Activity.RESULT_OK) {209 <span class="c">// signer error (e.g. crash)</span>210 } <span class="k">else if</span> (result.data?.getBooleanExtra(<span class="s">"rejected"</span>, <span class="c">false</span>) == <span class="c">true</span>) {211 <span class="c">// user rejected the request</span>212 } <span class="k">else</span> {213 <span class="k">val</span> value = result.data?.getStringExtra(<span class="s">"result"</span>)214 }215}</pre>216 </div>217218 <figure class="diagram">219 <div class="frame">220 <svg viewBox="0 0 560 270" role="img" aria-label="Sequence diagram: client launches a nostrsigner intent, the signer asks the user, then returns RESULT_OK with result extras or a rejected extra">221 <line class="d-wire" x1="110" y1="40" x2="110" y2="255" stroke-dasharray="3 4"/>222 <line class="d-wire" x1="450" y1="40" x2="450" y2="255" stroke-dasharray="3 4"/>223 <rect class="d-actor" x="55" y="8" width="110" height="30" rx="6"/>224 <text class="d-actor-label" x="110" y="28" text-anchor="middle">Client</text>225 <rect class="d-actor" x="395" y="8" width="110" height="30" rx="6"/>226 <text class="d-actor-label" x="450" y="28" text-anchor="middle">Signer</text>227 <path class="d-msg" d="M110,62 L445,62"/>228 <text class="d-label" x="280" y="55" text-anchor="middle">launch("nostrsigner:<payload>", type, id, current_user)</text>229 <text class="d-note" x="450" y="95" text-anchor="end">signer opens - user approves or rejects</text>230 <path class="d-msg blue" d="M450,125 L115,125"/>231 <text class="d-label" x="280" y="118" text-anchor="middle">RESULT_OK - extras: result, id, event</text>232 <path class="d-msg blue" d="M450,175 L115,175"/>233 <text class="d-label" x="280" y="168" text-anchor="middle">RESULT_OK - extras: rejected=true</text>234 <text class="d-note" x="280" y="215" text-anchor="middle">Any other resultCode = signer failure, not a rejection.</text>235 <circle r="4" fill="#f26a00">236 <animateMotion dur="2.6s" repeatCount="indefinite" path="M110,62 L445,62"/>237 </circle>238 </svg>239 </div>240 <figcaption>Fig. 55-2 — <b>Intent round trip.</b> One request, one approval screen, one result intent. Rejection is a normal <code>RESULT_OK</code> answer.</figcaption>241 </figure>242243 <h3>Signing multiple events in one launch</h3>244 <p>To sign several events without reopening the signer for each one, add two flags to the intent. The signer then returns one array of results in a <code>results</code> extra. The signer must declare <code>android:launchMode="singleTop"</code> for this to work.</p>245 <div class="crt">246 <div class="titlebar">Kotlin - batch signing</div>247<pre><span class="c">// client adds:</span>248intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP <span class="k">or</span> Intent.FLAG_ACTIVITY_CLEAR_TOP)249250<span class="c">// signer answers with:</span>251intent.putExtra(<span class="s">"results"</span>,252 listOf(Result(<span class="k">package</span> = signerPackageName, result = signature, id = intentId)).toJson())</pre>253 </div>254255 <h2 class="bar" id="resolver"><span class="num">6</span> Content Resolver Flow</h2>256 <p>The Content Resolver flow runs in the background. The signer does not open. It answers only for permissions the user chose to remember. The client queries a content URI built from the signer package name and the method name in uppercase:</p>257 <div class="crt">258 <div class="titlebar">Content URI shape</div>259<pre>content://<span class="c"><package-name></span>.<span class="c"><TYPE></span> <span class="c">// e.g. SIGN_EVENT, NIP44_ENCRYPT</span></pre>260 </div>261 <p>The query's <code>selectionArgs</code> carry the payload and the parameters. The order is fixed: <code>[payload, pubkey, current_user]</code>. Pass an empty string for a parameter the method does not use.</p>262 <div class="crt">263 <div class="titlebar">Kotlin - background query</div>264<pre><span class="k">val</span> result = context.contentResolver.query(265 Uri.parse(<span class="s">"content://com.example.signer.SIGN_EVENT"</span>),266 listOf(eventJson, <span class="s">""</span>, loggedInUserPubkey),267 <span class="c">null</span>, <span class="c">null</span>, <span class="c">null</span>,268)</pre>269 </div>270 <p>The cursor returns a <code>result</code> column. For <code>sign_event</code> it also returns an <code>event</code> column with the signed event JSON. The query fails — it returns <code>null</code>, or the cursor has a <code>rejected</code> column — when any of these is true:</p>271 <ul>272 <li>The user did not enable "remember my choice" for this request.</li>273 <li>The <code>user-pubkey</code> is not present in the signer.</li>274 <li>The request <code>type</code> is not recognized.</li>275 <li>The user chose to always reject this request. The signer returns a <code>rejected</code> column. The client SHOULD NOT then fall back to an Intent.</li>276 </ul>277 <div class="crt">278 <div class="titlebar">Kotlin - reading the cursor</div>279<pre><span class="k">if</span> (result == <span class="c">null</span>) <span class="k">return</span>280<span class="k">if</span> (result.getColumnIndex(<span class="s">"rejected"</span>) > -<span class="c">1</span>) <span class="k">return</span>281<span class="k">if</span> (result.moveToFirst()) {282 <span class="k">val</span> value = result.getString(result.getColumnIndex(<span class="s">"result"</span>))283 <span class="c">// for sign_event also: result.getColumnIndex("event")</span>284}</pre>285 </div>286 <p>Clients SHOULD store the <code>user-pubkey</code> locally. Do not call <code>get_public_key</code> again after the user is logged in.</p>287288 <figure class="diagram">289 <div class="frame">290 <svg viewBox="0 0 560 134" role="img" aria-label="Content Resolver query outcomes: a result row when the permission is remembered, null when no choice is remembered, a rejected column when the user always rejects">291 <text class="d-actor-label" x="10" y="18">Query outcome map</text>292 <rect x="10" y="30" width="130" height="26" rx="4" fill="#2c7a3f"/>293 <text class="d-label" x="75" y="47" text-anchor="middle" fill="#fff">result row</text>294 <text class="d-note" x="150" y="47">Permission remembered — read result (and event for sign_event)</text>295 <rect x="10" y="64" width="130" height="26" rx="4" fill="#7d8794"/>296 <text class="d-label" x="75" y="81" text-anchor="middle" fill="#fff">null</text>297 <text class="d-note" x="150" y="81">No remembered choice, unknown user-pubkey, or unknown type</text>298 <rect x="10" y="98" width="130" height="26" rx="4" fill="#f26a00"/>299 <text class="d-label" x="75" y="115" text-anchor="middle" fill="#fff">rejected column</text>300 <text class="d-note" x="150" y="115">User always rejects — stop; do not fall back to an Intent</text>301 </svg>302 </div>303 <figcaption>Fig. 55-3 — <b>Background query outcomes.</b> Only a remembered approval produces a row with a <code>result</code> column.</figcaption>304 </figure>305306 <div class="box tech">307 <span class="tag">Tech Note</span>308 <p>The source spec lists the four failure conditions under one sentence: "the query returns <code>null</code>, or a <code>rejected</code> column is present". It states the <code>rejected</code> column only for the always-reject case. For the first three conditions it does not say which of the two failure shapes applies. Handle both shapes for every condition, as the sample code above does.</p>309 </div>310311 <h2 class="bar" id="web"><span class="num">7</span> Web Flow</h2>312 <p>A web page cannot receive an intent result. Instead, the signer appends the result to a <code>callbackUrl</code>. If the request has no <code>callbackUrl</code>, the signer copies the result to the clipboard. The request is a <code>nostrsigner:</code> URL. The path is the payload. The query string carries the parameters:</p>313 <div class="crt">314 <div class="titlebar">Web request shape</div>315<pre>nostrsigner:<span class="c"><payload></span>?type=<span class="c"><method></span>&pubkey=<span class="c"><hex_pubkey></span>&callbackUrl=<span class="s">https://example.com/?result=</span></pre>316 </div>317 <div class="tscroll"><table class="ref">318 <caption>Web query parameters</caption>319 <tr><th>Parameter</th><th>Description</th></tr>320 <tr><td class="btn">type</td><td>The method name.</td></tr>321 <tr><td class="btn">pubkey</td><td>The other party's hex pubkey. Used by the encryption and decryption methods.</td></tr>322 <tr><td class="btn">callbackUrl</td><td>The URL the signer appends the result to. If omitted, the signer copies the result to the clipboard.</td></tr>323 <tr><td class="btn">returnType</td><td><code>signature</code> or <code>event</code>.</td></tr>324 <tr><td class="btn">compressionType</td><td><code>none</code> (default) or <code>gzip</code>. With <code>gzip</code>, the returned event is the string <code>"Signer1"</code> plus Base64(gzip(event JSON)). Useful because intents and URLs have length limits.</td></tr>325 </table></div>326 <div class="crt">327 <div class="titlebar">JavaScript - opening the signer</div>328<pre>window.href = <span class="s">`nostrsigner:${eventJson}?compressionType=none&returnType=signature`</span> +329 <span class="s">`&type=sign_event&callbackUrl=https://example.com/?event=`</span>;</pre>330 </div>331 <div class="box warn">332 <span class="tag">Warning!</span>333 <p>Consider <a href="46.html">NIP-46</a> remote signing for web applications instead. The web flow here cannot call the signer in the background. The user sees a signer popup for every single request.</p>334 </div>335 <div class="box tech">336 <span class="tag">Tech Note</span>337 <p>The source spec's JavaScript samples assign <code>window.href</code>. That property does not trigger navigation; the working form is <code>window.location.href</code>. The samples are reproduced as written. Adjust in real code.</p>338 </div>339340 <h2 class="bar" id="examples"><span class="num">8</span> Worked Example</h2>341 <p>A web page asks the signer for one signature. It sends a kind-1 event, then reads the result from its own query string when the signer calls back.</p>342 <div class="crt">343 <div class="titlebar">web-client.html - full round trip</div>344<pre><<span class="k">script</span>>345window.onload = <span class="k">function</span>() {346 <span class="c">// 2. when the signer calls back, the result is in the URL</span>347 <span class="k">var</span> params = <span class="k">new</span> URL(window.location.href).searchParams;348 <span class="k">var</span> result = params.get(<span class="s">"event"</span>);349 <span class="k">if</span> (result) alert(result);350351 <span class="c">// 1. build the request link</span>352 <span class="k">var</span> json = { kind: <span class="c">1</span>, content: <span class="s">"test"</span> };353 <span class="k">var</span> encodedJson = encodeURIComponent(JSON.stringify(json));354 <span class="k">var</span> anchor = document.createElement(<span class="s">"a"</span>);355 anchor.href = <span class="s">`nostrsigner:${encodedJson}?compressionType=none`</span> +356 <span class="s">`&returnType=signature&type=sign_event`</span> +357 <span class="s">`&callbackUrl=https://example.com/?event=`</span>;358 anchor.textContent = <span class="s">"Open External Signer"</span>;359 document.body.appendChild(anchor);360}361</<span class="k">script</span>></pre>362 </div>363 <p>The same request over the Intent flow, as a transcript. Orange lines go to the signer. Blue lines come back:</p>364 <div class="crt">365 <div class="titlebar">Intent session - client and com.example.signer</div>366<pre><span class="c">-- connect: no package yet --</span>367<span class="send">→ launch nostrsigner: {type: "get_public_key", permissions: [{"type":"sign_event","kind":1}]}</span>368<span class="recv">← RESULT_OK {result: "b1a649ebe8…", package: "com.example.signer"}</span>369370<span class="c">-- sign: addressed to the stored package --</span>371<span class="send">→ launch nostrsigner:{"kind":1,"content":"test",…} package=com.example.signer372 {type: "sign_event", id: "req-1", current_user: "b1a649ebe8…"}</span>373<span class="recv">← RESULT_OK {result: "e8551d85…", id: "req-1", event: "{\"id\":\"5c83…\",\"sig\":\"e8551d85…\",…}"}</span>374375<span class="c">-- a rejection looks like this --</span>376<span class="recv">← RESULT_OK {rejected: true}</span></pre>377 </div>378379 <h2 class="bar" id="history"><span class="num">9</span> Version History</h2>380 <p>greenart7c3 wrote this NIP for the Amber signer. It entered the repository as a draft numbered 100 and became NIP-55 in mid-2024. Notable changes, newest first:</p>381 <ul class="patchnotes">382 <li><span class="ver">2026</span><span class="when">2026-06</span>Full rewrite: shorter text, one shared methods table, clearer transport sections.</li>383 <li><span class="ver">2026</span><span class="when">2026-01</span>Fixed the <code>nip44_encrypt</code> return field; clarified the connection handshake (store the pubkey and package name once).</li>384 <li><span class="ver">2025</span><span class="when">2025-10</span>Documented <code>get_public_key</code> over the Content Resolver.</li>385 <li><span class="ver">2025</span><span class="when">2025-02</span>Added the web-flow warning, the <code>rejected</code> checks in the samples, and the package-name usage rules. Removed the <code>get_relays</code> method.</li>386 <li><span class="ver">2024</span><span class="when">2024-11</span>Added batch signing: intent flags, <code>singleTop</code> launch mode, and the <code>results</code> array.</li>387 <li><span class="ver">2024</span><span class="when">2024-10</span>Renamed the return field from <code>signature</code> to <code>result</code>.</li>388 <li><span class="ver">2024</span><span class="when">2024-06</span>fiatjaf renumbered the document from NIP-100 to NIP-55. Weeks earlier, web requests moved from <code>intent:</code> to <code>nostrsigner:</code> URLs.</li>389 <li class="minor"><span class="ver">2024</span><span class="when">2024</span>Typo fixes, heading cleanup, and review edits.</li>390 <li><span class="ver">Origin</span><span class="when">2023-11</span>greenart7c3 published the first draft, with permissions and package-name discovery added in the same month.</li>391 </ul>392393 <div class="credits">394 <h3 class="cardhead">Original Proposal - Credits</h3>395 <table>396 <tr><td>Author</td><td>greenart7c3</td></tr>397 <tr><td>First commit</td><td>2023-11-08 — “Android Signer Application nip” (filed as 100.md; renumbered to 55.md on 2024-06-05)</td></tr>398 <tr><td>Status</td><td>draft - optional — for Android clients that delegate key handling</td></tr>399 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/55.md">55.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/55.md">full change history</a></td></tr>400 </table>401 </div>402403 <h2 class="bar"><span class="num">10</span> See Also</h2>404 <ul>405 <li><a href="01.html">NIP-01</a> — the event object, signatures, and kinds that the signer produces.</li>406 <li><a href="46.html">NIP-46</a> — remote signing over relays; the recommended option for web clients.</li>407 <li><a href="07.html">NIP-07</a> — the same capability model as a browser extension (<code>window.nostr</code>).</li>408 <li><a href="04.html">NIP-04</a> — the legacy encryption behind <code>nip04_encrypt</code> and <code>nip04_decrypt</code>.</li>409 <li><a href="44.html">NIP-44</a> — the current encryption behind <code>nip44_encrypt</code> and <code>nip44_decrypt</code>.</li>410 <li><a href="57.html">NIP-57</a> — zaps; <code>decrypt_zap_event</code> opens private zap requests.</li>411 </ul>412 </div>413414 <aside>415 <div class="cartridge">416 <div class="inner">417 <h3 class="cardhead">Spec Card</h3>418 <div class="lights"><span class="led on"></span><span class="led on"></span><span class="led on"></span></div>419 <dl>420 <dt>Status</dt><dd><span class="chip draft">draft</span><span class="chip optional">optional</span></dd>421 <dt>Defines</dt><dd>nostrsigner: scheme - 7 signer methods - intent extras - resolver columns - web params</dd>422 <dt>Kinds</dt><dd>none — transport only</dd>423 <dt>Requires</dt><dd><a href="01.html">NIP-01</a>; uses <a href="04.html">NIP-04</a>/<a href="44.html">NIP-44</a> encryption</dd>424 <dt>Transport</dt><dd>Android Intents - Content Resolver - nostrsigner: URLs</dd>425 <dt>Crypto</dt><dd>key stays in the signer app</dd>426 <dt>First commit</dt><dd>2023-11-08 (as NIP-100)</dd>427 <dt>Last updated</dt><dd>2026-06-03</dd>428 </dl>429 </div>430 </div>431 </aside>432 </main>433434 <nav class="navbtns">435 <a href="54.html">◀ NIP-54: Wiki</a>436 <a href="56.html">NIP-56: Reporting ▶</a>437 </nav>438439 <footer class="pagefoot">440 <a href="../index.html">Nostr Protocol Manual</a>441 <span class="pageno">NIP 55 / F4</span>442 </footer>443</div>444445<script src="../assets/webmcp.js" defer></script>446</body>447</html>