QR Secret Transfer
Move a password, a key or a token from one device to another. One shows a QR code, the other scans it, and you check five digits across the two screens. There is no account and no server of yours in the middle: the secret travels sealed, over public relays that cannot read it.
This device
Your other device
You need two devices. Open this page on the other one.
What a relay sees: nothing yet.
This device: relays and past transfers
What just happened, message by message
The protocol’s messages as this device sent and received them. The digits, a key or a secret never appear here.
Nothing has happened yet.
How it works
Either device can show the QR code. This is the usual way round: the device that should receive the secret shows it, because it is often a computer with no camera to point.
-
The receiving device makes a throwaway key pair and shows the public half as a QR code. The QR code holds no secret. Someone who photographs it can try to answer it, and the next steps are what stop them.
-
its camera
The sending device scans the QR code and makes a throwaway key pair of its own.
-
The receiving device answers with a random number of its own. Now each can work out the same five digits from both keys and both numbers.
sealed, by relayThe sending device commits to a random number, and reveals it only after the other has answered. Neither side can steer the digits.
-
The receiving device shows the five digits.
youThe sending device shows them too, and you check the two screens match. At the strictest setting you type them in instead. This is the step that stops someone sitting in the middle.
-
sealed, by relay
The digits match, so the sending device releases the secret, sealed so that only the receiving device’s throwaway key opens it.
-
The receiving device shows you what arrived and asks before keeping it. Both throwaway keys are destroyed.
A relay in the middle handles every sealed message and learns none of it. Each one is signed by a different one-time key and addressed to a throwaway key, and what is inside is encrypted twice: once to hide the contents, once to hide who sent it.
How carefully to check
How hard the sending device checks the other one is a setting, the slider above the demo. Each device has its own, and the stricter of the two is used, so neither device can talk the other down.
- Type digits: high. The receiving device shows five digits and you type them on the sending one. Typing means they have to be read, and the digits you type pick out your device even if a stranger answered first. For what can never be taken back, such as a private key.
- Compare digits: medium. Both devices show five digits and you say whether they match. A stranger’s digits are random, so a glance catches them, as long as you do glance. If another device answered, the sending device says so. The default, and right for most things.
- Confirm only: low. No digits to check: you only confirm the send, and whoever answers the QR code gets the secret. If a second device answers, the transfer stops and both are told, but someone who sees the QR code and answers before your device does can get it. For things you can revoke, such as a login session, and best when the receiving device shows the QR code.
These are recommendations. What a secret is worth, and what checking it is worth, is up to whoever builds with QRST.
What it stops, and what it does not
It stops
- A photograph of the QR code. It holds a public key, relay addresses and a one-time token. Someone who answers with it gets different digits from yours.
- Someone swapping keys in the middle. The five digits would differ. They get one guess in a hundred thousand, once, and cannot choose the digits.
- Someone watching a relay. They see the throwaway key but not the token, so they cannot answer the QR code at all.
- A relay reading or faking a message. It can drop one or delay one. It cannot open one or write one.
- A stranger who scans the QR code first. Their digits are different, so nothing is sent to them, and the sending device tells you another device answered.
- Something planted on the receiving device. It shows what arrived and keeps nothing until you say so.
It does not stop
- You sending to a device that should not have it. A hostile page can show a real QR code. If you check its digits and confirm, it receives the secret. The digits prove you are talking to the screen you read. They do not prove that screen deserves anything.
- With Confirm only, a stranger who answers first. If they answer before your device and you send before your device answers, they get it. That is the price of not checking.
- With Compare digits, a glance you skip. Comparing works only if you look. Typing is there for when that is not enough.
- A relay noticing that a transfer happened. It sees two throwaway keys exchange a few messages, when, and from which network addresses.
- A compromised device. It gives up whatever it holds.
That first limit is the reason the sending device is deliberately blunt. It says that this is not a login, it names what the other side claims to be, and the button that sends is never the easy one to press.
Build with it
QRST carries; it does not care what. What moves is defined by a profile: a short name, a size limit, a check that what arrived is the right kind of thing, a one-line summary a person can recognise, and the words the sending device uses before it lets go. This demo’s profile is qrst-demo-text. A login session, an API key or a signing key would each be another.
The transport is public Nostr relays, with messages sealed using NIP-44 and NIP-59. No Nostr account is involved: both devices use keys that live for one transfer. No relay is fixed, either. Before a device shows a QR code it sends itself a sealed message through each relay it knows, and names only the ones that brought it back.
- Read the specification, a draft. Its event kinds are provisional.
- Report a problem with the specification: an ambiguity, a suspected error or a design disagreement. Resolved so far.
- Test vectors for the five digits, which this demo reproduces.
- Source, and bugs in this demo. The protocol core is separate from this page and has no dependency on a browser.
Neither the specification nor this software has been audited. Review is more useful than deployment at this stage.
Questions
Is this a way to log in?
It can carry a login, but what it does is move a secret from one device to another. That difference matters because scanning a QR code feels like signing in, so the sending device always says in plain words that something is about to leave it.
What are the five digits for?
They prove that the two devices are talking to each other and not to something in between. Both work the digits out separately; they are never sent. You are the channel: you check that both screens show the same digits, or at the strictest setting read them on one and type them into the other. Taking a photo of them gets nobody anything: they belong to this one pair of devices.
Why can’t the relay read it?
The secret is encrypted on the sending device to a key that exists only on the receiving one, and that envelope is wrapped again under a one-time key. The relay stores and forwards what it is given, and what it is given is noise.
Do I need a Nostr account?
No. Nostr relays are used because anyone can publish to one without registering. Neither device shows a relay any identity that outlives the transfer.
Which relays does it use?
None in particular. A device prefers relays you add, then ones that worked on it before, then ones named by QR codes it has scanned, then ones that relay monitors report, and only then a short starting list. Every one of them has to pass the same test before it is used, and you can remove any of them.
What happens to my secret on this page?
It stays in this page’s memory until it is sent or you leave. It is never written to storage. What this page does store, on this device only, is which relays have worked and a log of transfers that never includes a secret.