@vantezzen/pow
Version:
A simple challenge-response proof-of-work implementation for web apps
4 lines (2 loc) • 4.81 kB
JavaScript
var g=Object.defineProperty;var m=(i,e,t)=>e in i?g(i,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):i[e]=t;var u=(i,e,t)=>(m(i,typeof e!="symbol"?e+"":e,t),t);(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))s(n);new MutationObserver(n=>{for(const r of n)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&s(o)}).observe(document,{childList:!0,subtree:!0});function t(n){const r={};return n.integrity&&(r.integrity=n.integrity),n.referrerPolicy&&(r.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?r.credentials="include":n.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function s(n){if(n.ep)return;n.ep=!0;const r=t(n);fetch(n.href,r)}})();const v="modulepreload",S=function(i){return"/dist/"+i},w={},C=function(e,t,s){if(!t||t.length===0)return e();const n=document.getElementsByTagName("link");return Promise.all(t.map(r=>{if(r=S(r),r in w)return;w[r]=!0;const o=r.endsWith(".css"),c=o?'[rel="stylesheet"]':"";if(!!s)for(let y=n.length-1;y>=0;y--){const p=n[y];if(p.href===r&&(!o||p.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${r}"]${c}`))return;const l=document.createElement("link");if(l.rel=o?"stylesheet":v,o||(l.as="script",l.crossOrigin=""),l.href=r,document.head.appendChild(l),o)return new Promise((y,p)=>{l.addEventListener("load",y),l.addEventListener("error",()=>p(new Error(`Unable to preload CSS for ${r}`)))})})).then(()=>e())};function d(i){return i.map(e=>e.toString(16).padStart(2,"0")).join("")}class f{constructor(e){u(this,"key",null);u(this,"crypto",null);this.secret=e}async prepareCrypto(){this.crypto||(typeof window<"u"?this.crypto=window.crypto:this.crypto=await C(()=>import("./webcrypto.es-d636e2d7.js"),[]).then(e=>new e.Crypto))}async prepareKey(){if(await this.prepareCrypto(),this.key)return;if(!this.secret)throw new Error("No secret provided");const e=new TextEncoder,t=await this.crypto.subtle.digest("SHA-256",e.encode(this.secret));this.key=await this.crypto.subtle.importKey("raw",t,"AES-GCM",!1,["encrypt","decrypt"])}async encryptValue(e){await this.prepareKey();const t=this.crypto.getRandomValues(new Uint8Array(12)),n=new TextEncoder().encode(e),r=await this.crypto.subtle.encrypt({name:"AES-GCM",iv:t},this.key,n),o=Array.from(new Uint8Array(r)),c=d(o);return d(Array.from(t))+c}async decryptValue(e){await this.prepareKey();const t=new Uint8Array(e.substring(0,24).match(/.{1,2}/g).map(o=>parseInt(o,16))),s=new Uint8Array(e.substring(24).match(/.{1,2}/g).map(o=>parseInt(o,16))),n=await this.crypto.subtle.decrypt({name:"AES-GCM",iv:t},this.key,s);return new TextDecoder().decode(n)}async generateSecret(){await this.prepareCrypto();const e=await this.crypto.subtle.generateKey({name:"AES-GCM",length:256},!0,["encrypt","decrypt"]),t=await this.crypto.subtle.exportKey("raw",e),s=Array.from(new Uint8Array(t));return d(s)}async hash(e){await this.prepareCrypto();const t=new TextEncoder().encode(e),s=await this.crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(s)).map(o=>o.toString(16).padStart(2,"0")).join("")}}const A={difficulty:4,timeout:1e3*10};class E{constructor(e=A){u(this,"crypto",new f);this.config=e}async solveChallenge(e){let t=0,s=Date.now();for(;;){if((await this.crypto.hash(e+t)).startsWith("0".repeat(this.config.difficulty)))return t.toString();if(t++,Date.now()-s>this.config.timeout)throw new Error("Timeout")}}}const P={difficulty:4,validity:1e3*15};class V{constructor(e,t=P){u(this,"crypto");this.config=t,this.crypto=new f(e)}async verifyProofOfWork(e,t){if(!(await this.crypto.hash(e+t)).startsWith("0".repeat(this.config.difficulty)))return{isValid:!1,error:"Invalid nonce"};let r;try{r=await this.crypto.decryptValue(e)}catch{return{isValid:!1,error:"Invalid payload"}}const o=parseInt(r);if(isNaN(o))return{isValid:!1,error:"Payload is not a valid timestamp"};const c=Date.now();return c-o>this.config.validity?{isValid:!1,error:"Payload is expired"}:c<o?{isValid:!1,error:"Payload is in the future"}:{isValid:!0}}async createChallenge(){const e=Date.now().toString();return this.crypto.encryptValue(e)}}const O=document.getElementById("out"),a=i=>{O.innerHTML+=`
`+i},N=async()=>{a("Generating secret key...");const e=await new f().generateSecret();a("Secret: "+e);const t=new E,s=new V(e);a("Creating challenge...");const n=await s.createChallenge();a("Challenge: "+n),a("Solving challenge...");const r=await t.solveChallenge(n);a("Nonce: "+r),a("Testing valid proof of work...");const o=await s.verifyProofOfWork(n,r);a(JSON.stringify(o)),a("Testing invalid proof of work...");const c=await s.verifyProofOfWork(n,"123");a(JSON.stringify(c))};N();