mirror of
https://github.com/sequentialread/pow-captcha.git
synced 2025-03-30 15:08:29 +00:00
commit before deleting non-wasm version
This commit is contained in:
parent
3fbc0e3fcd
commit
9a9008867c
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
wasm_build/node_modules
|
||||
wasm_build/scrypt-wasm
|
18
Dockerfile
Normal file
18
Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
FROM golang:1.15.2-alpine as build
|
||||
ARG GOARCH=
|
||||
ARG GO_BUILD_ARGS=
|
||||
|
||||
RUN mkdir /build
|
||||
WORKDIR /build
|
||||
RUN apk add --update --no-cache ca-certificates git \
|
||||
&& go get golang.org/x/crypto/scrypt
|
||||
COPY . .
|
||||
RUN go build -v $GO_BUILD_ARGS -o /build/sequentialread-pow-captcha .
|
||||
|
||||
FROM alpine
|
||||
WORKDIR /app
|
||||
COPY --from=build /build/sequentialread-pow-captcha /app/sequentialread-pow-captcha
|
||||
COPY --from=build /build/static /app/static
|
||||
RUN chmod +x /app/sequentialread-pow-captcha
|
||||
ENTRYPOINT ["/app/sequentialread-pow-captcha"]
|
41
build-docker.sh
Executable file
41
build-docker.sh
Executable file
@ -0,0 +1,41 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
VERSION="0.0.6"
|
||||
|
||||
rm -rf dockerbuild || true
|
||||
mkdir dockerbuild
|
||||
|
||||
cp Dockerfile dockerbuild/Dockerfile-amd64
|
||||
cp Dockerfile dockerbuild/Dockerfile-arm
|
||||
cp Dockerfile dockerbuild/Dockerfile-arm64
|
||||
|
||||
sed -E 's|FROM alpine|FROM amd64/alpine|' -i dockerbuild/Dockerfile-amd64
|
||||
sed -E 's|FROM alpine|FROM arm32v7/alpine|' -i dockerbuild/Dockerfile-arm
|
||||
sed -E 's|FROM alpine|FROM arm64v8/alpine|' -i dockerbuild/Dockerfile-arm64
|
||||
|
||||
sed -E 's/GOARCH=/GOARCH=amd64/' -i dockerbuild/Dockerfile-amd64
|
||||
sed -E 's/GOARCH=/GOARCH=arm/' -i dockerbuild/Dockerfile-arm
|
||||
sed -E 's/GOARCH=/GOARCH=arm64/' -i dockerbuild/Dockerfile-arm64
|
||||
|
||||
docker build -f dockerbuild/Dockerfile-amd64 -t sequentialread/pow-captcha:$VERSION-amd64 .
|
||||
docker build -f dockerbuild/Dockerfile-arm -t sequentialread/pow-captcha:$VERSION-arm .
|
||||
docker build -f dockerbuild/Dockerfile-arm64 -t sequentialread/pow-captcha:$VERSION-arm64 .
|
||||
|
||||
docker push sequentialread/pow-captcha:$VERSION-amd64
|
||||
docker push sequentialread/pow-captcha:$VERSION-arm
|
||||
docker push sequentialread/pow-captcha:$VERSION-arm64
|
||||
|
||||
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
|
||||
docker manifest create sequentialread/pow-captcha:$VERSION \
|
||||
sequentialread/pow-captcha:$VERSION-amd64 \
|
||||
sequentialread/pow-captcha:$VERSION-arm \
|
||||
sequentialread/pow-captcha:$VERSION-arm64
|
||||
|
||||
docker manifest annotate --arch amd64 sequentialread/pow-captcha:$VERSION sequentialread/pow-captcha:$VERSION-amd64
|
||||
docker manifest annotate --arch arm sequentialread/pow-captcha:$VERSION sequentialread/pow-captcha:$VERSION-arm
|
||||
docker manifest annotate --arch arm64 sequentialread/pow-captcha:$VERSION sequentialread/pow-captcha:$VERSION-arm64
|
||||
|
||||
docker manifest push sequentialread/pow-captcha:$VERSION
|
||||
|
||||
rm -rf dockerbuild || true
|
31
main.go
31
main.go
@ -7,9 +7,9 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
@ -28,8 +28,9 @@ type ScryptParameters struct {
|
||||
|
||||
type Challenge struct {
|
||||
ScryptParameters
|
||||
Preimage string `json:"i"`
|
||||
Difficulty string `json:"d"`
|
||||
Preimage string `json:"i"`
|
||||
Difficulty string `json:"d"`
|
||||
DifficultyLevel int `json:"dl"`
|
||||
}
|
||||
|
||||
var currentChallengesGeneration = 0
|
||||
@ -38,7 +39,7 @@ var challenges = map[string]int{}
|
||||
func main() {
|
||||
|
||||
scryptParameters := ScryptParameters{
|
||||
CPUAndMemoryCost: 2048,
|
||||
CPUAndMemoryCost: 4096,
|
||||
BlockSize: 8,
|
||||
Paralellization: 1,
|
||||
KeyLength: 16,
|
||||
@ -78,10 +79,24 @@ func main() {
|
||||
return
|
||||
}
|
||||
preimage := base64.StdEncoding.EncodeToString(preimageBytes)
|
||||
difficulty := fmt.Sprintf(fmt.Sprintf("%%0%dd", difficultyLevel), 0)
|
||||
difficultyBytes := make([]byte, int(math.Ceil(float64(difficultyLevel)/float64(8))))
|
||||
|
||||
for j := 0; j < len(difficultyBytes); j++ {
|
||||
difficultyByte := byte(0)
|
||||
for k := 0; k < 8; k++ {
|
||||
currentBitIndex := (len(difficultyBytes) * 8) - (j*8 + k)
|
||||
if currentBitIndex > difficultyLevel {
|
||||
difficultyByte = difficultyByte | 1<<k
|
||||
}
|
||||
}
|
||||
difficultyBytes[j] = difficultyByte
|
||||
}
|
||||
|
||||
difficulty := hex.EncodeToString(difficultyBytes)
|
||||
challenge := Challenge{
|
||||
Preimage: preimage,
|
||||
Difficulty: difficulty,
|
||||
Preimage: preimage,
|
||||
Difficulty: difficulty,
|
||||
DifficultyLevel: difficultyLevel,
|
||||
}
|
||||
challenge.CPUAndMemoryCost = scryptParameters.CPUAndMemoryCost
|
||||
challenge.BlockSize = scryptParameters.BlockSize
|
||||
@ -198,7 +213,7 @@ func main() {
|
||||
}
|
||||
|
||||
hashHex := hex.EncodeToString(hash)
|
||||
if !strings.HasSuffix(hashHex, challenge.Difficulty) {
|
||||
if hashHex[len(hashHex)-len(challenge.Difficulty):] > challenge.Difficulty {
|
||||
http.Error(
|
||||
responseWriter,
|
||||
fmt.Sprintf(
|
||||
|
157
static/captcha.css
Normal file
157
static/captcha.css
Normal file
@ -0,0 +1,157 @@
|
||||
.sqr-captcha {
|
||||
border: 1px solid #9359fa;
|
||||
border-radius: 1rem;
|
||||
font-size: 1.2rem;
|
||||
padding: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
border-bottom: 2px solid #452775;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.sqr-captcha-link {
|
||||
color: #333333;
|
||||
font-weight: black;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.sqr-captcha-link:hover,
|
||||
.sqr-captcha-link:active,
|
||||
.sqr-captcha-link:visited {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
|
||||
.sqr-captcha-row {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.sqr-captcha-icon-container {
|
||||
margin-left: 1.5rem;
|
||||
margin-top: 0.2rem;
|
||||
margin-bottom: -2rem;
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
|
||||
.sqr-captcha-best-hash {
|
||||
font-family: monospace;
|
||||
background: #585a29;
|
||||
color: #f6ff72;
|
||||
transition: background 0.5s ease-in-out, color 0.5s ease-in-out;
|
||||
padding: 0.2rem 0.8rem;
|
||||
margin-left: -0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bolder;
|
||||
display: block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.sqr-captcha-best-hash-done {
|
||||
background: #3b6262;
|
||||
color: #53f65d;
|
||||
}
|
||||
|
||||
.sqr-captcha-description {
|
||||
margin-top: 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.sqr-captcha-progress-bar-container {
|
||||
border-radius: 1rem;
|
||||
background: #444;
|
||||
height: 1rem;
|
||||
margin-top: 1rem;
|
||||
border: 1px solid #727630;
|
||||
box-sizing: content-box;
|
||||
|
||||
}
|
||||
|
||||
.sqr-captcha-progress-bar {
|
||||
background: #f6ff72;
|
||||
height: 1rem;
|
||||
width: 0;
|
||||
border-radius: 1rem;
|
||||
transition: width 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.sqr-captcha-icon {
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.sqr-captcha-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sqr-checkmark-icon-checkmark {
|
||||
fill:none;
|
||||
stroke: #31bd82;
|
||||
stroke-width: 9rem;
|
||||
stroke-dasharray: 60rem;
|
||||
stroke-dashoffset: 74rem;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
animation: 0.8s normal forwards ease-in-out sqr-draw-checkmark;
|
||||
animation-play-state: inherit;
|
||||
}
|
||||
|
||||
.sqr-checkmark-icon-border {
|
||||
fill:none;
|
||||
stroke: #ccc;
|
||||
stroke-width: 4rem;
|
||||
stroke-dasharray: 110rem;
|
||||
stroke-dashoffset: 110rem;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
animation: 0.8s normal forwards ease-in-out sqr-draw-checkmark-border;
|
||||
animation-play-state: inherit;
|
||||
}
|
||||
|
||||
.sqr-gears-icon-gear-large {
|
||||
fill: #9359fa;
|
||||
animation: 4s linear infinite sqr-spinning-gears-large;
|
||||
animation-play-state: running;
|
||||
}
|
||||
.sqr-gears-icon-gear-small {
|
||||
fill: #9359fa;
|
||||
animation: 4s linear infinite sqr-spinning-gears-small;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
|
||||
@keyframes sqr-draw-checkmark-border {
|
||||
0% {
|
||||
stroke-dashoffset: 110rem;
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 10rem;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sqr-draw-checkmark {
|
||||
0% {
|
||||
stroke-dashoffset: 74rem;
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 120rem;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sqr-spinning-gears-small {
|
||||
0% {
|
||||
transform: translate(16.1rem, 16.1rem) rotate(0deg) translate(-16.1rem,-16.1rem);
|
||||
}
|
||||
100% {
|
||||
transform: translate(16.1rem, 16.1rem) rotate(360deg) translate(-16.1rem,-16.1rem);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sqr-spinning-gears-large {
|
||||
0% {
|
||||
transform: translate(7.3rem, 7.3rem) rotate(360deg) translate(-7.3rem,-7.3rem);
|
||||
}
|
||||
100% {
|
||||
transform: translate(7.3rem, 7.3rem) rotate(0deg) translate(-7.3rem,-7.3rem);
|
||||
}
|
||||
}
|
@ -1,76 +1,298 @@
|
||||
(function(undefined, document){
|
||||
(function(window, document, undefined){
|
||||
|
||||
const challenges = Array.from(document.querySelectorAll("[data-sqr-captcha-challenge]"));
|
||||
const challengesMap = {};
|
||||
const url = null;
|
||||
let proofOfWorker = { postMessage: () => console.error("error: proofOfWorker was never loaded. ") };
|
||||
challenges.forEach(element => {
|
||||
const numberOfWebWorkersToCreate = 4;
|
||||
|
||||
if(!url) {
|
||||
if(!element.dataset.sqrCaptchaUrl) {
|
||||
console.error("error: element with data-sqr-captcha-challenge property is missing the data-sqr-captcha-url property");
|
||||
}
|
||||
url = element.dataset.sqrCaptchaUrl;
|
||||
if(!url.endsWith("/")) {
|
||||
url = `${url}/`
|
||||
}
|
||||
window.sqrCaptchaInit = () => {
|
||||
if(window.sqrCaptchaInitDone) {
|
||||
console.error("sqrCaptchaInit was called twice!");
|
||||
return
|
||||
}
|
||||
window.sqrCaptchaInitDone = true;
|
||||
|
||||
let form = null;
|
||||
let parent = element.parentElement;
|
||||
let sanity = 1000;
|
||||
while(parent && !form && sanity > 0) {
|
||||
sanity--;
|
||||
if(parent.tagName == "form") {
|
||||
form = parent
|
||||
const challenges = Array.from(document.querySelectorAll("[data-sqr-captcha-challenge]"));
|
||||
const challengesMap = {};
|
||||
let url = null;
|
||||
let proofOfWorker = { postMessage: () => console.error("error: proofOfWorker was never loaded. ") };
|
||||
|
||||
challenges.forEach(element => {
|
||||
|
||||
if(!url) {
|
||||
if(!element.dataset.sqrCaptchaUrl) {
|
||||
console.error("error: element with data-sqr-captcha-challenge property is missing the data-sqr-captcha-url property");
|
||||
}
|
||||
url = element.dataset.sqrCaptchaUrl;
|
||||
if(url.endsWith("/")) {
|
||||
url = url.substring(0, url.length-1)
|
||||
}
|
||||
}
|
||||
parent = parent.parentElement
|
||||
}
|
||||
if(!form) {
|
||||
console.error("error: element with data-sqr-captcha-challenge property was not inside a form element");
|
||||
|
||||
let form = null;
|
||||
let parent = element.parentElement;
|
||||
let sanity = 1000;
|
||||
while(parent && !form && sanity > 0) {
|
||||
sanity--;
|
||||
if(parent.tagName.toLowerCase() == "form") {
|
||||
form = parent
|
||||
}
|
||||
parent = parent.parentElement
|
||||
}
|
||||
if(!form) {
|
||||
console.error("error: element with data-sqr-captcha-challenge property was not inside a form element");
|
||||
//todo
|
||||
}
|
||||
|
||||
renderCaptcha(element, url);
|
||||
|
||||
const onFormWasTouched = () => {
|
||||
const challenge = element.dataset.sqrCaptchaChallenge;
|
||||
if(!challengesMap[challenge]) {
|
||||
challengesMap[challenge] = {
|
||||
element: element,
|
||||
attempts: 0,
|
||||
startTime: new Date().getTime(),
|
||||
};
|
||||
const progressBarContainer = element.querySelector(".sqr-captcha-progress-bar-container");
|
||||
progressBarContainer.style.display = "block";
|
||||
const mainElement = element.querySelector(".sqr-captcha");
|
||||
mainElement.style.display = "inline-block";
|
||||
const gears = element.querySelector(".sqr-gears-icon");
|
||||
gears.style.display = "block";
|
||||
|
||||
challengesMap[challenge].updateProgressInterval = setInterval(() => {
|
||||
// calculate the probability of finding a valid nonce after n tries
|
||||
if(challengesMap[challenge].probabilityOfFailurePerAttempt && !challengesMap[challenge].done) {
|
||||
const probabilityOfSuccessSoFar = 1-Math.pow(
|
||||
challengesMap[challenge].probabilityOfFailurePerAttempt,
|
||||
challengesMap[challenge].attempts
|
||||
);
|
||||
const element = challengesMap[challenge].element;
|
||||
const progressBar = element.querySelector(".sqr-captcha-progress-bar");
|
||||
const bestHashElement = element.querySelector(".sqr-captcha-best-hash");
|
||||
bestHashElement.textContent = getHashProgressText(challengesMap[challenge]);
|
||||
progressBar.style.width = `${probabilityOfSuccessSoFar*100}%`;
|
||||
}
|
||||
}, 500);
|
||||
|
||||
|
||||
proofOfWorker.postMessage({challenge: challenge});
|
||||
}
|
||||
};
|
||||
|
||||
const inputElements = Array.from(form.querySelectorAll("input"))
|
||||
.concat(Array.from(form.querySelectorAll("textarea")));
|
||||
|
||||
inputElements.forEach(inputElement => {
|
||||
inputElement.onchange = onFormWasTouched;
|
||||
inputElement.onkeydown = onFormWasTouched;
|
||||
});
|
||||
});
|
||||
|
||||
if (!window.Worker) {
|
||||
console.error("error: webworker is not support");
|
||||
//todo
|
||||
}
|
||||
|
||||
if(url) {
|
||||
|
||||
const onFormWasTouched = () => {
|
||||
if(!challengesMap[element.dataset.sqrCaptchaChallenge]) {
|
||||
challengesMap[element.dataset.sqrCaptchaChallenge] = element;
|
||||
myWorker.postMessage(element.dataset.sqrCaptchaChallenge);
|
||||
}
|
||||
};
|
||||
// https://stackoverflow.com/questions/21913673/execute-web-worker-from-different-origin/62914052#62914052
|
||||
const webWorkerUrlWhichIsProbablyCrossOrigin = `${url}/static/proofOfWorkerWASM.js`;
|
||||
|
||||
const inputElements = Array.from(form.querySelectorAll("input"))
|
||||
.concat(Array.from(form.querySelectorAll("textarea")));
|
||||
|
||||
inputElements.forEach(inputElement => {
|
||||
inputElement.onchange = onFormWasTouched;
|
||||
inputElement.onkeydown = onFormWasTouched;
|
||||
const webWorkerPointerDataURL = URL.createObjectURL(
|
||||
new Blob(
|
||||
[ `importScripts( "${ webWorkerUrlWhichIsProbablyCrossOrigin }" );` ],
|
||||
{ type: "text/javascript" }
|
||||
)
|
||||
);
|
||||
|
||||
let webWorkers;
|
||||
webWorkers = [...Array(numberOfWebWorkersToCreate)].map(x => {
|
||||
const webWorker = new Worker(webWorkerPointerDataURL);
|
||||
webWorker.onmessage = function(e) {
|
||||
const challengeState = challengesMap[e.data.challenge]
|
||||
if(!challengeState) {
|
||||
console.error(`error: webworker sent message with unknown challenge '${e.data.challenge}'`);
|
||||
}
|
||||
if(e.data.type == "progress") {
|
||||
challengeState.difficulty = e.data.difficulty;
|
||||
challengeState.probabilityOfFailurePerAttempt = e.data.probabilityOfFailurePerAttempt;
|
||||
if(!challengeState.smallestHash || challengeState.smallestHash > e.data.smallestHash) {
|
||||
challengeState.smallestHash = e.data.smallestHash;
|
||||
}
|
||||
challengeState.attempts += e.data.attempts;
|
||||
} else if(e.data.type == "success") {
|
||||
|
||||
challengeState.done = true;
|
||||
clearInterval(challengeState.updateProgressInterval);
|
||||
|
||||
const element = challengeState.element;
|
||||
const progressBar = element.querySelector(".sqr-captcha-progress-bar");
|
||||
const checkmark = element.querySelector(".sqr-checkmark-icon");
|
||||
const gears = element.querySelector(".sqr-gears-icon");
|
||||
const bestHashElement = element.querySelector(".sqr-captcha-best-hash");
|
||||
challengeState.smallestHash = e.data.smallestHash;
|
||||
bestHashElement.textContent = getHashProgressText(challengeState);
|
||||
bestHashElement.classList.add("sqr-captcha-best-hash-done");
|
||||
checkmark.style.display = "block";
|
||||
checkmark.style.animationPlayState = "running";
|
||||
gears.style.display = "none";
|
||||
progressBar.style.width = "100%";
|
||||
|
||||
// console.log("success: " + e.data.nonce)
|
||||
// console.log("hash: " + e.data.smallestHash)
|
||||
// console.log("difficulty: " + e.data.difficulty)
|
||||
webWorkers.forEach(x => x.postMessage({stop: "STOP"}));
|
||||
} else if(e.data.type == "error") {
|
||||
console.error(`error: webworker errored out: '${e.data.message}'`);
|
||||
} else {
|
||||
console.error(`error: webworker sent message with unknown type '${e.data.type}'`);
|
||||
}
|
||||
};
|
||||
return webWorker;
|
||||
});
|
||||
|
||||
URL.revokeObjectURL(webWorkerPointerDataURL);
|
||||
|
||||
proofOfWorker = {
|
||||
postMessage: arg => webWorkers.forEach((x, i) => {
|
||||
x.postMessage({ ...arg, workerId: i })
|
||||
})
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const challenges = Array.from(document.querySelectorAll("[data-sqr-captcha-challenge]"));
|
||||
if(challenges.length) {
|
||||
window.sqrCaptchaInit();
|
||||
}
|
||||
|
||||
function getHashProgressText(challengeState) {
|
||||
const durationSeconds = ((new Date().getTime()) - challengeState.startTime)/1000;
|
||||
let hashesPerSecond = '[...]';
|
||||
if (durationSeconds > 1) {
|
||||
hashesPerSecondFloat = challengeState.attempts / durationSeconds;
|
||||
hashesPerSecond = `[${leftPad(Math.round(hashesPerSecondFloat), 3)}h/s]`;
|
||||
}
|
||||
|
||||
return `${hashesPerSecond} ..${challengeState.smallestHash} → ..${challengeState.difficulty}`;
|
||||
}
|
||||
|
||||
function leftPad (str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? leftPad(" " + str, max) : str;
|
||||
}
|
||||
|
||||
function renderCaptcha(parent, baseUrl) {
|
||||
const svgXMLNS = "http://www.w3.org/2000/svg";
|
||||
const xmlnsXMLNS = 'http://www.w3.org/2000/xmlns/';
|
||||
const xmlSpaceXMLNS = 'http://www.w3.org/XML/1998/namespace';
|
||||
|
||||
|
||||
if(!document.querySelector(`link[href='${baseUrl}/static/captcha.css']`)) {
|
||||
createElement(document.head, "link", {
|
||||
"rel": "stylesheet",
|
||||
"charset": "utf8",
|
||||
"href": `${baseUrl}/static/captcha.css`,
|
||||
});
|
||||
}
|
||||
|
||||
const main = createElement(parent, "div", {"class": "sqr-captcha sqr-captcha-hidden"});
|
||||
const mainRow = createElement(main, "div", {"class": "sqr-captcha-row"});
|
||||
const mainColumn = createElement(mainRow, "div");
|
||||
const headerRow = createElement(mainColumn, "div");
|
||||
createElement(
|
||||
headerRow,
|
||||
"a",
|
||||
{
|
||||
"class": "sqr-captcha-link",
|
||||
"href": "https://git.sequentialread.com/forest/sequentialread-pow-captcha"
|
||||
},
|
||||
"PoW! Captcha"
|
||||
);
|
||||
createElement(headerRow, "div", {"class": "sqr-captcha-best-hash"}, "loading...");
|
||||
const description = createElement(mainColumn, "div", {"class": "sqr-captcha-description"});
|
||||
appendFragment(description, "Please wait for your computer to calculate a ");
|
||||
createElement(
|
||||
description,
|
||||
"a",
|
||||
{"href": "https://en.wikipedia.org/wiki/Proof_of_work"},
|
||||
"Proof of Work"
|
||||
);
|
||||
appendFragment(description, ". ");
|
||||
createElement(description, "br");
|
||||
appendFragment(description, "This a privacy-respecting anti-spam measure. ");
|
||||
const progressBarContainer = createElement(main, "div", {
|
||||
"class": "sqr-captcha-progress-bar-container sqr-captcha-hidden"
|
||||
});
|
||||
createElement(progressBarContainer, "div", {"class": "sqr-captcha-progress-bar"});
|
||||
const iconContainer = createElement(mainRow, "div", {"class": "sqr-captcha-icon-container"});
|
||||
|
||||
|
||||
const checkmarkIcon = createElementNS(iconContainer, svgXMLNS, "svg", {
|
||||
"xmlns": [xmlnsXMLNS, svgXMLNS],
|
||||
"xml:space": [xmlSpaceXMLNS, 'preserve'],
|
||||
"version": "1.1",
|
||||
"viewBox": "0 0 512 512",
|
||||
"class": "sqr-checkmark-icon sqr-captcha-icon sqr-captcha-hidden"
|
||||
});
|
||||
createElementNS(checkmarkIcon, svgXMLNS, "polyline", {
|
||||
"class": "sqr-checkmark-icon-checkmark",
|
||||
"points": "444,110 206,343 120,252"
|
||||
});
|
||||
createElementNS(checkmarkIcon, svgXMLNS, "polyline", {
|
||||
"class": "sqr-checkmark-icon-border",
|
||||
"points": "240,130 30,130 30,470 370,470 370,350"
|
||||
});
|
||||
});
|
||||
|
||||
if (!window.Worker) {
|
||||
console.error("error: webworker is not support");
|
||||
//todo
|
||||
const gearsIcon = createElementNS(iconContainer, svgXMLNS, "svg", {
|
||||
"xmlns": [xmlnsXMLNS, svgXMLNS],
|
||||
"xml:space": [xmlSpaceXMLNS, 'preserve'],
|
||||
"version": "1.1",
|
||||
"viewBox": "-30 0 250 218",
|
||||
"class": "sqr-gears-icon sqr-captcha-icon sqr-captcha-hidden"
|
||||
});
|
||||
createElementNS(gearsIcon, svgXMLNS, "path", {
|
||||
"class": "sqr-gears-icon-gear-large",
|
||||
"d": "M113.595,133.642l-5.932-13.169c5.655-4.151,10.512-9.315,14.307-15.209l13.507,5.118c2.583,0.979,5.469-0.322,6.447-2.904 l4.964-13.103c0.47-1.24,0.428-2.616-0.117-3.825c-0.545-1.209-1.547-2.152-2.788-2.622l-13.507-5.118 c1.064-6.93,0.848-14.014-0.637-20.871l13.169-5.932c1.209-0.545,2.152-1.547,2.622-2.788c0.47-1.24,0.428-2.616-0.117-3.825 l-5.755-12.775c-1.134-2.518-4.096-3.638-6.612-2.505l-13.169,5.932c-4.151-5.655-9.315-10.512-15.209-14.307l5.118-13.507 c0.978-2.582-0.322-5.469-2.904-6.447L93.88,0.82c-1.239-0.469-2.615-0.428-3.825,0.117c-1.209,0.545-2.152,1.547-2.622,2.788 l-5.117,13.506c-6.937-1.07-14.033-0.849-20.872,0.636L55.513,4.699c-0.545-1.209-1.547-2.152-2.788-2.622 c-1.239-0.469-2.616-0.428-3.825,0.117L36.124,7.949c-2.518,1.134-3.639,4.094-2.505,6.612l5.932,13.169 c-5.655,4.151-10.512,9.315-14.307,15.209l-13.507-5.118c-1.239-0.469-2.615-0.427-3.825,0.117 c-1.209,0.545-2.152,1.547-2.622,2.788L0.326,53.828c-0.978,2.582,0.322,5.469,2.904,6.447l13.507,5.118 c-1.064,6.929-0.848,14.015,0.637,20.871L4.204,92.196c-1.209,0.545-2.152,1.547-2.622,2.788c-0.47,1.24-0.428,2.616,0.117,3.825 l5.755,12.775c0.544,1.209,1.547,2.152,2.787,2.622c1.241,0.47,2.616,0.429,3.825-0.117l13.169-5.932 c4.151,5.656,9.314,10.512,15.209,14.307l-5.118,13.507c-0.978,2.582,0.322,5.469,2.904,6.447l13.103,4.964 c0.571,0.216,1.172,0.324,1.771,0.324c0.701,0,1.402-0.147,2.054-0.441c1.209-0.545,2.152-1.547,2.622-2.788l5.117-13.506 c6.937,1.069,14.034,0.849,20.872-0.636l5.931,13.168c0.545,1.209,1.547,2.152,2.788,2.622c1.24,0.47,2.617,0.429,3.825-0.117 l12.775-5.754C113.607,139.12,114.729,136.16,113.595,133.642z M105.309,86.113c-4.963,13.1-17.706,21.901-31.709,21.901 c-4.096,0-8.135-0.744-12.005-2.21c-8.468-3.208-15.18-9.522-18.899-17.779c-3.719-8.256-4-17.467-0.792-25.935 c4.963-13.1,17.706-21.901,31.709-21.901c4.096,0,8.135,0.744,12.005,2.21c8.468,3.208,15.18,9.522,18.899,17.778 C108.237,68.434,108.518,77.645,105.309,86.113z"
|
||||
});
|
||||
createElementNS(gearsIcon, svgXMLNS, "path", {
|
||||
"class": "sqr-gears-icon-gear-small",
|
||||
"d": "M216.478,154.389c-0.896-0.977-2.145-1.558-3.469-1.615l-9.418-0.404 c-0.867-4.445-2.433-8.736-4.633-12.697l6.945-6.374c2.035-1.867,2.17-5.03,0.303-7.064l-6.896-7.514 c-0.896-0.977-2.145-1.558-3.47-1.615c-1.322-0.049-2.618,0.416-3.595,1.312l-6.944,6.374c-3.759-2.531-7.9-4.458-12.254-5.702 l0.404-9.418c0.118-2.759-2.023-5.091-4.782-5.209l-10.189-0.437c-2.745-0.104-5.091,2.023-5.209,4.781l-0.404,9.418 c-4.444,0.867-8.735,2.433-12.697,4.632l-6.374-6.945c-0.896-0.977-2.145-1.558-3.469-1.615c-1.324-0.054-2.618,0.416-3.595,1.312 l-7.514,6.896c-2.035,1.867-2.17,5.03-0.303,7.064l6.374,6.945c-2.531,3.759-4.458,7.899-5.702,12.254l-9.417-0.404 c-2.747-0.111-5.092,2.022-5.21,4.781l-0.437,10.189c-0.057,1.325,0.415,2.618,1.312,3.595c0.896,0.977,2.145,1.558,3.47,1.615 l9.417,0.403c0.867,4.445,2.433,8.736,4.632,12.698l-6.944,6.374c-0.977,0.896-1.558,2.145-1.615,3.469 c-0.057,1.325,0.415,2.618,1.312,3.595l6.896,7.514c0.896,0.977,2.145,1.558,3.47,1.615c1.319,0.053,2.618-0.416,3.595-1.312 l6.944-6.374c3.759,2.531,7.9,4.458,12.254,5.702l-0.404,9.418c-0.118,2.759,2.022,5.091,4.781,5.209l10.189,0.437 c0.072,0.003,0.143,0.004,0.214,0.004c1.25,0,2.457-0.468,3.381-1.316c0.977-0.896,1.558-2.145,1.615-3.469l0.404-9.418 c4.444-0.867,8.735-2.433,12.697-4.632l6.374,6.945c0.896,0.977,2.145,1.558,3.469,1.615c1.33,0.058,2.619-0.416,3.595-1.312 l7.514-6.896c2.035-1.867,2.17-5.03,0.303-7.064l-6.374-6.945c2.531-3.759,4.458-7.899,5.702-12.254l9.417,0.404 c2.756,0.106,5.091-2.022,5.21-4.781l0.437-10.189C217.847,156.659,217.375,155.366,216.478,154.389z M160.157,183.953 c-12.844-0.55-22.846-11.448-22.295-24.292c0.536-12.514,10.759-22.317,23.273-22.317c0.338,0,0.678,0.007,1.019,0.022 c12.844,0.551,22.846,11.448,22.295,24.292C183.898,174.511,173.106,184.497,160.157,183.953z"
|
||||
});
|
||||
}
|
||||
|
||||
if(url) {
|
||||
proofOfWorker = new Worker(`${url}static/proofOfWorker.js`);
|
||||
|
||||
proofOfWorker.onmessage = function(e) {
|
||||
const challengeElement = challengesMap[e.data.challenge]
|
||||
if(!challengeElement) {
|
||||
console.error(`error: webworker sent message with unknown challenge '${e.data.challenge}'`);
|
||||
}
|
||||
if(e.data.type == "progress") {
|
||||
console.log("progress: " + e.data.value)
|
||||
} else if(e.data.type == "success") {
|
||||
console.log("success: " + e.data.nonce)
|
||||
} else if(e.data.type == "error") {
|
||||
console.error(`error: webworker errored out: '${e.data.message}'`);
|
||||
} else {
|
||||
console.error(`error: webworker sent message with unknown type '${e.data.type}'`);
|
||||
}
|
||||
};
|
||||
function createElementNS(parent, ns, tag, attr) {
|
||||
const element = document.createElementNS(ns, tag);
|
||||
if(attr) {
|
||||
Object.entries(attr).forEach(kv => {
|
||||
const value = kv[1];
|
||||
if((typeof value) == "string") {
|
||||
element.setAttributeNS(null, kv[0], kv[1])
|
||||
} else {
|
||||
element.setAttributeNS(value[0], kv[0], value[1])
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
parent.appendChild(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
function createElement(parent, tag, attr, textContent) {
|
||||
const element = document.createElement(tag);
|
||||
if(attr) {
|
||||
Object.entries(attr).forEach(kv => element.setAttribute(kv[0], kv[1]));
|
||||
}
|
||||
if(textContent) {
|
||||
element.textContent = textContent;
|
||||
}
|
||||
parent.appendChild(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
})(document);
|
||||
function appendFragment(parent, textContent) {
|
||||
const fragment = document.createDocumentFragment()
|
||||
fragment.textContent = textContent
|
||||
parent.appendChild(fragment)
|
||||
}
|
||||
|
||||
})(window, document);
|
148
static/proofOfWorkerSJCL.js
Normal file
148
static/proofOfWorkerSJCL.js
Normal file
@ -0,0 +1,148 @@
|
||||
// output of:
|
||||
// git clone https://github.com/bitwiseshiftleft/sjcl
|
||||
// cd sjcl
|
||||
// ./configure --without-all --with-scrypt --with-codecBase64 --with-codecHex
|
||||
// make
|
||||
// cat sjcl.js
|
||||
|
||||
"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(b){this.toString=function(){return"CORRUPT: "+this.message};this.message=b},invalid:function(b){this.toString=function(){return"INVALID: "+this.message};this.message=b},bug:function(b){this.toString=function(){return"BUG: "+this.message};this.message=b},notReady:function(b){this.toString=function(){return"NOT READY: "+this.message};this.message=b}}};
|
||||
sjcl.bitArray={bitSlice:function(b,d,c){b=sjcl.bitArray.m(b.slice(d/32),32-(d&31)).slice(1);return void 0===c?b:sjcl.bitArray.clamp(b,c-d)},extract:function(b,d,c){var a=Math.floor(-d-c&31);return((d+c-1^d)&-32?b[d/32|0]<<32-a^b[d/32+1|0]>>>a:b[d/32|0]>>>a)&(1<<c)-1},concat:function(b,d){if(0===b.length||0===d.length)return b.concat(d);var c=b[b.length-1],a=sjcl.bitArray.getPartial(c);return 32===a?b.concat(d):sjcl.bitArray.m(d,a,c|0,b.slice(0,b.length-1))},bitLength:function(b){var d=b.length;return 0===
|
||||
d?0:32*(d-1)+sjcl.bitArray.getPartial(b[d-1])},clamp:function(b,d){if(32*b.length<d)return b;b=b.slice(0,Math.ceil(d/32));var c=b.length;d=d&31;0<c&&d&&(b[c-1]=sjcl.bitArray.partial(d,b[c-1]&2147483648>>d-1,1));return b},partial:function(b,d,c){return 32===b?d:(c?d|0:d<<32-b)+0x10000000000*b},getPartial:function(b){return Math.round(b/0x10000000000)||32},equal:function(b,d){if(sjcl.bitArray.bitLength(b)!==sjcl.bitArray.bitLength(d))return!1;var c=0,a;for(a=0;a<b.length;a++)c|=b[a]^d[a];return 0===
|
||||
c},m:function(b,d,c,a){var e;e=0;for(void 0===a&&(a=[]);32<=d;d-=32)a.push(c),c=0;if(0===d)return a.concat(b);for(e=0;e<b.length;e++)a.push(c|b[e]>>>d),c=b[e]<<32-d;e=b.length?b[b.length-1]:0;b=sjcl.bitArray.getPartial(e);a.push(sjcl.bitArray.partial(d+b&31,32<d+b?c:a.pop(),1));return a},s:function(b,d){return[b[0]^d[0],b[1]^d[1],b[2]^d[2],b[3]^d[3]]},byteswapM:function(b){var d,c;for(d=0;d<b.length;++d)c=b[d],b[d]=c>>>24|c>>>8&0xff00|(c&0xff00)<<8|c<<24;return b}};
|
||||
sjcl.codec.utf8String={fromBits:function(b){var d="",c=sjcl.bitArray.bitLength(b),a,e;for(a=0;a<c/8;a++)0===(a&3)&&(e=b[a/4]),d+=String.fromCharCode(e>>>8>>>8>>>8),e<<=8;return decodeURIComponent(escape(d))},toBits:function(b){b=unescape(encodeURIComponent(b));var d=[],c,a=0;for(c=0;c<b.length;c++)a=a<<8|b.charCodeAt(c),3===(c&3)&&(d.push(a),a=0);c&3&&d.push(sjcl.bitArray.partial(8*(c&3),a));return d}};
|
||||
sjcl.codec.hex={fromBits:function(b){var d="",c;for(c=0;c<b.length;c++)d+=((b[c]|0)+0xf00000000000).toString(16).substr(4);return d.substr(0,sjcl.bitArray.bitLength(b)/4)},toBits:function(b){var d,c=[],a;b=b.replace(/\s|0x/g,"");a=b.length;b=b+"00000000";for(d=0;d<b.length;d+=8)c.push(parseInt(b.substr(d,8),16)^0);return sjcl.bitArray.clamp(c,4*a)}};
|
||||
sjcl.codec.base64={i:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(b,d,c){var a="",e=0,f=sjcl.codec.base64.i,k=0,g=sjcl.bitArray.bitLength(b);c&&(f=f.substr(0,62)+"-_");for(c=0;6*a.length<g;)a+=f.charAt((k^b[c]>>>e)>>>26),6>e?(k=b[c]<<6-e,e+=26,c++):(k<<=6,e-=6);for(;a.length&3&&!d;)a+="=";return a},toBits:function(b,d){b=b.replace(/\s|=/g,"");var c=[],a,e=0,f=sjcl.codec.base64.i,k=0,g;d&&(f=f.substr(0,62)+"-_");for(a=0;a<b.length;a++){g=f.indexOf(b.charAt(a));
|
||||
if(0>g)throw new sjcl.exception.invalid("this isn't base64!");26<e?(e-=26,c.push(k^g>>>e),k=g<<32-e):(e+=6,k^=g<<32-e)}e&56&&c.push(sjcl.bitArray.partial(e&56,k,1));return c}};sjcl.codec.base64url={fromBits:function(b){return sjcl.codec.base64.fromBits(b,1,1)},toBits:function(b){return sjcl.codec.base64.toBits(b,1)}};
|
||||
sjcl.codec.bytes={fromBits:function(b){var d=[],c=sjcl.bitArray.bitLength(b),a,e;for(a=0;a<c/8;a++)0===(a&3)&&(e=b[a/4]),d.push(e>>>24),e<<=8;return d},toBits:function(b){var d=[],c,a=0;for(c=0;c<b.length;c++)a=a<<8|b[c],3===(c&3)&&(d.push(a),a=0);c&3&&d.push(sjcl.bitArray.partial(8*(c&3),a));return d}};sjcl.hash.sha256=function(b){this.g[0]||r(this);b?(this.f=b.f.slice(0),this.c=b.c.slice(0),this.a=b.a):this.reset()};sjcl.hash.sha256.hash=function(b){return(new sjcl.hash.sha256).update(b).finalize()};
|
||||
sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.f=this.l.slice(0);this.c=[];this.a=0;return this},update:function(b){"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));var d,c=this.c=sjcl.bitArray.concat(this.c,b);d=this.a;b=this.a=d+sjcl.bitArray.bitLength(b);if(0x1fffffffffffff<b)throw new sjcl.exception.invalid("Cannot hash more than 2^53 - 1 bits");if("undefined"!==typeof Uint32Array){var a=new Uint32Array(c),e=0;for(d=512+d-(512+d&0x1ff);d<=b;d+=512)u(this,a.subarray(16*e,
|
||||
16*(e+1))),e+=1;c.splice(0,16*e)}else for(d=512+d-(512+d&0x1ff);d<=b;d+=512)u(this,c.splice(0,16));return this},finalize:function(){var b,d=this.c,c=this.f,d=sjcl.bitArray.concat(d,[sjcl.bitArray.partial(1,1)]);for(b=d.length+2;b&15;b++)d.push(0);d.push(Math.floor(this.a/0x100000000));for(d.push(this.a|0);d.length;)u(this,d.splice(0,16));this.reset();return c},l:[],g:[]};
|
||||
function u(b,d){var c,a,e,f=b.f,k=b.g,g=f[0],h=f[1],l=f[2],n=f[3],m=f[4],q=f[5],p=f[6],t=f[7];for(c=0;64>c;c++)16>c?a=d[c]:(a=d[c+1&15],e=d[c+14&15],a=d[c&15]=(a>>>7^a>>>18^a>>>3^a<<25^a<<14)+(e>>>17^e>>>19^e>>>10^e<<15^e<<13)+d[c&15]+d[c+9&15]|0),a=a+t+(m>>>6^m>>>11^m>>>25^m<<26^m<<21^m<<7)+(p^m&(q^p))+k[c],t=p,p=q,q=m,m=n+a|0,n=l,l=h,h=g,g=a+(h&l^n&(h^l))+(h>>>2^h>>>13^h>>>22^h<<30^h<<19^h<<10)|0;f[0]=f[0]+g|0;f[1]=f[1]+h|0;f[2]=f[2]+l|0;f[3]=f[3]+n|0;f[4]=f[4]+m|0;f[5]=f[5]+q|0;f[6]=f[6]+p|0;f[7]=
|
||||
f[7]+t|0}function r(b){function d(a){return 0x100000000*(a-Math.floor(a))|0}for(var c=0,a=2,e,f;64>c;a++){f=!0;for(e=2;e*e<=a;e++)if(0===a%e){f=!1;break}f&&(8>c&&(b.l[c]=d(Math.pow(a,.5))),b.g[c]=d(Math.pow(a,1/3)),c++)}}sjcl.misc.hmac=function(b,d){this.j=d=d||sjcl.hash.sha256;var c=[[],[]],a,e=d.prototype.blockSize/32;this.b=[new d,new d];b.length>e&&(b=d.hash(b));for(a=0;a<e;a++)c[0][a]=b[a]^909522486,c[1][a]=b[a]^1549556828;this.b[0].update(c[0]);this.b[1].update(c[1]);this.h=new d(this.b[0])};
|
||||
sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(b){if(this.o)throw new sjcl.exception.invalid("encrypt on already updated hmac called!");this.update(b);return this.digest(b)};sjcl.misc.hmac.prototype.reset=function(){this.h=new this.j(this.b[0]);this.o=!1};sjcl.misc.hmac.prototype.update=function(b){this.o=!0;this.h.update(b)};sjcl.misc.hmac.prototype.digest=function(){var b=this.h.finalize(),b=(new this.j(this.b[1])).update(b).finalize();this.reset();return b};
|
||||
sjcl.misc.pbkdf2=function(b,d,c,a,e){c=c||1E4;if(0>a||0>c)throw new sjcl.exception.invalid("invalid params to pbkdf2");"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));"string"===typeof d&&(d=sjcl.codec.utf8String.toBits(d));e=e||sjcl.misc.hmac;b=new e(b);var f,k,g,h,l=[],n=sjcl.bitArray;for(h=1;32*l.length<(a||1);h++){e=f=b.encrypt(n.concat(d,[h]));for(k=1;k<c;k++)for(f=b.encrypt(f),g=0;g<f.length;g++)e[g]^=f[g];l=l.concat(e)}a&&(l=n.clamp(l,a));return l};
|
||||
sjcl.misc.scrypt=function(b,d,c,a,e,f,k){var g=Math.pow(2,32)-1,h=sjcl.misc.scrypt;c=c||16384;a=a||8;e=e||1;if(a*e>=Math.pow(2,30))throw sjcl.exception.invalid("The parameters r, p must satisfy r * p < 2^30");if(2>c||c&0!=c-1)throw sjcl.exception.invalid("The parameter N must be a power of 2.");if(c>g/128/a)throw sjcl.exception.invalid("N too big.");if(a>g/128/e)throw sjcl.exception.invalid("r too big.");d=sjcl.misc.pbkdf2(b,d,1,128*e*a*8,k);a=d.length/e;h.reverse(d);for(g=0;g<e;g++){var l=d.slice(g*
|
||||
a,(g+1)*a);h.blockcopy(h.ROMix(l,c),0,d,g*a)}h.reverse(d);return sjcl.misc.pbkdf2(b,d,1,f,k)};
|
||||
sjcl.misc.scrypt.salsa20Core=function(b,d){function c(a,b){return a<<b|a>>>32-b}for(var a=b.slice(0),e=d;0<e;e-=2)a[4]^=c(a[0]+a[12],7),a[8]^=c(a[4]+a[0],9),a[12]^=c(a[8]+a[4],13),a[0]^=c(a[12]+a[8],18),a[9]^=c(a[5]+a[1],7),a[13]^=c(a[9]+a[5],9),a[1]^=c(a[13]+a[9],13),a[5]^=c(a[1]+a[13],18),a[14]^=c(a[10]+a[6],7),a[2]^=c(a[14]+a[10],9),a[6]^=c(a[2]+a[14],13),a[10]^=c(a[6]+a[2],18),a[3]^=c(a[15]+a[11],7),a[7]^=c(a[3]+a[15],9),a[11]^=c(a[7]+a[3],13),a[15]^=c(a[11]+a[7],18),a[1]^=c(a[0]+a[3],7),a[2]^=
|
||||
c(a[1]+a[0],9),a[3]^=c(a[2]+a[1],13),a[0]^=c(a[3]+a[2],18),a[6]^=c(a[5]+a[4],7),a[7]^=c(a[6]+a[5],9),a[4]^=c(a[7]+a[6],13),a[5]^=c(a[4]+a[7],18),a[11]^=c(a[10]+a[9],7),a[8]^=c(a[11]+a[10],9),a[9]^=c(a[8]+a[11],13),a[10]^=c(a[9]+a[8],18),a[12]^=c(a[15]+a[14],7),a[13]^=c(a[12]+a[15],9),a[14]^=c(a[13]+a[12],13),a[15]^=c(a[14]+a[13],18);for(e=0;16>e;e++)b[e]=a[e]+b[e]};
|
||||
sjcl.misc.scrypt.blockMix=function(b){for(var d=b.slice(-16),c=[],a=b.length/16,e=sjcl.misc.scrypt,f=0;f<a;f++)e.blockxor(b,16*f,d,0,16),e.salsa20Core(d,8),0==(f&1)?e.blockcopy(d,0,c,8*f):e.blockcopy(d,0,c,8*(f^1+a));return c};sjcl.misc.scrypt.ROMix=function(b,d){for(var c=b.slice(0),a=[],e=sjcl.misc.scrypt,f=0;f<d;f++)a.push(c.slice(0)),c=e.blockMix(c);for(f=0;f<d;f++)e.blockxor(a[c[c.length-16]&d-1],0,c,0),c=e.blockMix(c);return c};
|
||||
sjcl.misc.scrypt.reverse=function(b){for(var d in b){var c=b[d]&255,c=c<<8|b[d]>>>8&255,c=c<<8|b[d]>>>16&255,c=c<<8|b[d]>>>24&255;b[d]=c}};sjcl.misc.scrypt.blockcopy=function(b,d,c,a,e){var f;e=e||b.length-d;for(f=0;f<e;f++)c[a+f]=b[d+f]|0};sjcl.misc.scrypt.blockxor=function(b,d,c,a,e){var f;e=e||b.length-d;for(f=0;f<e;f++)c[a+f]=c[a+f]^b[d+f]|0};"undefined"!==typeof module&&module.exports&&(module.exports=sjcl);"function"===typeof define&&define([],function(){return sjcl});
|
||||
|
||||
let working = false;
|
||||
const batchSize = 2;
|
||||
|
||||
onmessage = function(e) {
|
||||
if(e.data.stop) {
|
||||
working = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const challengeBase64 = e.data.challenge;
|
||||
const workerId = e.data.workerId;
|
||||
if(!challengeBase64) {
|
||||
postMessage({
|
||||
type: "error",
|
||||
challenge: challengeBase64,
|
||||
message: `challenge was not provided`
|
||||
});
|
||||
}
|
||||
working = true;
|
||||
let challengeJSON = null;
|
||||
let challenge = null;
|
||||
try {
|
||||
challengeJSON = atob(challengeBase64);
|
||||
} catch (err) {
|
||||
postMessage({
|
||||
type: "error",
|
||||
challenge: challengeBase64,
|
||||
message: `couldn't decode challenge '${challengeBase64}' as base64: ${err}`
|
||||
});
|
||||
}
|
||||
try {
|
||||
challenge = JSON.parse(challengeJSON);
|
||||
} catch (err) {
|
||||
postMessage({
|
||||
type: "error",
|
||||
challenge: challengeBase64,
|
||||
message: `couldn't parse challenge '${challengeJSON}' as json: ${err}`
|
||||
});
|
||||
}
|
||||
|
||||
challenge = {
|
||||
cpuAndMemoryCost: challenge.N,
|
||||
blockSize: challenge.r,
|
||||
paralellization: challenge.p,
|
||||
keyLength: challenge.klen,
|
||||
preimage: challenge.i,
|
||||
difficulty: challenge.d,
|
||||
difficultyLevel: challenge.dl
|
||||
}
|
||||
|
||||
const preimageBits = sjcl.codec.base64.toBits(challenge.preimage);
|
||||
const probabilityOfFailurePerAttempt = 1-(1/Math.pow(2, challenge.difficultyLevel));
|
||||
|
||||
var i = workerId * Math.pow(2, challenge.difficulty.length) * 100;
|
||||
let smallestHash = challenge.difficulty.split("").map(x => "f").join("");
|
||||
|
||||
postMessage({
|
||||
type: "progress",
|
||||
challenge: challengeBase64,
|
||||
attempts: 0,
|
||||
smallestHash: smallestHash,
|
||||
difficulty: challenge.difficulty,
|
||||
probabilityOfFailurePerAttempt: probabilityOfFailurePerAttempt
|
||||
});
|
||||
|
||||
const doWork = () => {
|
||||
|
||||
var j = 0;
|
||||
while(j < batchSize) {
|
||||
j++;
|
||||
i++;
|
||||
|
||||
const nonceBits = sjcl.codec.hex.toBits(i.toString(16))
|
||||
|
||||
const hashBits = sjcl.misc.scrypt(
|
||||
nonceBits,
|
||||
preimageBits,
|
||||
challenge.cpuAndMemoryCost,
|
||||
challenge.blockSize,
|
||||
challenge.paralellization,
|
||||
challenge.keyLength*8
|
||||
);
|
||||
const hashHex = sjcl.codec.hex.fromBits(hashBits);
|
||||
// if(j == 10) {
|
||||
// console.log(workerId, hashHex);
|
||||
// }
|
||||
|
||||
const endOfHash = hashHex.substring(hashHex.length-challenge.difficulty.length);
|
||||
if(endOfHash < smallestHash) {
|
||||
smallestHash = endOfHash
|
||||
}
|
||||
if(endOfHash <= challenge.difficulty) {
|
||||
postMessage({
|
||||
type: "success",
|
||||
challenge: challengeBase64,
|
||||
nonce: i.toString(16),
|
||||
smallestHash: endOfHash,
|
||||
difficulty: challenge.difficulty
|
||||
});
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if(working) {
|
||||
this.setTimeout(doWork, 1);
|
||||
}
|
||||
|
||||
postMessage({
|
||||
type: "progress",
|
||||
challenge: challengeBase64,
|
||||
attempts: batchSize,
|
||||
smallestHash: smallestHash,
|
||||
difficulty: challenge.difficulty,
|
||||
probabilityOfFailurePerAttempt: probabilityOfFailurePerAttempt
|
||||
});
|
||||
};
|
||||
|
||||
doWork();
|
||||
}
|
425
static/proofOfWorkerWASM.js
Normal file
425
static/proofOfWorkerWASM.js
Normal file
File diff suppressed because one or more lines are too long
143
static/proofOfWorkerWASMStub.js
Normal file
143
static/proofOfWorkerWASMStub.js
Normal file
@ -0,0 +1,143 @@
|
||||
|
||||
// IN ORDER FOR CHANGES TO THIS FILE TO "TAKE" AND BE USED IN THE APP, THE BUILD IN wasm_build HAS TO BE RE-RUN
|
||||
|
||||
// scrypt and scryptPromise will be filled out by js code that gets appended below this script by the wasm_build process
|
||||
let scrypt;
|
||||
let scryptPromise;
|
||||
|
||||
let working = false;
|
||||
const batchSize = 8;
|
||||
|
||||
onmessage = function(e) {
|
||||
if(e.data.stop) {
|
||||
working = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const challengeBase64 = e.data.challenge;
|
||||
const workerId = e.data.workerId;
|
||||
if(!challengeBase64) {
|
||||
postMessage({
|
||||
type: "error",
|
||||
challenge: challengeBase64,
|
||||
message: `challenge was not provided`
|
||||
});
|
||||
}
|
||||
working = true;
|
||||
let challengeJSON = null;
|
||||
let challenge = null;
|
||||
try {
|
||||
challengeJSON = atob(challengeBase64);
|
||||
} catch (err) {
|
||||
postMessage({
|
||||
type: "error",
|
||||
challenge: challengeBase64,
|
||||
message: `couldn't decode challenge '${challengeBase64}' as base64: ${err}`
|
||||
});
|
||||
}
|
||||
try {
|
||||
challenge = JSON.parse(challengeJSON);
|
||||
} catch (err) {
|
||||
postMessage({
|
||||
type: "error",
|
||||
challenge: challengeBase64,
|
||||
message: `couldn't parse challenge '${challengeJSON}' as json: ${err}`
|
||||
});
|
||||
}
|
||||
|
||||
challenge = {
|
||||
cpuAndMemoryCost: challenge.N,
|
||||
blockSize: challenge.r,
|
||||
paralellization: challenge.p,
|
||||
keyLength: challenge.klen,
|
||||
preimage: challenge.i,
|
||||
difficulty: challenge.d,
|
||||
difficultyLevel: challenge.dl
|
||||
}
|
||||
|
||||
const probabilityOfFailurePerAttempt = 1-(1/Math.pow(2, challenge.difficultyLevel));
|
||||
|
||||
let i = workerId * Math.pow(2, challenge.difficulty.length) * 100;
|
||||
const hexPreimage = base64ToHex(challenge.preimage);
|
||||
let smallestHash = challenge.difficulty.split("").map(x => "f").join("");
|
||||
|
||||
postMessage({
|
||||
type: "progress",
|
||||
challenge: challengeBase64,
|
||||
attempts: 0,
|
||||
smallestHash: smallestHash,
|
||||
difficulty: challenge.difficulty,
|
||||
probabilityOfFailurePerAttempt: probabilityOfFailurePerAttempt
|
||||
});
|
||||
|
||||
const doWork = () => {
|
||||
|
||||
var j = 0;
|
||||
while(j < batchSize) {
|
||||
j++;
|
||||
i++;
|
||||
|
||||
let nonceHex = i.toString(16);
|
||||
if((nonceHex.length % 2) == 1) {
|
||||
nonceHex = `0${nonceHex}`;
|
||||
}
|
||||
const hashHex = scrypt(
|
||||
nonceHex,
|
||||
hexPreimage,
|
||||
challenge.cpuAndMemoryCost,
|
||||
challenge.blockSize,
|
||||
challenge.paralellization,
|
||||
challenge.keyLength
|
||||
);
|
||||
|
||||
//console.log(i.toString(16), hashHex);
|
||||
|
||||
const endOfHash = hashHex.substring(hashHex.length-challenge.difficulty.length);
|
||||
if(endOfHash < smallestHash) {
|
||||
smallestHash = endOfHash
|
||||
}
|
||||
if(endOfHash <= challenge.difficulty) {
|
||||
postMessage({
|
||||
type: "success",
|
||||
challenge: challengeBase64,
|
||||
nonce: i.toString(16),
|
||||
smallestHash: endOfHash,
|
||||
difficulty: challenge.difficulty
|
||||
});
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
postMessage({
|
||||
type: "progress",
|
||||
challenge: challengeBase64,
|
||||
attempts: batchSize,
|
||||
smallestHash: smallestHash,
|
||||
difficulty: challenge.difficulty,
|
||||
probabilityOfFailurePerAttempt: probabilityOfFailurePerAttempt
|
||||
});
|
||||
|
||||
if(working) {
|
||||
this.setTimeout(doWork, 1);
|
||||
}
|
||||
};
|
||||
|
||||
if(scrypt) {
|
||||
doWork();
|
||||
} else {
|
||||
scryptPromise.then(() => {
|
||||
doWork();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/39460182/decode-base64-to-hexadecimal-string-with-javascript
|
||||
function base64ToHex(str) {
|
||||
const raw = atob(str);
|
||||
let result = '';
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
const hex = raw.charCodeAt(i).toString(16);
|
||||
result += (hex.length === 2 ? hex : '0' + hex);
|
||||
}
|
||||
return result;
|
||||
}
|
50
wasm_build/build_wasm.sh
Executable file
50
wasm_build/build_wasm.sh
Executable file
@ -0,0 +1,50 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
if [ ! -f build_wasm.sh ]; then
|
||||
printf "Please run this script from the wasm_build folder.\n"
|
||||
fi
|
||||
|
||||
if [ ! -d scrypt-wasm ]; then
|
||||
printf "Cloning https://github.com/MyEtherWallet/scrypt-wasm... \n"
|
||||
git clone https://github.com/MyEtherWallet/scrypt-wasm
|
||||
fi
|
||||
|
||||
cd scrypt-wasm
|
||||
|
||||
rust_is_installed="$(which rustc | wc -l)"
|
||||
|
||||
if [ "$rust_is_installed" == "0" ]; then
|
||||
printf "rust language compilers & tools will need to be installed."
|
||||
printf "using rustup.rs: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh \n"
|
||||
read -p "is this ok? [y] " -n 1 -r
|
||||
printf "\n"
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]
|
||||
then
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -d pkg ]; then
|
||||
printf "running Makefile for MyEtherWallet/scrypt-wasm... \n"
|
||||
make
|
||||
fi
|
||||
|
||||
cd ../
|
||||
|
||||
nodejs_is_installed="$(which node | wc -l)"
|
||||
npm_is_installed="$(which npm | wc -l)"
|
||||
|
||||
if [ "$nodejs_is_installed" == "0" ] || [ "$npm_is_installed" == "0" ]; then
|
||||
printf "nodejs and npm are required for the next step. Please install them manually 😇"
|
||||
fi
|
||||
|
||||
if [ ! -d node_modules ]; then
|
||||
printf "running npm install \n"
|
||||
npm install
|
||||
fi
|
||||
|
||||
node build_wasm_webworker.js > "../static/proofOfWorkerWASM.js"
|
||||
|
||||
printf "\n\nbuilt ../static/proofOfWorkerWASM.js successfully!\n\n"
|
||||
|
||||
|
64
wasm_build/build_wasm_webworker.js
Normal file
64
wasm_build/build_wasm_webworker.js
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
const base32768 = require('base32768');
|
||||
const fs = require('fs');
|
||||
|
||||
const base32768WASM = base32768.encode(fs.readFileSync("scrypt-wasm/pkg/scrypt_wasm_bg.wasm"));
|
||||
|
||||
const wasmWrappperJS = fs.readFileSync("scrypt-wasm/pkg/scrypt_wasm_bg.js", { encoding: "utf8" });
|
||||
let lines = wasmWrappperJS.split("\n");
|
||||
|
||||
// filter out the first line "import * as wasm from './scrypt_wasm_bg.wasm';"
|
||||
// because we are using global namespace, not es6 modules
|
||||
lines = lines.filter(line => !line.includes("scrypt_wasm_bg.wasm"))
|
||||
|
||||
// replace export with global namespace for the same reason.
|
||||
lines = lines.map(line => {
|
||||
if(line.startsWith("export function scrypt")) {
|
||||
return line.replace("export function scrypt", "scrypt = function");
|
||||
}
|
||||
return line;
|
||||
});
|
||||
const customWASMWrappperJS = lines.join("\n");
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Output the composited webworker JS
|
||||
|
||||
// first, include the warning about this file being automatically generated
|
||||
console.log(`
|
||||
|
||||
// THIS FILE IS GENERATED AUTOMATICALLY
|
||||
// Don't edit this file by hand.
|
||||
// Either edit proofOfWorkerWASMStub.js or edit the build located in the wasm_build folder.
|
||||
|
||||
`)
|
||||
|
||||
// add the actual webworker logic at the top, while filtering out comments
|
||||
const stubJS = fs.readFileSync("../static/proofOfWorkerWASMStub.js", { encoding: "utf8" });
|
||||
console.log(stubJS.split("\n").filter(x => !x.startsWith("//")).join("\n"));
|
||||
|
||||
console.log(`
|
||||
|
||||
// Everything below this line is created by the build scripts in the wasm_build folder.
|
||||
|
||||
`)
|
||||
|
||||
// Now its time to load the wasm module.
|
||||
// first, load the base32768 module into a global variable called "base32768"
|
||||
console.log(fs.readFileSync("node_modules/base32768/dist/iife/base32768.js", { encoding: "utf8" }))
|
||||
|
||||
// now, decode the base32768 string into an ArrayBuffer and tell WebAssembly to load it
|
||||
console.log(`
|
||||
const base32768WASM = "${base32768WASM}";
|
||||
|
||||
const wasmBinary = base32768.decode(base32768WASM);
|
||||
|
||||
scryptPromise = WebAssembly.instantiate(wasmBinary, {}).then(instantiatedModule => {
|
||||
const wasm = instantiatedModule.instance.exports;
|
||||
`);
|
||||
|
||||
// Output the WASM wrapper JS code that came from the Rust WASM compiler,
|
||||
// slightly modified to use global namespace instead of es6 modules
|
||||
console.log(customWASMWrappperJS.split("\n").map(x => ` ${x}`).join("\n"));
|
||||
|
||||
// finish off by closing scryptPromise
|
||||
console.log("});");
|
13
wasm_build/package-lock.json
generated
Normal file
13
wasm_build/package-lock.json
generated
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "wasm_build",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"base32768": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base32768/-/base32768-2.0.1.tgz",
|
||||
"integrity": "sha512-DfpYn6XUE8YSsooJ4rj61EiTKkPJM1exxxbJB0byWvRc39ogWDZtrOSY0PVvGQe+DI1FZt10ES7xBifxmirqwQ=="
|
||||
}
|
||||
}
|
||||
}
|
14
wasm_build/package.json
Normal file
14
wasm_build/package.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "wasm_build",
|
||||
"version": "0.0.0",
|
||||
"description": "build wasm module into webworker",
|
||||
"main": "build_wasm_webworker.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"base32768": "^2.0.1"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user