mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-12-13 19:06:24 +00:00
72bb8a128b
Co-authored-by: Viktor Vsk <me@viktorvsk.com>
124 lines
4.3 KiB
Markdown
124 lines
4.3 KiB
Markdown
NIP-13
|
|
======
|
|
|
|
Proof of Work
|
|
-------------
|
|
|
|
`draft` `optional` `author:jb55` `author:cameri`
|
|
|
|
This NIP defines a way to generate and interpret Proof of Work for nostr notes. Proof of Work (PoW) is a way to add a proof of computational work to a note. This is a bearer proof that all relays and clients can universally validate with a small amount of code. This proof can be used as a means of spam deterrence.
|
|
|
|
`difficulty` is defined to be the number of leading zero bits in the `NIP-01` id. For example, an id of `000000000e9d97a1ab09fc381030b346cdd7a142ad57e6df0b46dc9bef6c7e2d` has a difficulty of `36` with `36` leading 0 bits.
|
|
|
|
`002f...` is `0000 0000 0010 1111...` in binary, which has 10 leading zeroes. Do not forget to count leading zeroes for hex digits <= `7`.
|
|
|
|
Mining
|
|
------
|
|
|
|
To generate PoW for a `NIP-01` note, a `nonce` tag is used:
|
|
|
|
```json
|
|
{"content": "It's just me mining my own business", "tags": [["nonce", "1", "21"]]}
|
|
```
|
|
|
|
When mining, the second entry to the nonce tag is updated, and then the id is recalculated (see [NIP-01](./01.md)). If the id has the desired number of leading zero bits, the note has been mined. It is recommended to update the `created_at` as well during this process.
|
|
|
|
The third entry to the nonce tag `SHOULD` contain the target difficulty. This allows clients to protect against situations where bulk spammers targeting a lower difficulty get lucky and match a higher difficulty. For example, if you require 40 bits to reply to your thread and see a committed target of 30, you can safely reject it even if the note has 40 bits difficulty. Without a committed target difficulty you could not reject it. Committing to a target difficulty is something all honest miners should be ok with, and clients `MAY` reject a note matching a target difficulty if it is missing a difficulty commitment.
|
|
|
|
Example mined note
|
|
------------------
|
|
|
|
```json
|
|
{
|
|
"id": "000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358",
|
|
"pubkey": "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
|
"created_at": 1651794653,
|
|
"kind": 1,
|
|
"tags": [
|
|
[
|
|
"nonce",
|
|
"776797",
|
|
"21"
|
|
]
|
|
],
|
|
"content": "It's just me mining my own business",
|
|
"sig": "284622fc0a3f4f1303455d5175f7ba962a3300d136085b9566801bc2e0699de0c7e31e44c81fb40ad9049173742e904713c3594a1da0fc5d2382a25c11aba977"
|
|
}
|
|
```
|
|
|
|
Validating
|
|
----------
|
|
|
|
Here is some reference C code for calculating the difficulty (aka number of leading zero bits) in a nostr event id:
|
|
|
|
```c
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
int countLeadingZeroes(const char *hex) {
|
|
int count = 0;
|
|
|
|
for (int i = 0; i < strlen(hex); i++) {
|
|
int nibble = (int)strtol((char[]){hex[i], '\0'}, NULL, 16);
|
|
if (nibble == 0) {
|
|
count += 4;
|
|
} else {
|
|
count += __builtin_clz(nibble) - 28;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
if (argc != 2) {
|
|
fprintf(stderr, "Usage: %s <hex_string>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
const char *hex_string = argv[1];
|
|
int result = countLeadingZeroes(hex_string);
|
|
printf("Leading zeroes in hex string %s: %d\n", hex_string, result);
|
|
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
Here is some JavaScript code for doing the same thing:
|
|
|
|
```javascript
|
|
// hex should be a hexadecimal string (with no 0x prefix)
|
|
function countLeadingZeroes(hex) {
|
|
let count = 0;
|
|
|
|
for (let i = 0; i < hex.length; i++) {
|
|
const nibble = parseInt(hex[i], 16);
|
|
if (nibble === 0) {
|
|
count += 4;
|
|
} else {
|
|
count += Math.clz32(nibble) - 28;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
```
|
|
|
|
Querying relays for PoW notes
|
|
-----------------------------
|
|
|
|
If relays allow searching on prefixes, you can use this as a way to filter notes of a certain difficulty:
|
|
|
|
```
|
|
$ echo '["REQ", "subid", {"ids": ["000000000"]}]' | websocat wss://some-relay.com | jq -c '.[2]'
|
|
{"id":"000000000121637feeb68a06c8fa7abd25774bdedfa9b6ef648386fb3b70c387", ...}
|
|
```
|
|
|
|
Delegated Proof of Work
|
|
-----------------------
|
|
|
|
Since the `NIP-01` note id does not commit to any signature, PoW can be outsourced to PoW providers, perhaps for a fee. This provides a way for clients to get their messages out to PoW-restricted relays without having to do any work themselves, which is useful for energy-constrained devices like mobile phones.
|