|
| 1 | +package helper |
| 2 | + |
| 3 | +import ( |
| 4 | + "crypto/rand" |
| 5 | + "crypto/sha256" |
| 6 | + "encoding/hex" |
| 7 | + "fmt" |
| 8 | + "strconv" |
| 9 | + "strings" |
| 10 | + "time" |
| 11 | +) |
| 12 | + |
| 13 | +const ( |
| 14 | + // DefaultPowDifficulty is the default number of leading zeros required in the hash |
| 15 | + DefaultPowDifficulty = 4 |
| 16 | +) |
| 17 | + |
| 18 | +// GeneratePowChallenge generates a random challenge string for proof-of-work |
| 19 | +func GeneratePowChallenge() string { |
| 20 | + // Use timestamp + random bytes for uniqueness |
| 21 | + timestamp := time.Now().UnixNano() |
| 22 | + randomBytes := make([]byte, 16) |
| 23 | + _, _ = rand.Read(randomBytes) |
| 24 | + return fmt.Sprintf("%d-%s", timestamp, hex.EncodeToString(randomBytes)) |
| 25 | +} |
| 26 | + |
| 27 | +// VerifyPowSolution verifies that a nonce produces a valid proof-of-work for the given challenge |
| 28 | +func VerifyPowSolution(challenge string, nonce int, difficulty int) bool { |
| 29 | + // Compute SHA-256(challenge + nonce) |
| 30 | + input := challenge + strconv.Itoa(nonce) |
| 31 | + hash := sha256.Sum256([]byte(input)) |
| 32 | + hashHex := hex.EncodeToString(hash[:]) |
| 33 | + |
| 34 | + // Check if hash has required number of leading zeros |
| 35 | + target := strings.Repeat("0", difficulty) |
| 36 | + return strings.HasPrefix(hashHex, target) |
| 37 | +} |
| 38 | + |
| 39 | +// GetPowJS returns the proof-of-work JavaScript implementation |
| 40 | +func GetPowJS() string { |
| 41 | + return `// Proof of Work CAPTCHA |
| 42 | +(function() { |
| 43 | + function initPoW() { |
| 44 | + var captchaDiv = document.querySelector('[data-callback]'); |
| 45 | + if (!captchaDiv) { |
| 46 | + console.error('PoW: captcha div not found'); |
| 47 | + return; |
| 48 | + } |
| 49 | +
|
| 50 | + var callbackName = captchaDiv.getAttribute('data-callback'); |
| 51 | + var challenge = captchaDiv.getAttribute('data-challenge'); |
| 52 | + var difficulty = parseInt(captchaDiv.getAttribute('data-difficulty') || '4', 10); |
| 53 | +
|
| 54 | + if (!callbackName || !challenge) { |
| 55 | + console.error('PoW: missing callback or challenge'); |
| 56 | + return; |
| 57 | + } |
| 58 | +
|
| 59 | + var progressDiv = document.createElement('div'); |
| 60 | + progressDiv.id = 'pow-progress'; |
| 61 | + progressDiv.style.marginTop = '20px'; |
| 62 | + progressDiv.style.fontFamily = 'monospace'; |
| 63 | + progressDiv.textContent = 'Computing proof of work...'; |
| 64 | + captchaDiv.parentNode.insertBefore(progressDiv, captchaDiv.nextSibling); |
| 65 | +
|
| 66 | + // Create worker from function |
| 67 | + var worker = createWorker(powWorker); |
| 68 | +
|
| 69 | + worker.onmessage = function(e) { |
| 70 | + if (e.data.nonce !== undefined) { |
| 71 | + progressDiv.textContent = 'Proof of work completed in ' + (e.data.duration / 1000).toFixed(2) + 's (tried ' + e.data.nonce + ' nonces)'; |
| 72 | + var token = challenge + ':' + e.data.nonce; |
| 73 | + if (typeof window[callbackName] === 'function') { |
| 74 | + window[callbackName](token); |
| 75 | + } |
| 76 | + worker.terminate(); |
| 77 | + } else if (e.data.progress !== undefined) { |
| 78 | + progressDiv.textContent = 'Computing proof of work... (' + e.data.progress + ' attempts, ' + (e.data.duration / 1000).toFixed(1) + 's)'; |
| 79 | + } |
| 80 | + }; |
| 81 | +
|
| 82 | + worker.onerror = function(error) { |
| 83 | + console.error('PoW worker error:', error); |
| 84 | + progressDiv.textContent = 'Error computing proof of work'; |
| 85 | + }; |
| 86 | +
|
| 87 | + worker.postMessage({ |
| 88 | + challenge: challenge, |
| 89 | + difficulty: difficulty |
| 90 | + }); |
| 91 | + } |
| 92 | +
|
| 93 | + // Helper to create worker from function |
| 94 | + function createWorker(fn) { |
| 95 | + var blob = new Blob(['(' + fn.toString() + ')()'], { type: 'application/javascript' }); |
| 96 | + return new Worker(URL.createObjectURL(blob)); |
| 97 | + } |
| 98 | +
|
| 99 | + // Worker function (will be serialized and run in worker context) |
| 100 | + function powWorker() { |
| 101 | + self.onmessage = function(e) { |
| 102 | + var challenge = e.data.challenge; |
| 103 | + var difficulty = e.data.difficulty; |
| 104 | + var target = "0".repeat(difficulty); |
| 105 | + var nonce = 0; |
| 106 | + var startTime = Date.now(); |
| 107 | + |
| 108 | + while (true) { |
| 109 | + var hash = sha256(challenge + nonce); |
| 110 | + if (hash.substring(0, difficulty) === target) { |
| 111 | + self.postMessage({ nonce: nonce, hash: hash, duration: Date.now() - startTime }); |
| 112 | + return; |
| 113 | + } |
| 114 | + nonce++; |
| 115 | + if (nonce % 10000 === 0) { |
| 116 | + self.postMessage({ progress: nonce, duration: Date.now() - startTime }); |
| 117 | + } |
| 118 | + } |
| 119 | + }; |
| 120 | +
|
| 121 | + function sha256(ascii) { |
| 122 | + function rightRotate(value, amount) { return (value >>> amount) | (value << (32 - amount)); } |
| 123 | + var mathPow = Math.pow, maxWord = mathPow(2, 32), lengthProperty = "length", i, j, result = ""; |
| 124 | + var words = [], asciiBitLength = ascii[lengthProperty] * 8; |
| 125 | + var hash = sha256.h = sha256.h || [], k = sha256.k = sha256.k || [], primeCounter = k[lengthProperty]; |
| 126 | + var isComposite = {}; |
| 127 | + for (var candidate = 2; primeCounter < 64; candidate++) { |
| 128 | + if (!isComposite[candidate]) { |
| 129 | + for (i = 0; i < 313; i += candidate) { isComposite[i] = candidate; } |
| 130 | + hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0; |
| 131 | + k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0; |
| 132 | + } |
| 133 | + } |
| 134 | + ascii += "\x80"; |
| 135 | + while (ascii[lengthProperty] % 64 - 56) ascii += "\x00"; |
| 136 | + for (i = 0; i < ascii[lengthProperty]; i++) { |
| 137 | + j = ascii.charCodeAt(i); |
| 138 | + if (j >> 8) return; |
| 139 | + words[i >> 2] |= j << ((3 - i) % 4) * 8; |
| 140 | + } |
| 141 | + words[words[lengthProperty]] = ((asciiBitLength / maxWord) | 0); |
| 142 | + words[words[lengthProperty]] = (asciiBitLength); |
| 143 | + for (j = 0; j < words[lengthProperty];) { |
| 144 | + var w = words.slice(j, j += 16), oldHash = hash; |
| 145 | + hash = hash.slice(0, 8); |
| 146 | + for (i = 0; i < 64; i++) { |
| 147 | + var w15 = w[i - 15], w2 = w[i - 2], a = hash[0], e = hash[4]; |
| 148 | + var temp1 = hash[7] + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) + ((e & hash[5]) ^ ((~e) & hash[6])) + k[i] + (w[i] = (i < 16) ? w[i] : (w[i - 16] + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) + w[i - 7] + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))) | 0); |
| 149 | + var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2])); |
| 150 | + hash = [(temp1 + temp2) | 0].concat(hash); |
| 151 | + hash[4] = (hash[4] + temp1) | 0; |
| 152 | + } |
| 153 | + for (i = 0; i < 8; i++) { hash[i] = (hash[i] + oldHash[i]) | 0; } |
| 154 | + } |
| 155 | + for (i = 0; i < 8; i++) { |
| 156 | + for (j = 3; j + 1; j--) { |
| 157 | + var b = (hash[i] >> (j * 8)) & 255; |
| 158 | + result += ((b < 16) ? 0 : "") + b.toString(16); |
| 159 | + } |
| 160 | + } |
| 161 | + return result; |
| 162 | + } |
| 163 | + } |
| 164 | +
|
| 165 | + if (document.readyState === 'loading') { |
| 166 | + document.addEventListener('DOMContentLoaded', initPoW); |
| 167 | + } else { |
| 168 | + initPoW(); |
| 169 | + } |
| 170 | +})();` |
| 171 | +} |
0 commit comments