Open Time Stamps on Nostr and the deprecation of NIP-03
NIP-03 — OpenTimestamps Attestations for Events is broken, and maybe that is a blessing in disguise, because there were actually two (and a half) issues with how Open Time Stamp (OTS) proofs were handled on Nostr. Because one demanded a fix, it allows us to fix the other one-and-a-half problems while we are at it.
To bring everyone up to speed from the get-go (skip this section if you understand OTS and its benefits): OpenTimeStamps (OTS) is a system that allows you to create a proof that something existed at least at some point in time; it could be older, but it definitely is not newer than that. It does this by tying that proof into the Bitcoin blockchain, which, due to the properties of this chain and its PoW, is bound to time. This is not a precise thing in terms of seconds or minutes, but is a reasonable anchor in terms of hours. The point of OTS is that you can’t go back in time, so it is very easy for an honest person to make a habit of time-stamping their things, whilst it is impossible for an attacker to create such proofs after the fact. In the context of Nostr, this means, for example, that if you timestamped all your posts around the time they were created, and at some later point your keys get stolen, the thief can’t make credible posts that pretend to be old, and as such the integrity of your post history under that key remains intact even though it is compromised. The creation of these proofs is efficient and cheap, scales really well, and barely requires any space in the blockchain.
The main issue with NIP-03 is that it creates an OTS-proof for the wrong thing; it proved that the event-ID existed before some point in time. In Nostr, the contents of your post get hashed and that hash is the event-ID, then you produce a cryptographic signature with your private-key based on that event-ID to prove you are indeed the publisher. Because the signing happens AFTER the creation of the event-ID, creating a proof that the event-ID existed at some point in time tells you nothing as to when it was signed. In general this should not be much of an issue, but technically it is, because it undermines the point of OTS-proofs. In theory, an attacker that targets you could create a bunch of posts that supposedly were written by you and create OTS-proofs for them based on the event-ID. The attacker does not have your keys (yet), so he can’t sign them. Luckily for the attacker, he can always do that after the fact, when he successfully steals your keys. The moment the attacker manages to get your keys and signs his fraudulent-but-timestamped posts, the outside world has no way of detecting this; according to NIP-03 rules, all is good and well.
What actually needs to be timestamped is the signature, not the event-ID, in order to prove when something was signed. This could have been a relatively simple fix in terms of NIP-03: just sign something else and done. But the second issue is that with NIP-03, the OTS-proof is published in a Nostr event. There is nothing wrong with this per se, it is just a LOT of absolutely useless overhead. All you need is the OTS-proof; nothing else matters — not who created that proof, not at what time that proof was published; all the things that are standard meta-data in a Nostr event have no additional value at all. The one thing that these separate OTS-events (kind-1040) did was create an index for them. It allowed you to go and look if perhaps a proof for a particular event existed, by querying kind-1040 and the event-ID of the thing you needed the proof for.
I set out to solve all of this. First was proving the right thing. In my proposal (NIP-3B), instead of hashing the event-ID, the event-ID + the signature get hashed. In hindsight, I made a silly mistake in that you only need the hash of the signature, but it does not matter enough for me to fix it. You can’t timestamp the signature directly, it is not a 32-byte string, so it needs to be hashed in order to fit the Merkle-tree type proof OTS relies on; so even though adding the event-ID is redundant, it is also trivial.
Second was solving how we deal with these OTS-proofs on Nostr. Putting them in separate events is simply too wasteful, so I did a sneaky thing. I put the proofs inside the JSON of the original event it provides the proof for. You can just do this, and it should not break anything; any Nostr client or relay should basically just ignore this data. Nostr events have canonical fields, being “id”, “pubkey”, “created_at”, “tags”, “content” and “sig”. All NIP-3B does is add a field called “ots” and store the proof there. No overhead; you don’t have to keep track of anything; the event now carries its own proof. Clients that are aware are able to verify; clients that are not just ignore it. Anyone can add a (valid) proof to an event after the fact, and if you come across another proof that is older, you just replace the newer with the older proof. There are two downsides: The first downside is that these proofs are relatively big compared to most Nostr events, so they bloat them in size a lot. This is not an issue per se; people are free to strip away these proofs if they don’t want to store those bytes. This leads us into the second downside, which is a lack of index. If people can just remove these proofs, that also means you might need to go look if a proof perhaps exists for an event. But unlike with the kind-1040, there is nothing to query.
I solved both these issues with a second NIP, NIP-3C. NIP-3C is a behavioral standard that allows clients and relays to set policies and declare them to the network. Do you store proofs or strip them away, and for what event-kinds and for what npubs? Do you only accept events if they contain a proof perhaps, and how old can this proof be compared to how old the event claims to be? Do you automatically create proofs for events, and for what npubs and what event-kinds? Etc.
This way, based on these policies, you can figure out what relays you could try to see if they have a version that includes a proof. It also allows relays to make it clear that they are not interested in storing all these proofs, and will simply delete them when they receive the event. These proofs can be freely added and removed, and NIP-3C is there to create a bit of order in that chaos. On top of that, efficient databases designed to just store OTS-proofs can be used, making it fairly cheap to store A LOT of proofs; these databases could be available for query directly, and will hand over a proof if they have it. These databases are not Nostr-specific; they have nothing to do with Nostr as such. They are OTS-specific, and could store proofs for all sorts of things.
The last thing that remains is: what do we do with all those old NIP-03 proofs? Technically it is a broken standard, but practically speaking all those proofs are probably valid and nothing malicious happened. NIP-3B incorporates these old proofs. Instead of storing them inside the JSON under “ots”, they are stored as “oldots”, to make clear that they only prove the event-ID. The proofs can simply be extracted from existing kind-1040 events, and put inside the JSON of the original event, just like it is done under the new standard. NIP-3B, however, does sunset NIP-03; every “oldots” proof that is anchored in a block-height equal to or higher than 1,000,000 is deemed invalid (that should be by the end of this year or thereabouts).
And there we have it. A new OTS standard for Nostr, that is not broken and more efficient. Anyone that does not want to fill up their storage with proofs is free to remove them; actors that actively stamp and store stuff on the network can declare they do so. All the old NIP-03 proofs are included till the end of this year.
I am sure there are many, many small things that need fixing or fine-tuning, both in the two NIPs and in the software that I (the robot) produced. At this point in time I will leave things as they are.
Link to repository: https://gitworkshop.dev/npub1zkd4h6zshlh2a3yuzaxqk2wgk2kv2za5trz76rd5upwy06fscjrq8mk5ta/relay.ngit.dev/otsuite-mobile
NIP-3B:
NIP-3C:
Write a comment