Skip to content

Commit

Permalink
update to hash schemes
Browse files Browse the repository at this point in the history
  • Loading branch information
zkPorpoise committed Feb 26, 2024
1 parent 74ea376 commit 44fec80
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 97 deletions.
116 changes: 19 additions & 97 deletions take-survey.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

<link rel="stylesheet" href="/take-survey.css">

<script src="./utilities.js"></script>

<script>
// global variables for a given user's survey
let prediction = "";
Expand Down Expand Up @@ -85,7 +87,7 @@
} else {
deadlineTimestamp = alarmclock;
updateCountdown();
surveyLeafs.push(alarmclock);
surveyLeafs.push(mapBigIntTo256BitNumber(BigInt(alarmclock)));
}

if (oracle === null) {
Expand Down Expand Up @@ -118,12 +120,17 @@
document.getElementById('🐬').innerHTML = "A PORPOISE survey must have at least 2 options (🗳1 & 🗳2)."
return 0;
} else {
paddedSurveyLeafs = padArrayToPowerOfTwo(surveyLeafs, '0');
computeMerkleRoot(paddedSurveyLeafs).then((root) => {
console.log("Merkle Root: ", root);
surveyRoot = root;
setOptions(options);
padArrayToPowerOfTwo(surveyLeafs, '0').then((leafs) => {
console.log("Number of leafs: ", leafs.length);
paddedSurveyLeafs = leafs;
return computeMerkleRoot(leafs, [], 1);
})
.then((merkle) => {
surveyRoot = arrayBufferToHex(merkle[0]);
console.log("Merkle Root: ", surveyRoot);
surveyRoot = merkle[0];
setOptions(options);
});
}
} else {

Expand Down Expand Up @@ -175,20 +182,12 @@
document.getElementById("inputContainer").style.display = "block";
return 0;
} else {
// hash the prediction
hashString(prediction).then((predictionHash) => {
console.log("Prediction Hash: ", predictionHash);

// don't regenerate a salt value if we've already made on in the past for this surveyRoot
predictionSalt = (predictionSalt === 0) ? generateRandomString(18) : predictionSalt;
console.log("Prediction Salt: ", predictionSalt);

// now create the prediction commitment by concatenating the salt, prediction hash, ans survey root
hashString(predictionSalt + predictionHash + surveyRoot).then((predictionCommitment) => {
setPredictionHTMLElements(predictionCommitment);
savePrediction(prediction, predictionSalt, predictionCommitment);
getPasskeySignature(predictionCommitment);
});
predictionSalt = (predictionSalt === 0) ? generateRandomString(18) : predictionSalt;
console.log("Prediction Salt: ", predictionSalt);
predictionCommitment(predictionSalt, prediction, stringToBuffer(surveyRoot, 'hex')).then((commitment) => {
setPredictionHTMLElements(commitment);
savePrediction(prediction, predictionSalt, commitment);
getPasskeySignature(predictionCommitment);
})
}
}
Expand Down Expand Up @@ -409,59 +408,6 @@
return randomString;
}

function padArrayToPowerOfTwo(arr, paddingValue) {
// Check if the array length is already a power of 2
if ((arr.length & (arr.length - 1)) === 0) {
return arr; // Array length is already a power of 2, no padding needed
}

// Calculate the next power of 2 greater than the current length
const nextPowerOfTwo = Math.pow(2, Math.ceil(Math.log2(arr.length)));

// Calculate the number of elements to pad
const paddingCount = nextPowerOfTwo - arr.length;

// Pad the array with the specified paddingValue (default is undefined)
return arr.concat(new Array(paddingCount).fill(paddingValue));
}

// Function to compute the Merkle root hash from an array of strings
async function computeMerkleRoot(strings) {
if (strings.length === 0) {
return null;
}

// Base case: if there's only one element, return its hash
if (strings.length === 1) {
return await hashString(strings[0]);
}

// Recursive case: compute hash of pairs until a single hash is obtained
const pairedHashes = [];
for (let i = 0; i < strings.length; i += 2) {
const left = strings[i];
const right = (i + 1 < strings.length) ? strings[i + 1] : '';
const pairHash = await hashString(left + right);
pairedHashes.push(pairHash);
}

// Recursively compute the Merkle root for the paired hashes
return await computeMerkleRoot(pairedHashes);
}

// Function to hash a string (use any suitable hash function)
// takes in a utf-8 string, then returns a hex string 🤷
// this is dumb but we'll unfuck it l8r
function hashString(str) {
const encoder = new TextEncoder();
const data = encoder.encode(str);

return crypto.subtle.digest('SHA-256', data).then(hashBuffer => {
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
});
}

function createAccount() {
// first clear out the other button options
const container = document.getElementById("loginContainer");
Expand Down Expand Up @@ -530,17 +476,6 @@
// TODO: put some logic here to surface the error
console.log("ERROR Registering Passkey: ", err);
});
/*return fetch('https://zkporpoise.replit.app/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"keyID": id,
"username": username,
"base64EncodedPublicKey": base64UrlEncodedPublicKey
})
})*/
})
}

Expand Down Expand Up @@ -629,19 +564,6 @@
storePublicKey(userData.keyid, base64UrlStringToArrayBuffer(userData.base64urlencodedpublickey), userData.username);
location.reload();
})
// if we're signing a login message, ask the server for the associated public key and username
/*return fetch('https://zkporpoise.replit.app/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"keyID": keyID,
"base64EncodedAuthenticatorData": arrayBufferToBase64String(authenticatorData),
"base64EncodedClientDataJSON": arrayBufferToBase64String(clientDataJSON),
"base64EncodedSignature": arrayBufferToBase64String(signature)
})
})*/
} else {
const predictionLink = urlPrefix + '/predictAndVerify' + window.location.search + '&' +
urlEncodedSalt + '=' + predictionSalt + '&' + urlEncodedCheck + '=' + prediction + '&' +
Expand Down
134 changes: 134 additions & 0 deletions utilities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
function helloWorld() {
return "🐬 Hello World!";
}
function hashBuffer(data) {
return __awaiter(this, void 0, void 0, function* () {
return yield crypto.subtle.digest('SHA-256', data);
});
}
function arrayBufferToHex(arrayBuffer) {
const uint8Array = new Uint8Array(arrayBuffer);
return Array.prototype.map.call(uint8Array, byte => {
return ('0' + byte.toString(16)).slice(-2);
}).join('');
}
function mapBigIntTo256BitNumber(bigIntValue) {
// Convert the BigInt to a hexadecimal string
let hexString = bigIntValue.toString(16);
// Pad the hexadecimal string with zeros to ensure it is 64 characters long
while (hexString.length < 64) {
hexString = '0' + hexString;
}
return hexString;
}
function stringToBuffer(str, encoding) {
if (encoding === 'utf8' || encoding === 'utf-8') {
const encoder = new TextEncoder();
return encoder.encode(str).buffer;
}
else if (encoding === 'hex') {
if (str.length % 2 !== 0) {
throw new Error("Hex string must have an even number of characters");
}
const uint8Array = new Uint8Array(str.length / 2);
for (let i = 0; i < str.length; i += 2) {
const byteValue = parseInt(str.substring(i, i + 2), 16);
uint8Array[i / 2] = byteValue;
}
return uint8Array.buffer;
}
else {
return new ArrayBuffer(0);
}
}
function padArrayToPowerOfTwo(arr, paddingValue) {
return __awaiter(this, void 0, void 0, function* () {
// Check if the array length is already a power of 2
if ((arr.length & (arr.length - 1)) === 0) {
return Promise.all(arr.map((elem, index) => {
if (index === 1) {
// for the time element, it must be hex encoded
return hashBuffer(stringToBuffer(elem, 'hex'));
}
else {
return hashBuffer(stringToBuffer(elem, 'utf8'));
}
})); // Array length is already a power of 2, no padding needed
}
// Calculate the next power of 2 greater than the current length
const nextPowerOfTwo = Math.pow(2, Math.ceil(Math.log2(arr.length)));
// Calculate the number of elements to pad
const paddingCount = nextPowerOfTwo - arr.length;
// Pad the array with the specified paddingValue (default is undefined)
return Promise.all(arr.concat(new Array(paddingCount).fill(paddingValue))
.map((elem, index) => {
if (index === 1) {
// for the time element, it must be hex encoded
return hashBuffer(stringToBuffer(elem, 'hex'));
}
else {
return hashBuffer(stringToBuffer(elem, 'utf8'));
}
}));
});
}
function computeMerkleRoot(leafs, proof, tracker) {
return __awaiter(this, void 0, void 0, function* () {
// Base case: if there's only one element, return its hash
if (leafs.length === 1) {
return [leafs[0], proof];
}
// Recursive case: compute hash of pairs until a single hash is obtained
const pairedHashes = [];
let j = 0;
for (let i = 0; i < leafs.length; i += 2, j++) {
const a = leafs[i];
const b = leafs[i + 1];
if (tracker === i) {
proof.push(b);
tracker = j;
}
else if (tracker === (i + 1)) {
proof.push(a);
tracker = j;
}
const pairHash = (uint8ArrayToBigInt(new Uint8Array(a)) < uint8ArrayToBigInt(new Uint8Array(b))) ?
yield hashBuffer(concatArrayBuffers(a, b)) :
yield hashBuffer(concatArrayBuffers(b, a));
pairedHashes.push(pairHash);
}
// Recursively compute the Merkle root for the paired hashes
return computeMerkleRoot(pairedHashes, proof, tracker);
});
}
function predictionCommitment(salt, prediction, surveyRoot) {
return __awaiter(this, void 0, void 0, function* () {
const saltBuffer = stringToBuffer(salt, 'utf8');
const predictionBuffer = stringToBuffer(prediction, 'utf8');
return hashBuffer(concatArrayBuffers(concatArrayBuffers(saltBuffer, predictionBuffer), surveyRoot));
});
}
function concatArrayBuffers(buffer1, buffer2) {
const combinedLength = buffer1.byteLength + buffer2.byteLength;
const combinedArray = new Uint8Array(combinedLength);
combinedArray.set(new Uint8Array(buffer1), 0);
combinedArray.set(new Uint8Array(buffer2), buffer1.byteLength);
return combinedArray.buffer;
}
function uint8ArrayToBigInt(uint8Array) {
let result = BigInt(0);
for (let i = 0; i < uint8Array.length; i++) {
result = result << BigInt(8);
result = result + BigInt(uint8Array[i]);
}
return result;
}

0 comments on commit 44fec80

Please sign in to comment.