// www.hashcash.org/docs/hashcash.html // <input type=“hiden” name=“hashcash” data-hashcash=“{resource: ‘site.example’, bits: 16}”/> Hashcash = function(input) {
options = JSON.parse(input.getAttribute("data-hashcash")) Hashcash.disableParentForm(input, options) input.dispatchEvent(new CustomEvent("hashcash:mint", {bubbles: true})) Hashcash.mint(options.resource, options, function(stamp) { input.value = stamp.toString() Hashcash.enableParentForm(input, options) input.dispatchEvent(new CustomEvent("hashcash:minted", {bubbles: true, detail: {stamp: stamp}})) }) this.input = input input.form.addEventListener("submit", this.preventFromAutoSubmitFromPasswordManagers.bind(this))
}
Hashcash.setup = function() {
if (document.readyState != "loading") { var input = document.querySelector("input#hashcash") input && new Hashcash(input) } else document.addEventListener("DOMContentLoaded", Hashcash.setup )
}
Hashcash.setSubmitText = function(submit, text) {
if (!text) { return } if (submit.tagName == "BUTTON") { !submit.originalValue && (submit.originalValue = submit.innerHTML) submit.innerHTML = text } else { !submit.originalValue && (submit.originalValue = submit.value) submit.value = text }
}
Hashcash.disableParentForm = function(input, options) {
input.form.querySelectorAll("[type=submit]").forEach(function(submit) { Hashcash.setSubmitText(submit, options["waiting_message"]) submit.disabled = true })
}
Hashcash.enableParentForm = function(input, options) {
input.form.querySelectorAll("[type=submit]").forEach(function(submit) { Hashcash.setSubmitText(submit, submit.originalValue) submit.disabled = null })
}
Hashcash.prototype.preventFromAutoSubmitFromPasswordManagers = function(event) {
this.input.value == "" && event.preventDefault()
}
Hashcash.default = {
version: 1, bits: 20, extension: null,
}
Hashcash.mint = function(resource, options, callback) {
// Format date to YYMMDD var date = new Date var year = date.getFullYear().toString() year = year.slice(year.length - 2, year.length) var month = (date.getMonth() + 1).toString().padStart(2, "0") var day = date.getDate().toString().padStart(2, "0") var stamp = new Hashcash.Stamp( options.version || Hashcash.default.version, options.bits || Hashcash.default.bits, options.date || year + month + day, resource, options.extension || Hashcash.default.extension, options.rand || Math.random().toString(36).substr(2, 10), ) return stamp.work(callback)
}
Hashcash.Stamp = function(version, bits, date, resource, extension, rand, counter = 0) {
this.version = version this.bits = bits this.date = date this.resource = resource this.extension = extension this.rand = rand this.counter = counter
}
Hashcash.Stamp.parse = function(string) {
var args = string.split(":") return new Hashcash.Stamp(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
}
Hashcash.Stamp.prototype.toString = function() {
return [this.version, this.bits, this.date, this.resource, this.extension, this.rand, this.counter].join(":")
}
// Trigger the given callback when the problem is solved. // In order to not freeze the page, setTimeout is called every 100ms to let some CPU to other tasks. Hashcash.Stamp.prototype.work = function(callback) {
this.startClock() var timer = performance.now() while (!this.check()) if (this.counter++ && performance.now() - timer > 100) return setTimeout(this.work.bind(this), 0, callback) this.stopClock() callback(this)
}
Hashcash.Stamp.prototype.check = function() {
var array = Hashcash.sha1(this.toString()) return array[0] >> (160-this.bits) == 0
}
Hashcash.Stamp.prototype.startClock = function() {
this.startedAt || (this.startedAt = performance.now())
}
Hashcash.Stamp.prototype.stopClock = function() {
this.endedAt || (this.endedAt = performance.now()) var duration = this.endedAt - this.startedAt var speed = Math.round(this.counter * 1000 / duration) console.debug("Hashcash " + this.toString() + " minted in " + duration + "ms (" + speed + " per seconds)")
}
/**
* Secure Hash Algorithm (SHA1) * http://www.webtoolkit.info/ **/
Hashcash.sha1 = function(msg) {
var rotate_left = Hashcash.sha1.rotate_left var Utf8Encode = Hashcash.sha1.Utf8Encode var blockstart; var i, j; var W = new Array(80); var H0 = 0x67452301; var H1 = 0xEFCDAB89; var H2 = 0x98BADCFE; var H3 = 0x10325476; var H4 = 0xC3D2E1F0; var A, B, C, D, E; var temp; msg = Utf8Encode(msg); var msg_len = msg.length; var word_array = new Array(); for (i = 0; i < msg_len - 3; i += 4) { j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 | msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3); word_array.push(j); } switch (msg_len % 4) { case 0: i = 0x080000000; break; case 1: i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000; break; case 2: i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000; break; case 3: i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80; break; } word_array.push(i); while ((word_array.length % 16) != 14) word_array.push(0); word_array.push(msg_len >>> 29); word_array.push((msg_len << 3) & 0x0ffffffff); for (blockstart = 0; blockstart < word_array.length; blockstart += 16) { for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i]; for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1); A = H0; B = H1; C = H2; D = H3; E = H4; for (i = 0; i <= 19; i++) { temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff; E = D; D = C; C = rotate_left(B, 30); B = A; A = temp; } for (i = 20; i <= 39; i++) { temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff; E = D; D = C; C = rotate_left(B, 30); B = A; A = temp; } for (i = 40; i <= 59; i++) { temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff; E = D; D = C; C = rotate_left(B, 30); B = A; A = temp; } for (i = 60; i <= 79; i++) { temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff; E = D; D = C; C = rotate_left(B, 30); B = A; A = temp; } H0 = (H0 + A) & 0x0ffffffff; H1 = (H1 + B) & 0x0ffffffff; H2 = (H2 + C) & 0x0ffffffff; H3 = (H3 + D) & 0x0ffffffff; H4 = (H4 + E) & 0x0ffffffff; } return [H0, H1, H2, H3, H4]
}
Hashcash.hexSha1 = function(msg) {
var array = Hashcash.sha1(msg) var cvt_hex = Hashcash.sha1.cvt_hex return cvt_hex(array[0]) + cvt_hex(array[1]) + cvt_hex(array[2]) + cvt_hex(array3) + cvt_hex(array[4])
}
Hashcash.sha1.rotate_left = function(n, s) {
var t4 = (n << s) | (n >>> (32 - s)); return t4;
};
Hashcash.sha1.lsb_hex = function(val) {
var str = ''; var i; var vh; var vl; for (i = 0; i <= 6; i += 2) { vh = (val >>> (i * 4 + 4)) & 0x0f; vl = (val >>> (i * 4)) & 0x0f; str += vh.toString(16) + vl.toString(16); } return str;
};
Hashcash.sha1.cvt_hex = function(val) {
var str = ''; var i; var v; for (i = 7; i >= 0; i--) { v = (val >>> (i * 4)) & 0x0f; str += v.toString(16); } return str;
};
Hashcash.sha1.Utf8Encode = function(string) {
string = string.replace(/\r\n/g, '\n'); var utftext = ''; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if ((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext;
};
Hashcash.setup()