The mechanism, in full
Copy the URL.It is already dead.
Every tap is signed inside the chip by a key that was never written down, over a counter that only ever counts up. Which means the thing an attacker can capture, the URL, describes one moment that has already passed.
This page is the whole argument, including the parts of it that do not go our way.
The hardware
What an NTAG 424DNA actually is.
A passive NFC chip with AES-128 inside it and a feature called Secure Dynamic Messaging. It has no battery. It does nothing until a phone’s field powers it, and what it does then is arithmetic.
Secure Dynamic Messaging, sometimes called SUN, is the specific capability the whole design rests on. On every read the chip increments an internal counter, encrypts that counter along with its own serial number, computes a signature over the encrypted block, and writes both into the URL it hands the phone. It is the chip, not our server and not an app, that produces those bytes.
That is why a phone with no Tap2MoMo software on it can still take part. The proof is manufactured at the tag, travels as ordinary characters in a web address, and is checked at our end. The customer’s phone is a courier and needs to understand nothing.
A printed QR code
- A picture. Reproducible by anyone with a printer.
- Can be covered with another one and nobody can see it happened.
- Identical every time it is read, so a photograph of it is as good as the original.
- Carries no proof of who is being paid.
An NTAG 424 DNA tag
- A chip that performs a cryptographic operation on every single read.
- Emits different bytes every time, so there is nothing stable to copy.
- A swapped tag either fails its signature check or names a merchant the customer can see is wrong.
- Cannot be reproduced by reading it, because reading it is what changes it.
Why not MIFARE DESFire, which is the part this project first named
Because DESFire EV2 cannot do Secure Dynamic Messaging at all. NXP lists the feature as unsupported on EV2, which means the part originally written into this project’s own intake document could not run the design that intake document described. That is a correction we made against ourselves during research and it is recorded rather than quietly fixed.
EV3 can do it. It also costs roughly two to six times as much, for a large file system and an access-control feature set that a tag whose entire job is to prove a merchant’s identity will never touch. Paying that premium per counter, across an estate, buys nothing this product uses.
Watch it happen
Two fields, andboth of themchange.
The tag appends two values to its stored URL. One is the encrypted block holding its serial number and its read counter. The other is the signature over that block.
Both are different on every read. Write one down, and the counter it carries is already behind the one the server has recorded by the time anybody tries to use it. Press the replay button and watch what the server does with it.
- picc
- The tag’s serial number and read counter, encrypted. Changes on every single read.
- mac
- A signature computed inside the chip, by a key that was never written down and cannot be read out.
Values above are generated in your browser for illustration. The structure, the field names and the behaviour are exactly what a real NTAG 424 DNA tag produces; the bytes are not live cryptographic output.
Key management
There is no keydatabase to steal.
Every tag has its own keys, and none of them are stored anywhere. A tag’s key is derived on demand from an estate master key and that tag’s own serial number, recomputed each time it is needed and discarded again. There is no key column in the database, no key table, no key cache, no key file, and no key in any log line.
The instinct that says this is wrong is a good instinct and worth answering. A key table would allow rotation, audit and recovery, and every one of those is a real benefit. It would also create the thing this design does not have: a single file whose theft compromises every tag in the country at once.
Derivation costs a few microseconds per tap. A key database costs the whole estate if it ever leaks.
The keys inside the chip are written once during provisioning and cannot be read back out afterwards. Not by us, not by a reader, not by whoever picks the tag up. The chip can prove it holds the key by using it, which is the only thing anyone needs it to do.
Replay defence
One databaseoperation, andit must be one.
The counter inside the tag only ever goes up. The server keeps the highest value it has seen for each tag and will only accept a tap carrying something higher. That much is ordinary. How it is written is not.
Read, then write
Fetch the stored counter, compare it in application code, then write the new one back. This passes every single-threaded test you can write. It also lets two simultaneous taps of one captured URL both read the old value, both decide they are ahead of it, and both open a payment session.
The window is a few milliseconds wide and an attacker with a script gets to aim at it as many times as they like.
One compare-and-set
A single database operation that says: raise this tag’s counter to this value, but only if it is currently below it. The database answers with how many rows it changed. One means this tap won. Zero means it did not, and there is no third answer.
Eight identical taps racing against a real database open exactly one session. That is a test in the suite, not an argument on a slide.
The ordering matters too, and in a way that is easy to get backwards. The counter is advanced before the tag’s status is checked, not after. If the status check came first, one captured URL from a cancelled tag could be replayed endlessly to spawn refusal after refusal, each one a free database write.
Reversing that order passes the entire test suite. That is precisely why it is written down as a decision record rather than left as a line of code somebody tidies up later.
The interface as a control
A refused tag hasno Pay button.Not even a grey one.
When a tag is cancelled, not yet live, or belongs to a merchant who is not currently accepting payments, the customer still gets a proper screen naming that merchant, in red, with a plain sentence saying what is wrong. What they do not get is a way forward, and the button is not disabled: it is not rendered at all.
A greyed-out button is a promise. It says the action exists and something temporary is in the way, so the natural response is to wait a second and press it again. On a refused tag, trying again is exactly the wrong move: it is the move that makes a customer stand at a counter poking at a dead tag instead of telling the cashier.
Cancel is the only way out of that screen, and the refusal is enforced on the server regardless of what the page shows. The interface is not the control. It is there so the control does not have to be discovered by trial and error.
What this does not do
The limits, namedby us rather thanfound by you.
Everything above is about one attack: somebody putting a tag on your counter that is not yours, or reusing a tap that was. Here is what it does not reach.
A merchant who is themselves dishonest
The tag proves the customer is paying the business the tag is enrolled to. It does not prove that business charged the right amount, delivered anything, or is trading honestly. That is a different problem and this is not a solution to it.
A customer who approves without reading
The merchant name and code are shown in large type before any amount is entered, and that control only works if somebody reads it. A customer who taps through every screen out of habit is not protected by a screen they did not look at.
A tag that has been physically destroyed
Prising a tag off a counter or scratching through the antenna does not let anyone take money, but it does stop the merchant taking money until a replacement arrives. Availability and security are different properties and this design buys the second one.
A tag physically moved to another counter
A stolen tag still belongs to its enrolled merchant, so money taken through it still lands in that merchant’s account, which makes the theft pointless and traceable. The anomaly flag that would detect a tag reporting from a new place is specified and is not built yet. We would rather say that than list it as a feature.
No part of this system has been through an external security audit, and nothing on this page should be read as saying otherwise. The cryptographic primitives are tested against the published RFC 4493 vectors and the chip vendor’s own worked example, which is a statement about correctness, not about accreditation.
Nor has a single tap ever come off real silicon. Every provisioning run so far has been verified against a software simulator written from the same reading of the datasheet as the code it tests, so a shared misunderstanding would pass every test quietly. The first real tag is the first real test of that, and we know it.