Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add verifyPow #315

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ package-lock.json
.envrc
lib
test.html
bun.lockb
103 changes: 102 additions & 1 deletion nip13.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { getPow, minePow } from './nip13.ts'
import { getPow, minePow, verifyPow } from './nip13.ts'
import { getPublicKey } from './keys.ts'
import { type UnsignedEvent, type Event, getEventHash, finishEvent } from './event.ts'

test('identifies proof-of-work difficulty', async () => {
const id = '000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358'
Expand All @@ -22,3 +24,102 @@ test('mines POW for an event', async () => {

expect(getPow(event.id)).toBeGreaterThanOrEqual(difficulty)
})

function fakePow<K extends number>(unsigned: UnsignedEvent<K>, targetDifficulty: number, difficulty: number): Omit<Event<K>, 'sig'> {
let count = 0

const event = unsigned as Omit<Event<K>, 'sig'>
const tag = ['nonce', count.toString(), targetDifficulty.toString()]

event.tags.push(tag)

while (true) {
const now = Math.floor(new Date().getTime() / 1000)

if (now !== event.created_at) {
count = 0
event.created_at = now
}

tag[1] = (++count).toString()

event.id = getEventHash(event)

if (getPow(event.id) >= difficulty) {
break
}
}

return event
}

describe('verifyPow', () => {
it('should return difficulty 10', () => {
const difficulty = 10

const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf'
const publicKey = getPublicKey(privateKey)
const unsignedEvent = minePow(
{
kind: 1,
tags: [],
content: 'Hello, world!',
created_at: 0,
pubkey: publicKey,
},
difficulty,
)

const event = finishEvent(unsignedEvent, privateKey);

expect(verifyPow(event)).toBeGreaterThanOrEqual(difficulty)
})

it('should return difficulty 0 for no nonce tag', () => {
const difficulty = 10

const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf'
const publicKey = getPublicKey(privateKey)
const unsignedEvent = minePow(
{
kind: 1,
tags: [],
content: 'Hello, world!',
created_at: 0,
pubkey: publicKey,
},
difficulty,
)

const event = finishEvent(unsignedEvent, privateKey);

let noNonceTagEvent = event;
noNonceTagEvent.tags = [];

expect(verifyPow(noNonceTagEvent)).toEqual(0)
})

it('should return difficulty 5- target difficulty is lower than difficulty from hash', () => {
const targetDifficulty = 5
const difficulty = 10

const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf'
const publicKey = getPublicKey(privateKey)
const unsignedEvent = fakePow(
{
kind: 1,
tags: [],
content: 'Hello, world!',
created_at: 0,
pubkey: publicKey,
},
targetDifficulty,
difficulty
)

const event = finishEvent(unsignedEvent, privateKey);

expect(verifyPow(event)).toBeLessThan(getPow(event.id))
expect(verifyPow(event)).toEqual(targetDifficulty)
})
})
16 changes: 16 additions & 0 deletions nip13.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,19 @@ export function minePow<K extends number>(unsigned: UnsignedEvent<K>, difficulty

return event
}

/** Verify POW difficulty for a NOSTR event. */
export function verifyPow(event: Event): number {
const hash = getEventHash(event)
const count = getPow(hash)

// Extract the target difficulty level from the tags
const nonceTag = event.tags.find(tag => tag[0] === 'nonce');
if (!nonceTag || nonceTag.length < 3) {
return 0 // Without specifying target difficulty, there is no PROOF of work
}
const targetDifficulty = parseInt(nonceTag[2], 10);

// The proof-of-work is the minimum of actual hash result and target
return Math.min(count, targetDifficulty)
}