nip-13: add pow and only invoke noxy in events with valid work

added pow to text notes, reactions and metadata events.

noxy is now only used if event has valid work proof.
OFF0 2 years ago
parent 89c54ac08f
commit 0293aee247
Signed by: offbyn
GPG Key ID: 94A2F643C51F37FA

@ -158,6 +158,4 @@
max-width: 48rem; max-width: 48rem;
padding: 1.5rem 1.8rem; padding: 1.5rem 1.8rem;
width: 100%; width: 100%;
/* TODO: revert when things calm down or we find an alternative */
display: none;
} }

@ -1,4 +1,4 @@
import {relayPool, generatePrivateKey, getPublicKey, signEvent} from 'nostr-tools'; import {relayPool, generatePrivateKey, getEventHash, getPublicKey, signEvent} from 'nostr-tools';
import {elem, parseTextContent} from './domutil.js'; import {elem, parseTextContent} from './domutil.js';
import {dateTime, formatTime} from './timeutil.js'; import {dateTime, formatTime} from './timeutil.js';
// curl -H 'accept: application/nostr+json' https://relay.nostr.ch/ // curl -H 'accept: application/nostr+json' https://relay.nostr.ch/
@ -40,6 +40,8 @@ let pubkey = localStorage.getItem('pub_key') || (() => {
return pubkey; return pubkey;
})(); })();
const difficulty = 16;
const subList = []; const subList = [];
const unSubAll = () => { const unSubAll = () => {
subList.forEach(sub => sub.unsub()); subList.forEach(sub => sub.unsub());
@ -161,9 +163,9 @@ function renderProfile(evt, relay) {
if (content) { if (content) {
profileAbout.textContent = content.about; profileAbout.textContent = content.about;
profileName.textContent = content.name; profileName.textContent = content.name;
const noxyImg = getNoxyUrl('data', content.picture, evt.id, relay); const noxyImg = validatePow(evt) && getNoxyUrl('data', content.picture, evt.id, relay);
if (noxyImg) { if (noxyImg) {
profileImage.setAttribute('src', getNoxyUrl('data', noxyImg, evt.id, relay)); profileImage.setAttribute('src', noxyImg);
profileImage.hidden = false; profileImage.hidden = false;
} }
} }
@ -450,7 +452,7 @@ function createTextNote(evt, relay) {
]), ]),
elem('div', {/* data: isLongContent ? {append: evt.content.slice(280)} : null*/}, [ elem('div', {/* data: isLongContent ? {append: evt.content.slice(280)} : null*/}, [
...content, ...content,
firstLink ? linkPreview(firstLink, evt.id, relay) : '' (firstLink && validatePow(evt)) ? linkPreview(firstLink, evt.id, relay) : '',
]), ]),
elem('button', { elem('button', {
className: 'btn-inline', name: 'star', type: 'button', className: 'btn-inline', name: 'star', type: 'button',
@ -626,7 +628,7 @@ function setMetadata(evt, relay, content) {
} }
} }
// update profile images // update profile images
if (user.picture) { if (user.picture && validatePow(evt)) {
document.body document.body
.querySelectorAll(`canvas[data-pubkey="${evt.pubkey}"]`) .querySelectorAll(`canvas[data-pubkey="${evt.pubkey}"]`)
.forEach(canvas => (canvas.parentNode.replaceChild(elem('img', {src: user.picture}), canvas))); .forEach(canvas => (canvas.parentNode.replaceChild(elem('img', {src: user.picture}), canvas)));
@ -686,7 +688,7 @@ function getMetadata(evt, relay) {
const name = user?.metadata[relay]?.name; const name = user?.metadata[relay]?.name;
const userName = name || evt.pubkey.slice(0, 8); const userName = name || evt.pubkey.slice(0, 8);
const userAbout = user?.metadata[relay]?.about || ''; const userAbout = user?.metadata[relay]?.about || '';
const img = userImg ? elem('img', { const img = (userImg && validatePow(evt)) ? elem('img', {
alt: `${userName} ${host}`, alt: `${userName} ${host}`,
loading: 'lazy', loading: 'lazy',
src: userImg, src: userImg,
@ -761,13 +763,16 @@ function hideNewMessage(hide) {
async function upvote(eventId, relay) { async function upvote(eventId, relay) {
const privatekey = localStorage.getItem('private_key'); const privatekey = localStorage.getItem('private_key');
const newReaction = { const newReaction = powEvent({
kind: 7, kind: 7,
pubkey, // TODO: lib could check that this is the pubkey of the key to sign with pubkey, // TODO: lib could check that this is the pubkey of the key to sign with
content: '+', content: '+',
tags: [['e', eventId, relay, 'reply']], tags: [
['nonce', '0', `${difficulty}`],
['e', eventId, relay, 'reply'],
],
created_at: Math.floor(Date.now() * 0.001), created_at: Math.floor(Date.now() * 0.001),
}; }, difficulty);
const sig = await signEvent(newReaction, privatekey).catch(console.error); const sig = await signEvent(newReaction, privatekey).catch(console.error);
if (sig) { if (sig) {
const ev = await pool.publish({...newReaction, sig}, (status, url) => { const ev = await pool.publish({...newReaction, sig}, (status, url) => {
@ -798,13 +803,13 @@ writeForm.addEventListener('submit', async (e) => {
} }
const replyTo = localStorage.getItem('reply_to'); const replyTo = localStorage.getItem('reply_to');
const tags = replyTo ? [['e', replyTo, eventRelayMap[replyTo][0]]] : []; const tags = replyTo ? [['e', replyTo, eventRelayMap[replyTo][0]]] : [];
const newEvent = { const newEvent = powEvent({
kind: 1, kind: 1,
pubkey,
content, content,
tags, pubkey,
tags: [['nonce', '0', `${difficulty}`], ...tags],
created_at: Math.floor(Date.now() * 0.001), created_at: Math.floor(Date.now() * 0.001),
}; }, difficulty);
const sig = await signEvent(newEvent, privatekey).catch(onSendError); const sig = await signEvent(newEvent, privatekey).catch(onSendError);
if (sig) { if (sig) {
const ev = await pool.publish({...newEvent, sig}, (status, url) => { const ev = await pool.publish({...newEvent, sig}, (status, url) => {
@ -940,13 +945,13 @@ profileForm.addEventListener('submit', async (e) => {
const form = new FormData(profileForm); const form = new FormData(profileForm);
const privatekey = localStorage.getItem('private_key'); const privatekey = localStorage.getItem('private_key');
const newProfile = { const newProfile = powEvent({
kind: 0, kind: 0,
pubkey, pubkey,
content: JSON.stringify(Object.fromEntries(form)), content: JSON.stringify(Object.fromEntries(form)),
tags: [['nonce', '0', `${difficulty}`]],
created_at: Math.floor(Date.now() * 0.001), created_at: Math.floor(Date.now() * 0.001),
tags: [], }, difficulty);
};
const sig = await signEvent(newProfile, privatekey).catch(console.error); const sig = await signEvent(newProfile, privatekey).catch(console.error);
if (sig) { if (sig) {
const ev = await pool.publish({...newProfile, sig}, (status, url) => { const ev = await pool.publish({...newProfile, sig}, (status, url) => {
@ -961,3 +966,30 @@ profileForm.addEventListener('submit', async (e) => {
}).catch(console.error); }).catch(console.error);
} }
}); });
function validatePow(evt) {
const tag = evt.tags.find(tag => tag[0] === 'nonce');
if (!tag) {
return false;
}
const [, , difficulty2] = tag;
if (difficulty2 < 16) {
return false;
}
return evt.id.substring(0, difficulty2 / 4) === '00'.repeat(difficulty2 / 8);
}
function powEvent(newEvent, difficulty) {
const chars = difficulty / 8;
let n = Number(newEvent.tags[0][1]) + 1;
// const until = Date.now() + 15000;
console.time('pow');
while (true/*Date.now() < until*/) {
newEvent.tags[0][1] = `${n++}`;
const id = getEventHash(newEvent, privatekey);
if (id.substring(0, chars * 2) === '00'.repeat(chars)) {
console.timeEnd('pow');
return newEvent;
}
}
}
Loading…
Cancel
Save