commit before deleting non-wasm version

This commit is contained in:
forest 2021-02-24 21:38:05 -06:00
parent 3fbc0e3fcd
commit 9a9008867c
13 changed files with 1381 additions and 68 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
wasm_build/node_modules
wasm_build/scrypt-wasm

18
Dockerfile Normal file
View 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
View 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
View File

@ -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
View 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);
}
}

View File

@ -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
View 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

File diff suppressed because one or more lines are too long

View 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
View 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"

View 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
View 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
View 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"
}
}