Skip to content

Commit bab8854

Browse files
committed
fix: a malicious user can replay a previous response to work around the challenge
1 parent 5f69b9e commit bab8854

File tree

11 files changed

+289
-33
lines changed

11 files changed

+289
-33
lines changed

core/const.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package core
22

3+
import "time"
4+
35
const (
4-
AppName = "cerberus"
5-
VarName = "cerberus-block"
6-
Version = "v0.2.1"
6+
AppName = "cerberus"
7+
VarName = "cerberus-block"
8+
Version = "v0.2.1"
9+
NonceTTL = 2 * time.Minute
710
)

core/state.go

+31-8
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import (
66
"unsafe"
77

88
"crypto/sha256"
9+
"encoding/binary"
910
"encoding/hex"
1011

1112
"github.com/elastic/go-freelru"
1213
"github.com/google/uuid"
14+
"github.com/sjtug/cerberus/internal/expiremap"
1315
"github.com/sjtug/cerberus/internal/ipblock"
1416
"github.com/zeebo/xxh3"
1517
"golang.org/x/crypto/ed25519"
@@ -26,14 +28,7 @@ func hashIPBlock(ip ipblock.IPBlock) uint32 {
2628
data := ip.ToUint64()
2729

2830
var buf [8]byte
29-
buf[0] = byte(data >> 56)
30-
buf[1] = byte(data >> 48)
31-
buf[2] = byte(data >> 40)
32-
buf[3] = byte(data >> 32)
33-
buf[4] = byte(data >> 24)
34-
buf[5] = byte(data >> 16)
35-
buf[6] = byte(data >> 8)
36-
buf[7] = byte(data)
31+
binary.BigEndian.PutUint64(buf[:], data)
3732

3833
hash := xxh3.Hash(buf[:])
3934
return uint32(hash) // #nosec G115 -- expected truncation
@@ -51,6 +46,7 @@ type InstanceState struct {
5146
pending freelru.Cache[ipblock.IPBlock, *atomic.Int32]
5247
blocklist freelru.Cache[ipblock.IPBlock, struct{}]
5348
approval freelru.Cache[uuid.UUID, *atomic.Int32]
49+
usedNonce *expiremap.ExpireMap[uint32, struct{}]
5450
stop chan struct{}
5551
}
5652

@@ -82,6 +78,24 @@ func initLRU[K comparable, V any](
8278
return cache, nil
8379
}
8480

81+
// initUsedNonce creates and initializes an ExpireMap for tracking used nonces
82+
func initUsedNonce(stop chan struct{}, purgeInterval time.Duration) *expiremap.ExpireMap[uint32, struct{}] {
83+
usedNonce := expiremap.NewExpireMap[uint32, struct{}](func(x uint32) uint32 {
84+
return x
85+
})
86+
go func() {
87+
for {
88+
select {
89+
case <-stop:
90+
return
91+
case <-time.After(purgeInterval):
92+
usedNonce.PurgeExpired()
93+
}
94+
}
95+
}()
96+
return usedNonce
97+
}
98+
8599
func NewInstanceState(pendingMaxMemUsage int64, blocklistMaxMemUsage int64, approvedMaxMemUsage int64, pendingTTL time.Duration, blocklistTTL time.Duration, approvalTTL time.Duration) (*InstanceState, int64, int64, int64, error) {
86100
uuid.EnableRandPool()
87101

@@ -123,6 +137,8 @@ func NewInstanceState(pendingMaxMemUsage int64, blocklistMaxMemUsage int64, appr
123137
return nil, 0, 0, 0, err
124138
}
125139

140+
usedNonce := initUsedNonce(stop, 41*time.Second)
141+
126142
pub, priv, err := ed25519.GenerateKey(nil)
127143
if err != nil {
128144
return nil, 0, 0, 0, err
@@ -137,6 +153,7 @@ func NewInstanceState(pendingMaxMemUsage int64, blocklistMaxMemUsage int64, appr
137153
pending: pending,
138154
blocklist: blocklist,
139155
approval: approval,
156+
usedNonce: usedNonce,
140157
stop: stop,
141158
}, int64(pendingElems), int64(blocklistElems), int64(approvalElems), nil
142159
}
@@ -217,6 +234,12 @@ func (s *InstanceState) DecApproval(id uuid.UUID) bool {
217234
return false
218235
}
219236

237+
// InsertUsedNonce inserts a nonce into the usedNonce map.
238+
// Returns true if the nonce was inserted, false if it was already present.
239+
func (s *InstanceState) InsertUsedNonce(nonce uint32) bool {
240+
return s.usedNonce.SetIfAbsent(nonce, struct{}{}, NonceTTL)
241+
}
242+
220243
func (s *InstanceState) Close() {
221244
close(s.stop)
222245
}

directives/common.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package directives
22

33
import (
44
"context"
5+
"crypto/ed25519"
56
"crypto/sha256"
67
"encoding/hex"
78
"errors"
@@ -16,7 +17,10 @@ import (
1617
"github.com/zeebo/blake3"
1718
)
1819

19-
const IV = "/L4y6KgWa8vHEujU3O6JyI8osQxwh1nE0Eoay4nD3vw/y36eSFT0s/GTGfrngN6+"
20+
const (
21+
IV1 = "/L4y6KgWa8vHEujU3O6JyI8osQxwh1nE0Eoay4nD3vw/y36eSFT0s/GTGfrngN6+"
22+
IV2 = "KHo5hHR3ZfisR7xeG1gJwO3LSc1cYyDUQ5+StoAjV8jLhp01NBNi4joHYTWXDqF0"
23+
)
2024

2125
func clearCookie(w http.ResponseWriter, cookieName string) {
2226
http.SetCookie(w, &http.Cookie{
@@ -90,12 +94,19 @@ func challengeFor(r *http.Request, c *core.Instance) (string, error) {
9094
r.Header.Get("User-Agent"),
9195
fp,
9296
c.Difficulty,
93-
IV,
97+
IV1,
9498
)
9599

96100
return blake3sum(payload)
97101
}
98102

103+
func calcSignature(challenge string, nonce uint32, ts int64, c *core.Instance) string {
104+
payload := fmt.Sprintf("Challenge=%s,Nonce=%d,TS=%d,IV=%s", challenge, nonce, ts, IV2)
105+
106+
signature := ed25519.Sign(c.GetPrivateKey(), []byte(payload))
107+
return hex.EncodeToString(signature)
108+
}
109+
99110
func respondFailure(w http.ResponseWriter, r *http.Request, c *core.Config, msg string, blocked bool, status int, baseURL string) {
100111
if blocked {
101112
if c.Drop {

directives/endpoint.go

+55-4
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,55 @@ func (e *Endpoint) answerHandle(w http.ResponseWriter, r *http.Request) error {
3333
respondFailure(w, r, &c.Config, "nonce is empty", false, http.StatusBadRequest, ".")
3434
return nil
3535
}
36+
nonce64, err := strconv.ParseUint(nonceStr, 10, 32)
37+
if err != nil {
38+
e.logger.Debug("nonce is not an integer", zap.Error(err))
39+
respondFailure(w, r, &c.Config, "nonce is not an integer", false, http.StatusBadRequest, ".")
40+
return nil
41+
}
42+
nonce := uint32(nonce64)
43+
if !c.InsertUsedNonce(nonce) {
44+
e.logger.Info("nonce already used")
45+
respondFailure(w, r, &c.Config, "nonce already used", false, http.StatusBadRequest, ".")
46+
return nil
47+
}
3648

37-
nonce, err := strconv.Atoi(nonceStr)
49+
tsStr := r.FormValue("ts")
50+
if tsStr == "" {
51+
e.logger.Info("ts is empty")
52+
respondFailure(w, r, &c.Config, "ts is empty", false, http.StatusBadRequest, ".")
53+
return nil
54+
}
55+
ts, err := strconv.ParseInt(tsStr, 10, 64)
3856
if err != nil {
39-
e.logger.Debug("nonce is not a integer", zap.Error(err))
40-
respondFailure(w, r, &c.Config, "nonce is not a integer", false, http.StatusBadRequest, ".")
57+
e.logger.Debug("ts is not a integer", zap.Error(err))
58+
respondFailure(w, r, &c.Config, "ts is not a integer", false, http.StatusBadRequest, ".")
59+
return nil
60+
}
61+
now := time.Now().Unix()
62+
if ts < now-int64(core.NonceTTL) || ts > now {
63+
e.logger.Info("invalid ts", zap.Int64("ts", ts), zap.Int64("now", now))
64+
respondFailure(w, r, &c.Config, "invalid ts", false, http.StatusBadRequest, ".")
65+
return nil
66+
}
67+
68+
signature := r.FormValue("signature")
69+
if signature == "" {
70+
e.logger.Info("signature is empty")
71+
respondFailure(w, r, &c.Config, "signature is empty", false, http.StatusBadRequest, ".")
72+
return nil
73+
}
74+
75+
solutionStr := r.FormValue("solution")
76+
if solutionStr == "" {
77+
e.logger.Info("solution is empty")
78+
respondFailure(w, r, &c.Config, "solution is empty", false, http.StatusBadRequest, ".")
79+
return nil
80+
}
81+
solution, err := strconv.Atoi(solutionStr)
82+
if err != nil {
83+
e.logger.Debug("solution is not a integer", zap.Error(err))
84+
respondFailure(w, r, &c.Config, "solution is not a integer", false, http.StatusBadRequest, ".")
4185
return nil
4286
}
4387

@@ -50,7 +94,14 @@ func (e *Endpoint) answerHandle(w http.ResponseWriter, r *http.Request) error {
5094
return err
5195
}
5296

53-
answer, err := sha256sum(fmt.Sprintf("%s%d", challenge, nonce))
97+
expectedSignature := calcSignature(challenge, nonce, ts, c)
98+
if signature != expectedSignature {
99+
e.logger.Debug("signature mismatch", zap.String("expected", expectedSignature), zap.String("actual", signature))
100+
respondFailure(w, r, &c.Config, "signature mismatch", false, http.StatusForbidden, ".")
101+
return nil
102+
}
103+
104+
answer, err := sha256sum(fmt.Sprintf("%s|%d|%d|%s%d", challenge, nonce, ts, signature, solution))
54105
if err != nil {
55106
e.logger.Error("failed to calculate answer", zap.Error(err))
56107
return err

directives/middleware.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"net"
88
"net/http"
9+
"time"
910

1011
"github.com/a-h/templ"
1112
"github.com/caddyserver/caddy/v2"
@@ -14,6 +15,7 @@ import (
1415
"github.com/google/uuid"
1516
"github.com/sjtug/cerberus/core"
1617
"github.com/sjtug/cerberus/internal/ipblock"
18+
"github.com/sjtug/cerberus/internal/randpool"
1719
"github.com/sjtug/cerberus/web"
1820
"go.uber.org/zap"
1921
)
@@ -66,9 +68,13 @@ func (m *Middleware) invokeAuth(w http.ResponseWriter, r *http.Request) error {
6668
return err
6769
}
6870

71+
nonce := randpool.ReadUint32()
72+
ts := time.Now().Unix()
73+
signature := calcSignature(challenge, nonce, ts, c)
74+
6975
ctx := templ.WithChildren(
7076
context.WithValue(context.WithValue(r.Context(), web.BaseURLCtxKey, m.BaseURL), web.VersionCtxKey, core.Version),
71-
web.Challenge(challenge, c.Difficulty),
77+
web.Challenge(challenge, c.Difficulty, nonce, ts, signature),
7278
)
7379
templ.Handler(
7480
web.Base(c.Title),

internal/expiremap/expiremap.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package expiremap
2+
3+
import (
4+
"runtime"
5+
"sync"
6+
"time"
7+
)
8+
9+
var (
10+
numShards = runtime.GOMAXPROCS(0) * 16
11+
)
12+
13+
type entry[V any] struct {
14+
value V
15+
expire time.Time
16+
}
17+
18+
type shard[K comparable, V any] struct {
19+
mu sync.Mutex
20+
store map[K]entry[V]
21+
}
22+
23+
type ExpireMap[K comparable, V any] struct {
24+
shards []*shard[K, V]
25+
hash func(K) uint32
26+
}
27+
28+
// fastModulo calculates x % n without using the modulo operator (~4x faster).
29+
// Reference: https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
30+
func fastModulo(x, n uint32) uint32 {
31+
return uint32((uint64(x) * uint64(n)) >> 32) //nolint:gosec
32+
}
33+
34+
func NewExpireMap[K comparable, V any](hash func(K) uint32) *ExpireMap[K, V] {
35+
shards := make([]*shard[K, V], numShards)
36+
for i := range shards {
37+
shards[i] = &shard[K, V]{store: make(map[K]entry[V])}
38+
}
39+
return &ExpireMap[K, V]{shards: shards, hash: hash}
40+
}
41+
42+
func (m *ExpireMap[K, V]) Get(key K) (*V, bool) {
43+
shard := m.shards[fastModulo(m.hash(key), uint32(len(m.shards)))] // #nosec G115 we don't have so many cores
44+
45+
shard.mu.Lock()
46+
defer shard.mu.Unlock()
47+
48+
value, ok := shard.store[key]
49+
if !ok {
50+
// Key not found
51+
return nil, false
52+
}
53+
54+
if value.expire.Before(time.Now()) {
55+
// Key expired, remove it
56+
delete(shard.store, key)
57+
return nil, false
58+
}
59+
60+
return &value.value, true
61+
}
62+
63+
// SetIfAbsent sets the value for the key if it is not already present.
64+
// Returns true if the value was set, false if it was already present.
65+
func (m *ExpireMap[K, V]) SetIfAbsent(key K, value V, ttl time.Duration) bool {
66+
shard := m.shards[fastModulo(m.hash(key), uint32(len(m.shards)))] // #nosec G115 we don't have so many cores
67+
68+
shard.mu.Lock()
69+
defer shard.mu.Unlock()
70+
71+
if _, ok := shard.store[key]; ok {
72+
return false
73+
}
74+
75+
shard.store[key] = entry[V]{value: value, expire: time.Now().Add(ttl)}
76+
return true
77+
}
78+
79+
func (m *ExpireMap[K, V]) PurgeExpired() {
80+
now := time.Now()
81+
82+
for _, shard := range m.shards {
83+
shard.mu.Lock()
84+
defer shard.mu.Unlock()
85+
86+
for key, entry := range shard.store {
87+
if entry.expire.Before(now) {
88+
delete(shard.store, key)
89+
}
90+
}
91+
}
92+
}

internal/randpool/randpool.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package randpool
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/binary"
6+
"io"
7+
"sync"
8+
)
9+
10+
const (
11+
poolSize = 16 * 16
12+
)
13+
14+
var (
15+
poolMu sync.Mutex
16+
pool [poolSize]byte
17+
poolPos = poolSize
18+
)
19+
20+
func ReadUint32() uint32 {
21+
poolMu.Lock()
22+
defer poolMu.Unlock()
23+
24+
if poolPos == poolSize {
25+
_, err := io.ReadFull(rand.Reader, pool[:])
26+
if err != nil {
27+
panic(err)
28+
}
29+
poolPos = 0
30+
}
31+
32+
poolPos += 4
33+
34+
return binary.BigEndian.Uint32(pool[poolPos-4 : poolPos])
35+
}

web/dist/main.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
(()=>{function f(l,e=5,t=null,s=null,a=navigator.hardwareConcurrency||1){return console.debug("fast algo"),new Promise((o,n)=>{let d=URL.createObjectURL(new Blob(["(",w(),")()"],{type:"application/javascript"})),m=[],u=()=>{m.forEach(i=>i.terminate()),t!=null&&(t.removeEventListener("abort",u),t.aborted&&(console.log("PoW aborted"),n(!1)))};t?.addEventListener("abort",u,{once:!0});for(let i=0;i<a;i++){let r=new Worker(d);r.onmessage=c=>{typeof c.data=="number"?s?.(c.data):(u(),o(c.data))},r.onerror=c=>{u(),n(c)},r.postMessage({data:l,difficulty:e,nonce:i,threads:a}),m.push(r)}URL.revokeObjectURL(d)})}function w(){return function(){let l=t=>{let s=new TextEncoder().encode(t);return crypto.subtle.digest("SHA-256",s.buffer)};function e(t){return Array.from(t).map(s=>s.toString(16).padStart(2,"0")).join("")}addEventListener("message",async t=>{let s=t.data.data,a=t.data.difficulty,o,n=t.data.nonce,d=t.data.threads,m=n;for(;;){let u=await l(s+n),i=new Uint8Array(u),r=!0;for(let p=0;p<a;p++){let y=Math.floor(p/2),h=p%2;if((i[y]>>(h===0?4:0)&15)!==0){r=!1;break}}if(r){o=e(i),console.log(o);break}let c=n;n+=d,n>c|1023&&(n>>10)%d===m&&postMessage(n)}postMessage({hash:o,data:s,difficulty:a,nonce:n})})}.toString()}var g=class{static state={baseURL:"",version:"1.0.0"};static elements={title:null,mascot:null,status:null,metrics:null,message:null,progressContainer:null,progressBar:null};static initialize(e){this.state={...this.state,...e},this.elements={title:document.getElementById("title"),mascot:document.getElementById("mascot"),status:document.getElementById("status"),metrics:document.getElementById("metrics"),message:document.getElementById("message"),progressContainer:document.getElementById("progress-container"),progressBar:document.getElementById("progress-bar")}}static setState(e,t={}){switch(e){case"checking":this.setChecking(t);break;case"success":this.setSuccess(t);break}}static setChecking(e){this.elements.title.textContent=e.title||"Making sure you're not a bot!",this.elements.mascot.src=`${this.state.baseURL}/static/img/mascot-puzzle.png?v=${this.state.version}`,this.elements.status.textContent=e.status||"Calculating...",this.elements.progressContainer.classList.remove("hidden"),this.setCheckingProgress(e.progress,e.metrics,e.message)}static setCheckingProgress(e,t,s){e!==void 0&&(this.elements.progressBar.style.width=`${e}%`),t!==void 0&&(t===""?this.elements.metrics.classList.add("hidden"):(this.elements.metrics.classList.remove("hidden"),this.elements.metrics.textContent=t)),s!==void 0&&(this.elements.message.textContent=s)}static setSuccess(e){this.elements.title.textContent="Success!",this.elements.mascot.src=`${this.state.baseURL}/static/img/mascot-pass.png?v=${this.state.version}`,this.elements.status.textContent=e.status||"Done!",this.elements.metrics.textContent=e.metrics||"Took ?, ? iterations",this.elements.message.textContent=e.message||"",this.elements.progressContainer.classList.add("hidden")}};function C(l,e,t){console.log("post url",`${t}/answer`);let s=document.createElement("form");s.method="POST",s.action=`${t}/answer`;let a=document.createElement("input");a.type="hidden",a.name="response",a.value=l;let o=document.createElement("input");o.type="hidden",o.name="nonce",o.value=e;let n=document.createElement("input");return n.type="hidden",n.name="redir",n.value=window.location.href,console.log("redir value",n.value),s.appendChild(a),s.appendChild(o),s.appendChild(n),document.body.appendChild(s),s}(async()=>{let l=JSON.parse(document.getElementById("challenge").textContent),e=JSON.parse(document.getElementById("difficulty").textContent),t=JSON.parse(document.getElementById("baseURL").textContent),s=JSON.parse(document.getElementById("version").textContent);g.initialize({baseURL:t,version:s}),g.setState("checking",{metrics:`Difficulty: ${e}, Speed: calculating...`,message:""});let a=Date.now(),o=0,n=Math.pow(16,-e),{hash:d,nonce:m}=await f(l,e,null,r=>{let c=Math.pow(1-n,r),p=(1-Math.pow(c,2))*100,h=Date.now()-a;if(console.log("delta",h,"lastUpdate",o,"delta - lastUpdate",h-o),h-o>100){let b=r/h;g.setCheckingProgress(p,`Difficulty: ${e}, Speed: ${b.toFixed(3)}kH/s`,c<.01?"This is taking longer than expected. Please do not refresh the page.":void 0),o=h}}),u=Date.now();console.log({hash:d,nonce:m}),g.setState("success",{status:"Verification Complete!",metrics:`Took ${u-a}ms, ${m} iterations`});let i=C(d,m,t);setTimeout(()=>{i.submit()},250)})();})();
1+
(()=>{function b(u,e=5,t=null,o=null,d=navigator.hardwareConcurrency||1){return console.debug("fast algo"),new Promise((m,n)=>{let r=URL.createObjectURL(new Blob(["(",k(),")()"],{type:"application/javascript"})),c=[],l=()=>{c.forEach(a=>a.terminate()),t!=null&&(t.removeEventListener("abort",l),t.aborted&&(console.log("PoW aborted"),n(!1)))};t?.addEventListener("abort",l,{once:!0});for(let a=0;a<d;a++){let i=new Worker(r);i.onmessage=s=>{typeof s.data=="number"?o?.(s.data):(l(),m(s.data))},i.onerror=s=>{l(),n(s)},i.postMessage({data:u,difficulty:e,nonce:a,threads:d}),c.push(i)}URL.revokeObjectURL(r)})}function k(){return function(){let u=t=>{let o=new TextEncoder().encode(t);return crypto.subtle.digest("SHA-256",o.buffer)};function e(t){return Array.from(t).map(o=>o.toString(16).padStart(2,"0")).join("")}addEventListener("message",async t=>{let o=t.data.data,d=t.data.difficulty,m,n=t.data.nonce,r=t.data.threads,c=n;for(;;){let l=await u(o+n),a=new Uint8Array(l),i=!0;for(let p=0;p<d;p++){let y=Math.floor(p/2),f=p%2;if((a[y]>>(f===0?4:0)&15)!==0){i=!1;break}}if(i){m=e(a),console.log(m);break}let s=n;n+=r,n>s|1023&&(n>>10)%r===c&&postMessage(n)}postMessage({hash:m,data:o,difficulty:d,nonce:n})})}.toString()}var h=class{static state={baseURL:"",version:"unknown"};static elements={title:null,mascot:null,status:null,metrics:null,message:null,progressContainer:null,progressBar:null};static initialize(e){this.state={...this.state,...e},this.elements={title:document.getElementById("title"),mascot:document.getElementById("mascot"),status:document.getElementById("status"),metrics:document.getElementById("metrics"),message:document.getElementById("message"),progressContainer:document.getElementById("progress-container"),progressBar:document.getElementById("progress-bar")}}static setState(e,t={}){switch(e){case"checking":this.setChecking(t);break;case"success":this.setSuccess(t);break}}static setChecking(e){this.elements.title.textContent=e.title||"Making sure you're not a bot!",this.elements.mascot.src=`${this.state.baseURL}/static/img/mascot-puzzle.png?v=${this.state.version}`,this.elements.status.textContent=e.status||"Calculating...",this.elements.progressContainer.classList.remove("hidden"),this.setCheckingProgress(e.progress,e.metrics,e.message)}static setCheckingProgress(e,t,o){e!==void 0&&(this.elements.progressBar.style.width=`${e}%`),t!==void 0&&(t===""?this.elements.metrics.classList.add("hidden"):(this.elements.metrics.classList.remove("hidden"),this.elements.metrics.textContent=t)),o!==void 0&&(this.elements.message.textContent=o)}static setSuccess(e){this.elements.title.textContent="Success!",this.elements.mascot.src=`${this.state.baseURL}/static/img/mascot-pass.png?v=${this.state.version}`,this.elements.status.textContent=e.status||"Done!",this.elements.metrics.textContent=e.metrics||"Took ?, ? iterations",this.elements.message.textContent=e.message||"",this.elements.progressContainer.classList.add("hidden")}};function x(u,e,t,o,d,m){let n=document.createElement("form");n.method="POST",n.action=`${t}/answer`;let r=document.createElement("input");r.type="hidden",r.name="response",r.value=u;let c=document.createElement("input");c.type="hidden",c.name="solution",c.value=e;let l=document.createElement("input");l.type="hidden",l.name="nonce",l.value=o;let a=document.createElement("input");a.type="hidden",a.name="ts",a.value=d;let i=document.createElement("input");i.type="hidden",i.name="signature",i.value=m;let s=document.createElement("input");return s.type="hidden",s.name="redir",s.value=window.location.href,n.appendChild(r),n.appendChild(c),n.appendChild(l),n.appendChild(a),n.appendChild(i),n.appendChild(s),document.body.appendChild(n),n}(async()=>{let u=JSON.parse(document.getElementById("challenge").textContent),e=JSON.parse(document.getElementById("difficulty").textContent),t=JSON.parse(document.getElementById("baseURL").textContent),o=JSON.parse(document.getElementById("version").textContent),d=JSON.parse(document.getElementById("nonce").textContent),m=JSON.parse(document.getElementById("ts").textContent),n=JSON.parse(document.getElementById("signature").textContent);h.initialize({baseURL:t,version:o}),h.setState("checking",{metrics:`Difficulty: ${e}, Speed: calculating...`,message:""});let r=Date.now(),c=0,l=Math.pow(16,-e),a=`${u}|${d}|${m}|${n}`,{hash:i,nonce:s}=await b(a,e,null,f=>{let C=Math.pow(1-l,f),w=(1-Math.pow(C,2))*100,g=Date.now()-r;if(console.log("delta",g,"lastUpdate",c,"delta - lastUpdate",g-c),g-c>100){let E=f/g;h.setCheckingProgress(w,`Difficulty: ${e}, Speed: ${E.toFixed(3)}kH/s`,C<.01?"This is taking longer than expected. Please do not refresh the page.":void 0),c=g}}),p=Date.now();console.log({hash:i,solution:s}),h.setState("success",{status:"Verification Complete!",metrics:`Took ${p-r}ms, ${s} iterations`});let y=x(i,s,t,d,m,n);setTimeout(()=>{y.submit()},250)})();})();

0 commit comments

Comments
 (0)