Add NIP-13 proof-of-work policy

This commit is contained in:
Alex Gleason 2023-04-11 16:01:09 -05:00
parent 3ab9364622
commit d652bf7a94
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
5 changed files with 107 additions and 0 deletions

View File

@ -210,5 +210,16 @@
"https://esm.sh/v113/nostr-tools@1.8.1/lib/references.d.ts": "b2e39f5c439380a1dc8e578b471ba3992f33e3936aa0eecf625734c7ba669098", "https://esm.sh/v113/nostr-tools@1.8.1/lib/references.d.ts": "b2e39f5c439380a1dc8e578b471ba3992f33e3936aa0eecf625734c7ba669098",
"https://esm.sh/v113/nostr-tools@1.8.1/lib/relay.d.ts": "9bc6f2897a95ec12d128e94b2e7d6161d30a6bfe8d4350e217fbf4b710988db3", "https://esm.sh/v113/nostr-tools@1.8.1/lib/relay.d.ts": "9bc6f2897a95ec12d128e94b2e7d6161d30a6bfe8d4350e217fbf4b710988db3",
"https://esm.sh/v113/nostr-tools@1.8.1/lib/utils.d.ts": "6f37b09db0dce09f17ff7b70b921b9bb809bcb18c3bc058cb914afdf3c0e774e" "https://esm.sh/v113/nostr-tools@1.8.1/lib/utils.d.ts": "6f37b09db0dce09f17ff7b70b921b9bb809bcb18c3bc058cb914afdf3c0e774e"
},
"npm": {
"specifiers": {
"@noble/secp256k1@^1.7.1": "@noble/secp256k1@1.7.1"
},
"packages": {
"@noble/secp256k1@1.7.1": {
"integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==",
"dependencies": {}
}
}
} }
} }

1
mod.ts
View File

@ -4,6 +4,7 @@ export { default as hellthreadPolicy, type Hellthread } from './src/policies/hel
export { default as keywordPolicy } from './src/policies/keyword-policy.ts'; export { default as keywordPolicy } from './src/policies/keyword-policy.ts';
export { default as noopPolicy } from './src/policies/noop-policy.ts'; export { default as noopPolicy } from './src/policies/noop-policy.ts';
export { default as openaiPolicy, type OpenAI, type OpenAIHandler } from './src/policies/openai-policy.ts'; export { default as openaiPolicy, type OpenAI, type OpenAIHandler } from './src/policies/openai-policy.ts';
export { default as powPolicy } from './src/policies/pow-policy.ts';
export { default as pubkeyBanPolicy } from './src/policies/pubkey-ban-policy.ts'; export { default as pubkeyBanPolicy } from './src/policies/pubkey-ban-policy.ts';
export { default as rateLimitPolicy, type RateLimit } from './src/policies/rate-limit-policy.ts'; export { default as rateLimitPolicy, type RateLimit } from './src/policies/rate-limit-policy.ts';
export { default as readOnlyPolicy } from './src/policies/read-only-policy.ts'; export { default as readOnlyPolicy } from './src/policies/read-only-policy.ts';

View File

@ -1,3 +1,4 @@
export { readLines } from 'https://deno.land/std@0.181.0/io/mod.ts'; export { readLines } from 'https://deno.land/std@0.181.0/io/mod.ts';
export { assert, assertEquals } from 'https://deno.land/std@0.181.0/testing/asserts.ts'; export { assert, assertEquals } from 'https://deno.land/std@0.181.0/testing/asserts.ts';
export { Keydb } from 'https://deno.land/x/keydb@1.0.0/sqlite.ts'; export { Keydb } from 'https://deno.land/x/keydb@1.0.0/sqlite.ts';
export * as secp from 'npm:@noble/secp256k1@^1.7.1';

View File

@ -0,0 +1,20 @@
import { assertEquals } from '../deps.ts';
import { buildEvent, buildInputMessage } from '../test.ts';
import powPolicy from './pow-policy.ts';
Deno.test('blocks events without a nonce', async () => {
const msg = buildInputMessage();
assertEquals((await powPolicy(msg)).action, 'reject');
});
Deno.test('accepts event with sufficient POW', async () => {
const msg = buildInputMessage({
event: buildEvent({
id: '000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358',
tags: [['nonce', '776797', '20']],
}),
});
assertEquals((await powPolicy(msg, { difficulty: 20 })).action, 'accept');
});

View File

@ -0,0 +1,74 @@
import { secp } from '../deps.ts';
import type { Policy } from '../types.ts';
/** Policy options for `powPolicy`. */
interface POW {
/** Events will be rejected if their `id` does not contain at least this many leading `0`'s. Default: `1` */
difficulty?: number;
}
/** Reject events which don't meet Proof-of-Work ([NIP-13](https://github.com/nostr-protocol/nips/blob/master/13.md)) criteria. */
const powPolicy: Policy<POW> = ({ event }, opts = {}) => {
const { difficulty = 1 } = opts;
const pow = getPow(event.id);
const nonce = event.tags.find((t) => t[0] === 'nonce');
if (pow >= difficulty && nonce && Number(nonce[2]) >= difficulty) {
return {
id: event.id,
action: 'accept',
msg: '',
};
}
return {
id: event.id,
action: 'reject',
msg: `pow: insufficient proof-of-work (difficulty ${difficulty})`,
};
};
/** Get POW difficulty from a Nostr hex ID. */
function getPow(id: string): number {
return getLeadingZeroBits(secp.utils.hexToBytes(id));
}
/**
* Get number of leading 0 bits. Adapted from nostream.
* https://github.com/Cameri/nostream/blob/fb6948fd83ca87ce552f39f9b5eb780ea07e272e/src/utils/proof-of-work.ts
*/
function getLeadingZeroBits(hash: Uint8Array): number {
let total: number, i: number, bits: number;
for (i = 0, total = 0; i < hash.length; i++) {
bits = msb(hash[i]);
total += bits;
if (bits !== 8) {
break;
}
}
return total;
}
/**
* Adapted from nostream.
* https://github.com/Cameri/nostream/blob/fb6948fd83ca87ce552f39f9b5eb780ea07e272e/src/utils/proof-of-work.ts
*/
function msb(b: number) {
let n = 0;
if (b === 0) {
return 8;
}
// deno-lint-ignore no-cond-assign
while (b >>= 1) {
n++;
}
return 7 - n;
}
export default powPolicy;