active_hashcash 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,22 @@
1
1
  // http://www.hashcash.org/docs/hashcash.html
2
- // <input type="hiden" name="hashcash" data-hashcash="{resource: 'site.example', bits: 16}"/>
2
+ // <input type="hidden" name="hashcash" data-hashcash="{resource: 'site.example', bits: 16}"/>
3
3
  Hashcash = function(input) {
4
4
  options = JSON.parse(input.getAttribute("data-hashcash"))
5
5
  Hashcash.disableParentForm(input, options)
6
6
  input.dispatchEvent(new CustomEvent("hashcash:mint", {bubbles: true}))
7
7
 
8
8
  Hashcash.mint(options.resource, options, function(stamp) {
9
+ // Guard against Turbo navigation: if the input is no longer in the DOM,
10
+ // the user has navigated away and we should silently discard the result.
11
+ if (!input.isConnected) return
12
+
9
13
  input.value = stamp.toString()
10
14
  Hashcash.enableParentForm(input, options)
11
15
  input.dispatchEvent(new CustomEvent("hashcash:minted", {bubbles: true, detail: {stamp: stamp}}))
12
16
  })
17
+
18
+ this.input = input
19
+ input.form.addEventListener("submit", this.preventFromAutoSubmitFromPasswordManagers.bind(this))
13
20
  }
14
21
 
15
22
  Hashcash.setup = function() {
@@ -17,83 +24,112 @@ Hashcash.setup = function() {
17
24
  var input = document.querySelector("input#hashcash")
18
25
  input && new Hashcash(input)
19
26
  } else
20
- document.addEventListener("DOMContentLoaded", Hashcash.setup )
27
+ document.addEventListener("DOMContentLoaded", Hashcash.setup)
28
+ }
29
+
30
+ // Terminate the active worker and clean up its Blob URL.
31
+ Hashcash.cleanup = function() {
32
+ if (Hashcash._worker) {
33
+ Hashcash._worker.terminate()
34
+ Hashcash._worker = null
35
+ }
36
+ if (Hashcash._workerUrl) {
37
+ URL.revokeObjectURL(Hashcash._workerUrl)
38
+ Hashcash._workerUrl = null
39
+ }
40
+ }
41
+
42
+ // Turbo Drive: terminate the worker when navigating away so it doesn't
43
+ // complete after the page has changed, and restore the form state for
44
+ // Turbo's page cache so the snapshot doesn't have disabled buttons.
45
+ document.addEventListener("turbo:before-visit", Hashcash.cleanup)
46
+ document.addEventListener("turbo:before-cache", function() {
47
+ Hashcash.cleanup()
48
+ var input = document.querySelector("input#hashcash")
49
+ if (input && input.form) {
50
+ input.value = ""
51
+ input.form.querySelectorAll("[type=submit]").forEach(function(submit) {
52
+ if (submit.originalValue) {
53
+ Hashcash.setSubmitText(submit, submit.originalValue)
54
+ }
55
+ submit.disabled = null
56
+ })
57
+ }
58
+ })
59
+
60
+ Hashcash.setSubmitText = function(submit, text) {
61
+ if (!text) {
62
+ return
63
+ }
64
+ if (submit.tagName == "BUTTON") {
65
+ !submit.originalValue && (submit.originalValue = submit.innerHTML)
66
+ submit.innerHTML = text
67
+ } else {
68
+ !submit.originalValue && (submit.originalValue = submit.value)
69
+ submit.value = text
70
+ }
21
71
  }
22
72
 
23
73
  Hashcash.disableParentForm = function(input, options) {
24
74
  input.form.querySelectorAll("[type=submit]").forEach(function(submit) {
25
- submit.originalValue = submit.value
26
- options["waiting_message"] && (submit.value = options["waiting_message"])
75
+ Hashcash.setSubmitText(submit, options["waiting_message"])
27
76
  submit.disabled = true
28
77
  })
29
78
  }
30
79
 
31
80
  Hashcash.enableParentForm = function(input, options) {
32
81
  input.form.querySelectorAll("[type=submit]").forEach(function(submit) {
33
- submit.originalValue && (submit.value = submit.originalValue)
82
+ Hashcash.setSubmitText(submit, submit.originalValue)
34
83
  submit.disabled = null
35
84
  })
36
85
  }
37
86
 
87
+ Hashcash.prototype.preventFromAutoSubmitFromPasswordManagers = function(event) {
88
+ this.input.value == "" && event.preventDefault()
89
+ }
90
+
38
91
  Hashcash.default = {
39
92
  version: 1,
40
93
  bits: 20,
41
- extension: null,
42
94
  }
43
95
 
44
96
  Hashcash.mint = function(resource, options, callback) {
45
- // Format date to YYMMDD
46
- var date = new Date
47
- var year = date.getFullYear().toString()
48
- year = year.slice(year.length - 2, year.length)
49
- var month = (date.getMonth() + 1).toString().padStart(2, "0")
50
- var day = date.getDate().toString().padStart(2, "0")
51
-
52
97
  var stamp = new Hashcash.Stamp(
53
98
  options.version || Hashcash.default.version,
54
99
  options.bits || Hashcash.default.bits,
55
- options.date || year + month + day,
100
+ options.date || Hashcash.formatToday(),
56
101
  resource,
57
- options.extension || Hashcash.default.extension,
58
- options.rand || Math.random().toString(36).substr(2, 10),
102
+ options.rand || Math.random().toString(36).substr(2, 10)
59
103
  )
60
104
  return stamp.work(callback)
61
105
  }
62
106
 
63
- Hashcash.Stamp = function(version, bits, date, resource, extension, rand, counter = 0) {
107
+ // Format date to YYMMDD
108
+ Hashcash.formatToday = function() {
109
+ var date = new Date
110
+ var year = date.getFullYear().toString()
111
+ year = year.slice(year.length - 2, year.length)
112
+ var month = (date.getMonth() + 1).toString().padStart(2, "0")
113
+ var day = date.getDate().toString().padStart(2, "0")
114
+ return year + month + day
115
+ }
116
+
117
+ Hashcash.Stamp = function(version, bits, date, resource, rand, counter) {
64
118
  this.version = version
65
119
  this.bits = bits
66
120
  this.date = date
67
121
  this.resource = resource
68
- this.extension = extension
69
122
  this.rand = rand
70
- this.counter = counter
123
+ this.counter = counter || 0
71
124
  }
72
125
 
73
126
  Hashcash.Stamp.parse = function(string) {
74
127
  var args = string.split(":")
75
- return new Hashcash.Stamp(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
128
+ return new Hashcash.Stamp(args[0], args[1], args[2], args[3], args[5], args[6])
76
129
  }
77
130
 
78
131
  Hashcash.Stamp.prototype.toString = function() {
79
- return [this.version, this.bits, this.date, this.resource, this.extension, this.rand, this.counter].join(":")
80
- }
81
-
82
- // Trigger the given callback when the problem is solved.
83
- // In order to not freeze the page, setTimeout is called every 100ms to let some CPU to other tasks.
84
- Hashcash.Stamp.prototype.work = function(callback) {
85
- this.startClock()
86
- var timer = performance.now()
87
- while (!this.check())
88
- if (this.counter++ && performance.now() - timer > 100)
89
- return setTimeout(this.work.bind(this), 0, callback)
90
- this.stopClock()
91
- callback(this)
92
- }
93
-
94
- Hashcash.Stamp.prototype.check = function() {
95
- var array = Hashcash.sha1(this.toString())
96
- return array[0] >> (160-this.bits) == 0
132
+ return [this.version, this.bits, this.date, this.resource, "sha256", this.rand, this.counter].join(":")
97
133
  }
98
134
 
99
135
  Hashcash.Stamp.prototype.startClock = function() {
@@ -107,151 +143,176 @@ Hashcash.Stamp.prototype.stopClock = function() {
107
143
  console.debug("Hashcash " + this.toString() + " minted in " + duration + "ms (" + speed + " per seconds)")
108
144
  }
109
145
 
110
- /**
111
- * Secure Hash Algorithm (SHA1)
112
- * http://www.webtoolkit.info/
113
- **/
114
- Hashcash.sha1 = function(msg) {
115
- var rotate_left = Hashcash.sha1.rotate_left
116
- var Utf8Encode = Hashcash.sha1.Utf8Encode
117
-
118
- var blockstart;
119
- var i, j;
120
- var W = new Array(80);
121
- var H0 = 0x67452301;
122
- var H1 = 0xEFCDAB89;
123
- var H2 = 0x98BADCFE;
124
- var H3 = 0x10325476;
125
- var H4 = 0xC3D2E1F0;
126
- var A, B, C, D, E;
127
- var temp;
128
- msg = Utf8Encode(msg);
129
- var msg_len = msg.length;
130
- var word_array = new Array();
131
- for (i = 0; i < msg_len - 3; i += 4) {
132
- j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 |
133
- msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3);
134
- word_array.push(j);
135
- }
136
- switch (msg_len % 4) {
137
- case 0:
138
- i = 0x080000000;
139
- break;
140
- case 1:
141
- i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000;
142
- break;
143
- case 2:
144
- i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000;
145
- break;
146
- case 3:
147
- i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80;
148
- break;
149
- }
150
- word_array.push(i);
151
- while ((word_array.length % 16) != 14) word_array.push(0);
152
- word_array.push(msg_len >>> 29);
153
- word_array.push((msg_len << 3) & 0x0ffffffff);
154
- for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
155
- for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i];
156
- for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
157
- A = H0;
158
- B = H1;
159
- C = H2;
160
- D = H3;
161
- E = H4;
162
- for (i = 0; i <= 19; i++) {
163
- temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
164
- E = D;
165
- D = C;
166
- C = rotate_left(B, 30);
167
- B = A;
168
- A = temp;
169
- }
170
- for (i = 20; i <= 39; i++) {
171
- temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
172
- E = D;
173
- D = C;
174
- C = rotate_left(B, 30);
175
- B = A;
176
- A = temp;
146
+ // Mine a valid stamp using a Web Worker with a pure JS SHA-256 implementation.
147
+ // The worker is inlined as a Blob URL so no extra file needs to be served.
148
+ // Using a Web Worker keeps the main thread completely unblocked while mining.
149
+ // A synchronous SHA-256 in a tight loop is faster than async crypto.subtle
150
+ // because it avoids the per-call Promise/microtask overhead entirely.
151
+ Hashcash.Stamp.prototype.work = function(callback) {
152
+ this.startClock()
153
+ var self = this
154
+
155
+ var workerCode = function() {
156
+ // SHA-256 round constants (FIPS 180-4 §4.2.2)
157
+ var K = new Uint32Array([
158
+ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
159
+ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
160
+ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
161
+ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
162
+ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
163
+ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
164
+ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
165
+ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
166
+ ]);
167
+
168
+ // Compute SHA-256 over the first `len` bytes of `bytes`. Returns an array
169
+ // of 8 big-endian 32-bit integers (the 256-bit digest).
170
+ function sha256bytes(bytes, len) {
171
+ // Pad to a multiple of 64 bytes: append 0x80, then zeros, then 64-bit
172
+ // big-endian bit length (we only use the low 32 bits since stamp strings
173
+ // are always shorter than 512 MB).
174
+ var padded = new Uint8Array(((len + 9 + 63) & ~63));
175
+ for (var i = 0; i < len; i++) padded[i] = bytes[i];
176
+ padded[len] = 0x80;
177
+ var bitLen = len * 8;
178
+ var pLen = padded.length;
179
+ padded[pLen - 4] = (bitLen >>> 24) & 0xff;
180
+ padded[pLen - 3] = (bitLen >>> 16) & 0xff;
181
+ padded[pLen - 2] = (bitLen >>> 8) & 0xff;
182
+ padded[pLen - 1] = bitLen & 0xff;
183
+
184
+ // Initial hash values (FIPS 180-4 §5.3.3)
185
+ var H0 = 0x6a09e667, H1 = 0xbb67ae85, H2 = 0x3c6ef372, H3 = 0xa54ff53a;
186
+ var H4 = 0x510e527f, H5 = 0x9b05688c, H6 = 0x1f83d9ab, H7 = 0x5be0cd19;
187
+ var W = new Int32Array(64);
188
+
189
+ // Process each 512-bit (64-byte) block
190
+ for (var off = 0; off < pLen; off += 64) {
191
+ for (var t = 0; t < 16; t++) {
192
+ W[t] = (padded[off+t*4]<<24)|(padded[off+t*4+1]<<16)|(padded[off+t*4+2]<<8)|padded[off+t*4+3];
193
+ }
194
+ for (var t = 16; t < 64; t++) {
195
+ var w15 = W[t-15], w2 = W[t-2];
196
+ W[t] = (W[t-16] + (((w15>>>7)|(w15<<25))^((w15>>>18)|(w15<<14))^(w15>>>3)) + W[t-7] + (((w2>>>17)|(w2<<15))^((w2>>>19)|(w2<<13))^(w2>>>10))) | 0;
197
+ }
198
+ var a=H0,b=H1,c=H2,d=H3,e=H4,f=H5,g=H6,h=H7;
199
+ for (var t = 0; t < 64; t++) {
200
+ var t1 = (h + (((e>>>6)|(e<<26))^((e>>>11)|(e<<21))^((e>>>25)|(e<<7))) + ((e&f)^(~e&g)) + K[t] + W[t]) | 0;
201
+ var t2 = ((((a>>>2)|(a<<30))^((a>>>13)|(a<<19))^((a>>>22)|(a<<10))) + ((a&b)^(a&c)^(b&c))) | 0;
202
+ h=g; g=f; f=e; e=(d+t1)|0; d=c; c=b; b=a; a=(t1+t2)|0;
203
+ }
204
+ H0=(H0+a)|0; H1=(H1+b)|0; H2=(H2+c)|0; H3=(H3+d)|0;
205
+ H4=(H4+e)|0; H5=(H5+f)|0; H6=(H6+g)|0; H7=(H7+h)|0;
206
+ }
207
+ return [H0, H1, H2, H3, H4, H5, H6, H7];
177
208
  }
178
- for (i = 40; i <= 59; i++) {
179
- temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
180
- E = D;
181
- D = C;
182
- C = rotate_left(B, 30);
183
- B = A;
184
- A = temp;
209
+
210
+ // Encode a string as bytes into a pre-allocated buffer at `offset`.
211
+ // Handles ASCII and basic UTF-8 (BMP only).
212
+ function strToBytes(str, out, offset) {
213
+ for (var i = 0; i < str.length; i++) {
214
+ var c = str.charCodeAt(i);
215
+ if (c < 128) out[offset++] = c;
216
+ else if (c < 2048) { out[offset++] = (c>>6)|192; out[offset++] = (c&63)|128; }
217
+ else { out[offset++] = (c>>12)|224; out[offset++] = ((c>>6)&63)|128; out[offset++] = (c&63)|128; }
218
+ }
219
+ return offset;
185
220
  }
186
- for (i = 60; i <= 79; i++) {
187
- temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
188
- E = D;
189
- D = C;
190
- C = rotate_left(B, 30);
191
- B = A;
192
- A = temp;
221
+
222
+ // Write the decimal digits of `n` as ASCII bytes into `out` at `offset`.
223
+ // Avoids string allocation in the hot loop.
224
+ function numToBytes(n, out, offset) {
225
+ if (n === 0) { out[offset] = 48; return offset + 1; }
226
+ var digs = [];
227
+ while (n > 0) { digs.push(48 + (n % 10)); n = (n / 10) | 0; }
228
+ for (var i = digs.length - 1; i >= 0; i--) out[offset++] = digs[i];
229
+ return offset;
193
230
  }
194
- H0 = (H0 + A) & 0x0ffffffff;
195
- H1 = (H1 + B) & 0x0ffffffff;
196
- H2 = (H2 + C) & 0x0ffffffff;
197
- H3 = (H3 + D) & 0x0ffffffff;
198
- H4 = (H4 + E) & 0x0ffffffff;
199
- }
200
- return [H0, H1, H2, H3, H4]
201
- }
202
231
 
203
- Hashcash.hexSha1 = function(msg) {
204
- var array = Hashcash.sha1(msg)
205
- var cvt_hex = Hashcash.sha1.cvt_hex
206
- return cvt_hex(array[0]) + cvt_hex(array[1]) + cvt_hex(array[2]) + cvt_hex(array3) + cvt_hex(array[4])
207
- }
232
+ self.addEventListener("message", function(e) {
233
+ var d = e.data;
234
+ var prefix = d.prefix + ":";
235
+ var bits = d.bits;
236
+ var counter = 0;
208
237
 
209
- Hashcash.sha1.rotate_left = function(n, s) {
210
- var t4 = (n << s) | (n >>> (32 - s));
211
- return t4;
212
- };
213
-
214
- Hashcash.sha1.lsb_hex = function(val) {
215
- var str = '';
216
- var i;
217
- var vh;
218
- var vl;
219
- for (i = 0; i <= 6; i += 2) {
220
- vh = (val >>> (i * 4 + 4)) & 0x0f;
221
- vl = (val >>> (i * 4)) & 0x0f;
222
- str += vh.toString(16) + vl.toString(16);
223
- }
224
- return str;
225
- };
226
-
227
- Hashcash.sha1.cvt_hex = function(val) {
228
- var str = '';
229
- var i;
230
- var v;
231
- for (i = 7; i >= 0; i--) {
232
- v = (val >>> (i * 4)) & 0x0f;
233
- str += v.toString(16);
238
+ // Pre-compute how many leading zero bytes and remaining bits to check
239
+ var fullBytes = Math.floor(bits / 8);
240
+ var remBits = bits % 8;
241
+ var mask = remBits > 0 ? (0xFF << (8 - remBits)) & 0xFF : 0;
242
+
243
+ // Pre-encode the stamp prefix (everything before the counter) into a
244
+ // reusable byte buffer. Only the counter digits change per iteration.
245
+ var buf = new Uint8Array(prefix.length + 12);
246
+ var prefixLen = strToBytes(prefix, buf, 0);
247
+
248
+ // Yield every 65536 iterations so the worker stays terminable.
249
+ var YIELD = 65536;
250
+
251
+ function mine() {
252
+ var end = counter + YIELD;
253
+ while (counter < end) {
254
+ var len = numToBytes(counter, buf, prefixLen);
255
+ var H = sha256bytes(buf, len);
256
+
257
+ // Check leading zero bits by walking the digest words byte-by-byte.
258
+ var ok = true, byteIdx = 0;
259
+ check:
260
+ for (var w = 0; w < 8 && byteIdx <= fullBytes; w++) {
261
+ var word = H[w];
262
+ for (var s = 24; s >= 0; s -= 8) {
263
+ var bv = (word >>> s) & 0xFF;
264
+ if (byteIdx < fullBytes) {
265
+ if (bv !== 0) { ok = false; break check; }
266
+ } else if (byteIdx === fullBytes && remBits > 0) {
267
+ if ((bv & mask) !== 0) ok = false;
268
+ break check;
269
+ } else { break check; }
270
+ byteIdx++;
271
+ }
272
+ }
273
+ if (ok) { self.postMessage({ found: true, counter: counter }); return; }
274
+ counter++;
275
+ }
276
+ setTimeout(mine, 0);
277
+ }
278
+ mine();
279
+ });
234
280
  }
235
- return str;
236
- };
237
-
238
- Hashcash.sha1.Utf8Encode = function(string) {
239
- string = string.replace(/\r\n/g, '\n');
240
- var utftext = '';
241
- for (var n = 0; n < string.length; n++) {
242
- var c = string.charCodeAt(n);
243
- if (c < 128) {
244
- utftext += String.fromCharCode(c);
245
- } else if ((c > 127) && (c < 2048)) {
246
- utftext += String.fromCharCode((c >> 6) | 192);
247
- utftext += String.fromCharCode((c & 63) | 128);
248
- } else {
249
- utftext += String.fromCharCode((c >> 12) | 224);
250
- utftext += String.fromCharCode(((c >> 6) & 63) | 128);
251
- utftext += String.fromCharCode((c & 63) | 128);
281
+
282
+ // Clean up any previous worker (e.g. Turbo restoring a cached page)
283
+ Hashcash.cleanup()
284
+
285
+ var blob = new Blob(
286
+ ["(" + workerCode.toString() + ")()"],
287
+ {type: "application/javascript"}
288
+ )
289
+ var workerUrl = URL.createObjectURL(blob)
290
+ var worker = new Worker(workerUrl)
291
+
292
+ // Track the active worker so Hashcash.cleanup() can terminate it
293
+ Hashcash._worker = worker
294
+ Hashcash._workerUrl = workerUrl
295
+
296
+ worker.onmessage = function(e) {
297
+ if (e.data.found) {
298
+ Hashcash._worker = null
299
+ Hashcash._workerUrl = null
300
+ self.counter = e.data.counter
301
+ self.stopClock()
302
+ worker.terminate()
303
+ URL.revokeObjectURL(workerUrl)
304
+ callback(self)
252
305
  }
253
306
  }
254
- return utftext;
255
- };
307
+
308
+ // Build the prefix once and send it to the worker. The worker appends the
309
+ // counter on each iteration, avoiding repeated string building.
310
+ var prefix = [this.version, this.bits, this.date, this.resource, "sha256", this.rand].join(":")
311
+
312
+ worker.postMessage({
313
+ prefix: prefix,
314
+ bits: parseInt(this.bits, 10)
315
+ })
316
+ }
256
317
 
257
318
  Hashcash.setup()
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class AddressesController < ApplicationController
2
+ class AddressesController < ApplicationController # :nodoc:
3
3
  def index
4
4
  @addresses = Stamp.filter_by(params).group(:ip_address).order(count_all: :desc).limit(1000).count
5
5
  end
@@ -1,4 +1,5 @@
1
1
  module ActiveHashcash
2
- class ApplicationController < ActionController::Base
2
+ class ApplicationController < ActiveHashcash.base_controller_class.constantize # :nodoc:
3
+ layout "active_hashcash/application"
3
4
  end
4
5
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class AssetsController < ApplicationController
2
+ class AssetsController < ApplicationController # :nodoc:
3
3
  protect_from_forgery except: :show
4
4
 
5
5
  Mime::Type.register "image/x-icon", :ico
@@ -7,7 +7,7 @@ module ActiveHashcash
7
7
  def show
8
8
  if endpoints.include?(file_name = File.basename(request.path))
9
9
  file_path = ActiveHashcash::Engine.root.join / "app/views/active_hashcash/assets" / file_name
10
- if File.exists?("#{file_path}.erb")
10
+ if File.exist?("#{file_path}.erb")
11
11
  render(params[:id], mime_type: mime_type)
12
12
  else
13
13
  render(file: file_path)
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class StampsController < ApplicationController
2
+ class StampsController < ApplicationController # :nodoc:
3
3
  def index
4
4
  @stamps = Stamp.filter_by(params).order(created_at: :desc).limit(1000)
5
5
  end
@@ -1,4 +1,4 @@
1
1
  module ActiveHashcash
2
- module AddressesHelper
2
+ module AddressesHelper # :nodoc:
3
3
  end
4
4
  end
@@ -1,4 +1,4 @@
1
1
  module ActiveHashcash
2
- module ApplicationHelper
2
+ module ApplicationHelper # :nodoc:
3
3
  end
4
4
  end
@@ -1,4 +1,4 @@
1
1
  module ActiveHashcash
2
- module StampsHelper
2
+ module StampsHelper # :nodoc:
3
3
  end
4
4
  end
@@ -1,4 +1,4 @@
1
1
  module ActiveHashcash
2
- class ApplicationJob < ActiveJob::Base
2
+ class ApplicationJob < ActiveJob::Base # :nodoc:
3
3
  end
4
4
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class ApplicationMailer < ActionMailer::Base
2
+ class ApplicationMailer < ActionMailer::Base # :nodoc:
3
3
  default from: "from@example.com"
4
4
  layout "mailer"
5
5
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class ApplicationRecord < ActiveRecord::Base
2
+ class ApplicationRecord < ActiveRecord::Base # :nodoc:
3
3
  self.abstract_class = true
4
4
  end
5
5
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveHashcash
4
+ # This is the model to store hashcash stamps.
5
+ # Unless you need something really specific, you should not need interact directly with that class.
4
6
  class Stamp < ApplicationRecord
5
7
  validates_presence_of :version, :bits, :date, :resource, :rand, :counter
6
8
 
@@ -24,6 +26,8 @@ module ActiveHashcash
24
26
  scope
25
27
  end
26
28
 
29
+ # Verify and save the hashcash stamp.
30
+ # Saving in the database prevent from double spending the same stamp.
27
31
  def self.spend(string, resource, bits, date, options = {})
28
32
  return false unless stamp = parse(string)
29
33
  stamp.attributes = options
@@ -32,6 +36,9 @@ module ActiveHashcash
32
36
  false
33
37
  end
34
38
 
39
+ # Parse and instantiate a stamp from a string which respects the hashcash format:
40
+ #
41
+ # ver:bits:date:resource:[ext]:rand:counter
35
42
  def self.parse(string)
36
43
  args = string.to_s.split(":")
37
44
  return if args.size != 7
@@ -44,6 +51,7 @@ module ActiveHashcash
44
51
  bits: ActiveHashcash.bits,
45
52
  date: Date.today.strftime(ActiveHashcash.date_format),
46
53
  resource: resource,
54
+ ext: "sha256",
47
55
  rand: SecureRandom.alphanumeric(16),
48
56
  counter: 0,
49
57
  }.merge(attributes)).work
@@ -55,7 +63,11 @@ module ActiveHashcash
55
63
  end
56
64
 
57
65
  def authentic?
58
- Digest::SHA1.hexdigest(to_s).hex >> (160-bits) == 0
66
+ if ext == "sha256"
67
+ Digest::SHA256.hexdigest(to_s).hex >> (256 - bits) == 0
68
+ else
69
+ Digest::SHA1.hexdigest(to_s).hex >> (160 - bits) == 0
70
+ end
59
71
  end
60
72
 
61
73
  def verify(resource, bits, date)
@@ -0,0 +1,4 @@
1
+ ca:
2
+ active_hashcash:
3
+ waiting_label: "Esperant a validar el formulari..."
4
+ submit_filter: Filtra
@@ -1,3 +1,10 @@
1
+ # Successful hashcash stamp are stored in the database.
2
+ # This migration creates the table for the model ActiveHashcash::Stamp.
3
+ # Run the following commands to add it to your Rails application:
4
+ #
5
+ # rails active_hashcash:install:migrations
6
+ # rails db:migrate
7
+ #
1
8
  class CreateActiveHashcashStamps < ActiveRecord::Migration[5.2]
2
9
  def change
3
10
  create_table :active_hashcash_stamps do |t|
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class Engine < ::Rails::Engine
2
+ class Engine < ::Rails::Engine # :nodoc:
3
3
  config.assets.paths << File.expand_path("../..", __FILE__) if config.respond_to?(:assets)
4
4
 
5
5
  isolate_namespace ActiveHashcash