Skip to content

NIP-AB: Device Pairing#2328

Open
tlongwell-block wants to merge 1 commit intonostr-protocol:masterfrom
tlongwell-block:nip-ab
Open

NIP-AB: Device Pairing#2328
tlongwell-block wants to merge 1 commit intonostr-protocol:masterfrom
tlongwell-block:nip-ab

Conversation

@tlongwell-block
Copy link
Copy Markdown

@tlongwell-block tlongwell-block commented Apr 28, 2026

I wanted a simple way to move a Nostr key from one device to another without pasting nsecs around or depending on the old device staying online.

Scan a QR code on your new device, confirm the numbers match, and you're you on both devices.

The closest thing I found was @1l0's NIP-4B draft (#1768), which covers the same use case. That work and the discussion between @vitorpamplona and @mikedilger on it were really helpful in framing the problem. This draft makes a different set of tradeoffs: QR bootstrap, ephemeral ECDH, and a short code shown on both screens before the key is sent (same idea as Bluetooth pairing).

Scope is narrow: one-time transfer of a key between two devices controlled by the same user. Not trying to replace NIP-46 or define a general secure channel. I think it's worth standardizing this because it's a modern UX everyone is familiar with while also being provably secure.

Uses NIP-44 for encryption, NIP-49 ncryptsec for the payload format. Standard NIP-01 event routing, no new relay commands or behavior. Ephemeral keys only, discarded after the session.

There's a working implementation and a Tamarin model for the handshake in sprout if anyone wants to look at the code or poke at the crypto.

Did I miss something existing that already covers this?

@tlongwell-block tlongwell-block marked this pull request as ready for review April 28, 2026 16:18
@fiatjaf
Copy link
Copy Markdown
Member

fiatjaf commented Apr 28, 2026

What is the problem with https://github.com/nostr-protocol/nips/blob/master/49.md? Encrypt the key, transfer it encrypted, decrypt on the other device?

Actually if you're doing it with QR codes and you already trust the sender and receiver app I don't see why not just put the nsec directly in the QR code.

@tlongwell-block
Copy link
Copy Markdown
Author

What is the problem with https://github.com/nostr-protocol/nips/blob/master/49.md? Encrypt the key, transfer it encrypted, decrypt on the other device?

Actually if you're doing it with QR codes and you already trust the sender and receiver app I don't see why not just put the nsec directly in the QR code.

This does not require a strong password, which is inconvenient to type on a mobile device.

Also, the security model here is as strong as any other nostr message type (ie having the nsec in the message doesn't materially make this less secure than any other signed message).

Putting the nsec directly into the QR code means anyone who can see your screen can steal your nsec permanently. Like, say, a camera at an airport or someone sitting behing you at a coffee shop. With NIP-AB, someone seeing your screen does not matter.

Thanks for taking a look, @fiatjaf 👋

@tlongwell-block
Copy link
Copy Markdown
Author

Also, NIP-49 specifically says not publish to relays. I agree with it. NIP-AB does not have the mass cracking issue because it does not have a password.

@fiatjaf
Copy link
Copy Markdown
Member

fiatjaf commented Apr 28, 2026

I didn't suggest publishing to relays, just putting the encrypted nsec in the QR code directly (or putting the nsec without encrypting).

Do you really think it makes sense to have a complex protocol for key transfer instead of just reusing normal QR codes or basic encryption that are already implemented in many apps just because someone might decide to migrate their data from one phone to the other in an airport and can't be bothered to find a safer spot or type a longer password?

I feel like the existing solutions already give good enough security and UX, while this one is more likely to decrease UX because there will be less interoperability due to the complexity and people will resort to sending their nsecs through Telegram self-messages in the end.

@tlongwell-block
Copy link
Copy Markdown
Author

tlongwell-block commented Apr 28, 2026

The UX for this is like the bluetooth pairing flows everyone is used to:

Scan QR code. Confirm numbers match. Done!

Sharing an encrypted nsec over QR code is fine as long as you have a sufficiently long (and inconvenient to type) password in your head (that you have to type twice, once on the source and once on the target).

@jmecom
Copy link
Copy Markdown

jmecom commented Apr 29, 2026

@fiatjaf, I think the main difference is the security/UX tradeoff, not whether NIP-49 can technically carry the key.

With NIP-49, the transfer security ultimately depends on a user-chosen password. For this use case, that password has to be created on one mobile device and typed again on another. In practice, users will either choose something short, reuse an existing password, or abandon the flow. Scrypt helps, but it does not remove the offline guessing problem if the encrypted blob is captured. That capture risk is relevant because QR-based transfer is inherently screen-visible.

NIP-AB has a different failure mode. A passive observer of the QR does not get the private key and does not get an offline password-cracking artifact. To attack the transfer, they have to actively participate during the short pairing window.

The UX is also simpler:

  • NIP-49: choose password, make it strong enough, type it correctly on both devices, handle failures.
  • NIP-AB: scan QR, compare numbers, confirm.

I would also note that this protocol is well within the realm of established cryptographic patterns; there are other systems that use similar SAS-authenticated Diffie-Hellman flows. I collected a list:

Protocol What It Does Similarity To NIP-AB Key Difference
Bluetooth LE Secure Connections Numeric Comparison Devices exchange ECDH keys and show a 6-digit code to prevent MITM. Very similar UX: "do these numbers match?" NIP-AB's 6-digit SAS is directly in this family. Bluetooth has local radio proximity. NIP-AB uses global Nostr relays.
Matrix SAS verification Devices verify each other using SAS/emoji after key agreement over Matrix servers. Probably the closest conceptual match: untrusted servers, ECDH, transcript/commitment checks, human SAS comparison. Matrix verifies device keys/trust; it generally does not transfer the account's root private key.
Magic Wormhole Two endpoints use a short code and a rendezvous server to establish an encrypted transfer. Similar "temporary pairing over untrusted infrastructure" model. Wormhole uses PAKE so a short typed code can safely bootstrap encryption. NIP-AB uses a high-entropy QR secret plus ECDH, so no password/PAKE is needed.
Signal linked devices Desktop/tablet shows QR; phone scans and authorizes the linked device. Same mainstream "scan QR to add device" UX. Signal adds a device with its own keys; it does not simply copy the primary identity private key as the core model.
FIDO/WebAuthn hybrid transport Desktop shows QR; phone scans; BLE proves proximity; encrypted tunnel carries auth messages. Very close QR structure: public key plus shared secret in QR. FIDO uses BLE for proximity and the passkey private key never leaves the authenticator. NIP-AB exports/imports the Nostr secret.

@tlongwell-block
Copy link
Copy Markdown
Author

@paulmillr this might be in your wheelhouse?

@fiatjaf
Copy link
Copy Markdown
Member

fiatjaf commented May 1, 2026

I never said your proposed UX would be worse, I'm just saying that because this is more complex it will get implemented in less places, which may cause users to fall back to much less safe solutions.

I also do not disagree that your solution is safer, I was just saying that the tradeoffs aren't the best because we are talking about a new complex protocol implementation for doing a thing that very few people will ever do and even those that will will do it only once -- and the protocol only improves the security/UX marginally (debatable how much improvement there is, but the fact is that the NIP-49 QR code works and is reasonably safe already).

@1l0
Copy link
Copy Markdown
Contributor

1l0 commented May 2, 2026

Nice. We need this sort of layer for normies since they never use NIP-46, unfortunately. It's still dangerous for rogue clients, but definitely much safer than copying and pasting nsec directly. Also, many clients simply don't accept ncryptsec in the first place, so this NIP doesn't seem to harm anything.
That said, I found something that needs to be fixed:

  • This only covers specific situations: desktop to mobile, or mobile to mobile. We also need to cover the opposite: mobile to desktop, where the desktop has no camera.

@paulmillr
Copy link
Copy Markdown
Contributor

from a very brief glance, the cryptography seems fine, but a more deep dive would be useful

@tlongwell-block
Copy link
Copy Markdown
Author

tlongwell-block commented May 2, 2026

I also do not disagree that your solution is safer, I was just saying that the tradeoffs aren't the best because we are talking about a new complex protocol implementation for doing a thing that very few people will ever do and even those that will will do it only once -- and the protocol only improves the security/UX marginally (debatable how much improvement there is, but the fact is that the NIP-49 QR code works and is reasonably safe already).

@fiatjaf yeah, under the hood this is a little more complicated, but the NIP has a full set of test vectors to ensure the implementation is correct. It makes it much easier to get right. And the example implementation in Sprout should help, too. As far as handshake protocols, this one is pretty straighforward.

And, yeah, I think the UX is hugely improved and is exactly what most people expect these days when getting their phone onto a new service. I'm thinking of bluetooth pairing, Discord sign in, Slack magic links. This is right in the same ballpark of convenience and intuitiveness.

This only covers specific situations: desktop to mobile, or mobile to mobile. We also need to cover the opposite: mobile to desktop, where the desktop has no camera.

@1l0 yeah, that would have to be a different NIP. This one counts on the out of band nature of the QR code scan to transmit the ephemeral crypto that is used during the handshake. There are other out of band information transimission options available, but the implementation is different enough for each that I'd want separate protocols to keep this one simple.

from a very brief glance, the cryptography seems fine, but a more deep dive would be useful

@paulmillr thanks for taking a look! Here's the PR for the original implementation in Sprout block/sprout#333 And the full spec (too long to fit in well in this repo) along with the formal proof is in https://github.com/block/sprout/blob/main/crates/sprout-core/src/pairing/NIP-AB.md

@arthurfranca
Copy link
Copy Markdown
Contributor

If you go all the way back to when NIP-46 was proposed, the idea was to have a common remote communication channel where you could extend with whatever "commands" you needed, i.e. it wasn't meant to match only NIP-07 methods.

By using NIP-46 this NIP would be much easier to review and implement.

For example: On your setup, the "source" initiates the connection, so you'd use the bunker:// flow, then the "target" sends back a new command after connection, like, get_secret_keys (plural; I explain what later on), which the bunker reacts to by computing the 6-digit code and showing it on screen for user confirmation. The bunker delays the response up until the user confirms it matches the other device's 6-digit code. If the user confirms, it replies with the plain nsec(s), because nip46 already encrypts messages, else it replies with an empty array and an error.

Deviations from NIP-46:

  • Even though you'd be using the bunker:// flow, I think it's good to adopt your nostrpair:// protocol prefix to differentiate purpose and because of the below items.
  • Usually a nip46 url is related to a single nsec, but considering bunker apps may hold many nsecs, it makes sense to backup all of them (or the selected subset) at once, that's why I suggested a get_secret_keys method instead of get_secret_key.
  • Commands related to a single nsec (sign_event, nip44_encrypt etc) are ignored

@tlongwell-block
Copy link
Copy Markdown
Author

If you go all the way back to when NIP-46 was proposed, the idea was to have a common remote communication channel where you could extend with whatever "commands" you needed, i.e. it wasn't meant to match only NIP-07 methods.

By using NIP-46 this NIP would be much easier to review and implement.

For example: On your setup, the "source" initiates the connection, so you'd use the bunker:// flow, then the "target" sends back a new command after connection, like, get_secret_keys (plural; I explain what later on), which the bunker reacts to by computing the 6-digit code and showing it on screen for user confirmation. The bunker delays the response up until the user confirms it matches the other device's 6-digit code. If the user confirms, it replies with the plain nsec(s), because nip46 already encrypts messages, else it replies with an empty array and an error.

Deviations from NIP-46:

  • Even though you'd be using the bunker:// flow, I think it's good to adopt your nostrpair:// protocol prefix to differentiate purpose and because of the below items.
  • Usually a nip46 url is related to a single nsec, but considering bunker apps may hold many nsecs, it makes sense to backup all of them (or the selected subset) at once, that's why I suggested a get_secret_keys method instead of get_secret_key.
  • Commands related to a single nsec (sign_event, nip44_encrypt etc) are ignored

Thanks for taking a look, @arthurfranca 👋

I did take a good look at NIP-46, but its semantics are fundamentally different and I didn't think it would be appropriate to change its core premise that "Private keys should be exposed to as few systems - apps, operating systems, devices - as possible as each system adds to the attack surface."

I do talk about this in the Motivation section of NIP-AB in this PR, and go into more detail in the one in Sprout repo.

Here's the line from the full spec in the Sprout repo:

NIP-46 solves ongoing delegation: the key stays on one device and signs remotely. This NIP solves one-time transfer: the key moves to the new device, which then operates independently. They are complementary — this NIP can even bootstrap a NIP-46 session as one of its payload types.

NIP-46 doesn't need to be quite as concerned with security since it is an active session and can be revoed. NIP-AB is paranoid because it is a potentially permanent transfer. A working mitm would be catastrophic for nsec transfer.

In NIP-AB, the SAS code is cryptographically bound to the ECDH handshake and session transcript. The code proves you're talking to the right peer, not just that someone showed you a number. Adding a SAS display onto an already-established NIP-46 channel doesn't give you the same property. The channel is already authenticated (weakly) by the time you'd show the code.

You could theoretically add fresh ephemeral ECDH, transcript binding, and SAS verification inside a NIP-46 session, but at that point you've rebuilt NIP-AB's core under a different event kind. The security properties NIP-AB needs are the protocol.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants