demo & proof of concept

This commit is contained in:
overflowerror 2024-08-15 22:48:04 +02:00
commit 44f96fffe9
2 changed files with 159 additions and 0 deletions

106
index.html Normal file
View file

@ -0,0 +1,106 @@
<!DOCTYPE html>
<html>
<head>
<script src="main.js"></script>
<style>
.captcha {
width: 300px;
height: 70px;
padding: 20px;
border: 1px solid grey;
box-sizing: border-box;
font-size: 20px;
font-family: sans-serif;
}
.captcha span {
position: relative;
top: -9px;
}
.captcha .checkbox {
position: relative;
margin: 0;
margin-right: 20px;
height: 30px;
width: 30px;
display: inline-block;
background-color: white;
cursor: pointer;
border: 1px solid black;
border-radius: 3px;
}
.captcha .checkbox:hover {
background-color: lightgrey;
}
.captcha .checkbox.checked {
background-color: #2196F3;
}
.captcha .checkbox.checked::before {
position: absolute;
display: block;
content: "";
width: 7px;
height: 15px;
top: 2px;
left: 9px;
border: white solid;
border-width: 0 5px 5px 0;
transform: rotate(45deg);
}
.captcha .checkbox.loading {
}
.captcha .checkbox.loading::before {
content: "";
display: block;
position: absolute;
top: 2.5px;
left: 2.5px;
width: 17px;
height: 17px;
border: 4px solid;
border-radius: 50%;
border-color: black transparent black transparent;
animation: captchaLoading infinite 1s;
}
@keyframes captchaLoading {
to{
transform: rotate(.5turn)
}
}
</style>
<script>
window.addEventListener("load", () => {
[...document.querySelectorAll(".captcha .checkbox")].forEach(c => c.addEventListener("click", async function() {
const result = document.getElementById("result");
result.innerText = "Calculating...";
this.classList.add("loading");
const challenge = makeSuffix(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) + ":" + makeSuffix(new Date().valueOf()) + ":";
const response = await findHashWithPrefix(15, challenge);
result.innerText = "Challenge Response: " + response;
this.classList.remove("loading");
this.classList.add("checked");
}));
});
</script>
</head>
<body>
<div class="captcha">
<div class="checkbox"></div>
<span>I am not a robot.</span>
</div>
<div id="result"></div>
</body>
</html>

53
main.js Normal file
View file

@ -0,0 +1,53 @@
const hexCharset = "0123456789abcdef".split("");
const suffixCharset = [
"0123456789",
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"+-",
].flatMap(set => set.split(""));
const suffixCharsetBits = 6;
const sha256 = "SHA-256";
Uint8Array.prototype.toHex = function() {
return [...this].map(c => hexCharset[c >> 4] + hexCharset[c & 15]).join("");
};
Uint8Array.prototype.hasPrefix = function(array, bits) {
for(let i = 0; i < array.length && i * 8 < bits; i++) {
const bitsForByte = Math.min(8, bits - i * 8);
const bitMask = ((1 << bitsForByte) - 1) << (8 - bitsForByte);
if ((this[i] & bitMask) != (array[i] & bitMask)) {
return false;
}
}
return true;
};
async function digest(algo, input) {
return new Uint8Array(await crypto.subtle.digest(algo, new TextEncoder().encode(input)));
}
function makeSuffix(index) {
let result = "";
while (index > 0) {
result += suffixCharset[index & ((1 << suffixCharsetBits) - 1)];
index >>= suffixCharsetBits;
}
return result;
}
async function findHashWithPrefix(hashPrefixBits, inputPrefix) {
const hashPrefix = new Uint8Array(Array(Math.ceil(hashPrefixBits / 8)).map(c => 0));
let iteration = 0;
do {
var message = inputPrefix + makeSuffix(iteration++)
var hash = await digest(sha256, message);
} while (!hash.hasPrefix(hashPrefix, hashPrefixBits));
return message;
}