remove pow
parent
8444c7cbfc
commit
0c0da48150
@ -1,14 +0,0 @@
|
||||
export interface LokiMessageInterface {
|
||||
sendMessage(
|
||||
pubKey: string,
|
||||
data: Uint8Array,
|
||||
messageTimeStamp: number,
|
||||
ttl: number
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
declare class LokiMessageAPI implements LokiMessageInterface {
|
||||
constructor(ourKey: string);
|
||||
}
|
||||
|
||||
export default LokiMessageAPI;
|
@ -1,110 +0,0 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-loop-func */
|
||||
/* global log, dcodeIO, window, callWorker, textsecure */
|
||||
|
||||
const _ = require('lodash');
|
||||
const primitives = require('./loki_primitives');
|
||||
|
||||
const DEFAULT_CONNECTIONS = 3;
|
||||
|
||||
const calcNonce = (messageEventData, pubKey, data64, timestamp, ttl) => {
|
||||
const difficulty = window.storage.get('PoWDifficulty', null);
|
||||
// Nonce is returned as a base64 string to include in header
|
||||
window.Whisper.events.trigger('calculatingPoW', messageEventData);
|
||||
return callWorker('calcPoW', timestamp, ttl, pubKey, data64, difficulty);
|
||||
};
|
||||
|
||||
async function _openSendConnection(snode, params) {
|
||||
// TODO: Revert back to using snode address instead of IP
|
||||
const successfulSend = await window.NewSnodeAPI.storeOnNode(snode, params);
|
||||
if (successfulSend) {
|
||||
return snode;
|
||||
}
|
||||
// should we mark snode as bad if it can't store our message?
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class LokiMessageAPI {
|
||||
/**
|
||||
* Refactor note: We should really clean this up ... it's very messy
|
||||
*
|
||||
* We need to split it into 2 sends:
|
||||
* - Snodes
|
||||
* - Open Groups
|
||||
*
|
||||
* Mikunj:
|
||||
* Temporarily i've made it so `MessageSender` handles open group sends and calls this function for regular sends.
|
||||
*/
|
||||
async sendMessage(pubKey, data, messageTimeStamp, ttl, options = {}) {
|
||||
const { isPublic = false, numConnections = DEFAULT_CONNECTIONS } = options;
|
||||
// Data required to identify a message in a conversation
|
||||
const messageEventData = {
|
||||
pubKey,
|
||||
timestamp: messageTimeStamp,
|
||||
};
|
||||
|
||||
if (isPublic) {
|
||||
window.log.warn('this sendMessage() should not be called anymore with an open group message');
|
||||
return;
|
||||
}
|
||||
|
||||
const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64');
|
||||
|
||||
const timestamp = Date.now();
|
||||
const nonce = await calcNonce(
|
||||
messageEventData,
|
||||
window.getStoragePubKey(pubKey),
|
||||
data64,
|
||||
timestamp,
|
||||
ttl
|
||||
);
|
||||
// Using timestamp as a unique identifier
|
||||
const swarm = await window.SnodePool.getSnodesFor(pubKey);
|
||||
|
||||
// send parameters
|
||||
const params = {
|
||||
pubKey,
|
||||
ttl: ttl.toString(),
|
||||
nonce,
|
||||
timestamp: timestamp.toString(),
|
||||
data: data64,
|
||||
};
|
||||
|
||||
const usedNodes = _.slice(swarm, 0, numConnections);
|
||||
|
||||
const promises = usedNodes.map(snode => _openSendConnection(snode, params));
|
||||
|
||||
let snode;
|
||||
try {
|
||||
// eslint-disable-next-line more/no-then
|
||||
snode = await primitives.firstTrue(promises);
|
||||
} catch (e) {
|
||||
const snodeStr = snode ? `${snode.ip}:${snode.port}` : 'null';
|
||||
log.warn(
|
||||
`loki_message:::sendMessage - ${e.code} ${e.message} to ${pubKey} via snode:${snodeStr}`
|
||||
);
|
||||
if (e instanceof textsecure.WrongDifficultyError) {
|
||||
// Force nonce recalculation
|
||||
// NOTE: Currently if there are snodes with conflicting difficulties we
|
||||
// will send the message twice (or more). Won't affect client side but snodes
|
||||
// could store the same message multiple times because they will have different
|
||||
// timestamps (and therefore nonces)
|
||||
await this.sendMessage(pubKey, data, messageTimeStamp, ttl, options);
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
if (!snode) {
|
||||
throw new window.textsecure.EmptySwarmError(pubKey, 'Ran out of swarm nodes to query');
|
||||
} else {
|
||||
log.info(
|
||||
`loki_message:::sendMessage - Successfully stored message to ${pubKey} via ${snode.ip}:${snode.port}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These files are expected to be in commonjs so we can't use es6 syntax :(
|
||||
// If we move these to TS then we should be able to use es6
|
||||
module.exports = LokiMessageAPI;
|
@ -1,3 +0,0 @@
|
||||
export async function sleepFor(ms: number);
|
||||
|
||||
export async function abortableIterator(array: Array<any>, action: (any) => void);
|
@ -1,62 +0,0 @@
|
||||
// was timeoutDelay
|
||||
const sleepFor = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
// Taken from https://stackoverflow.com/questions/51160260/clean-way-to-wait-for-first-true-returned-by-promise
|
||||
// The promise returned by this function will resolve true when the first promise
|
||||
// in ps resolves true *or* it will resolve false when all of ps resolve false
|
||||
const firstTrue = ps => {
|
||||
const newPs = ps.map(
|
||||
p =>
|
||||
new Promise(
|
||||
// eslint-disable-next-line more/no-then
|
||||
(resolve, reject) => p.then(v => v && resolve(v), reject)
|
||||
)
|
||||
);
|
||||
// eslint-disable-next-line more/no-then
|
||||
newPs.push(Promise.all(ps).then(() => false));
|
||||
return Promise.race(newPs);
|
||||
};
|
||||
|
||||
function abortableIterator(array, iterator) {
|
||||
let abortIteration = false;
|
||||
|
||||
// for the control promise
|
||||
let controlResolveFunctor;
|
||||
const stopPolling = new Promise(res => {
|
||||
// store resolve functor
|
||||
controlResolveFunctor = res;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line more/no-then
|
||||
stopPolling.then(() => {
|
||||
abortIteration = true;
|
||||
});
|
||||
|
||||
const destructableList = [...array];
|
||||
const accum = [];
|
||||
|
||||
return {
|
||||
start: async serially => {
|
||||
let item = destructableList.pop();
|
||||
while (item && !abortIteration) {
|
||||
if (serially) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
accum.push(await iterator(item));
|
||||
} else {
|
||||
accum.push(iterator(item));
|
||||
}
|
||||
item = destructableList.pop();
|
||||
}
|
||||
return accum;
|
||||
},
|
||||
stop: () => {
|
||||
controlResolveFunctor();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sleepFor,
|
||||
abortableIterator,
|
||||
firstTrue,
|
||||
};
|
@ -1,125 +0,0 @@
|
||||
/* global dcodeIO, crypto, JSBI */
|
||||
const NONCE_LEN = 8;
|
||||
// Modify this value for difficulty scaling
|
||||
const FALLBACK_DIFFICULTY = 10;
|
||||
|
||||
const pow = {
|
||||
// Increment Uint8Array nonce by '_increment' with carrying
|
||||
incrementNonce(nonce, _increment = 1) {
|
||||
let idx = NONCE_LEN - 1;
|
||||
const newNonce = new Uint8Array(nonce);
|
||||
let increment = _increment;
|
||||
do {
|
||||
const sum = newNonce[idx] + increment;
|
||||
newNonce[idx] = sum % 256;
|
||||
increment = Math.floor(sum / 256);
|
||||
idx -= 1;
|
||||
} while (increment > 0 && idx >= 0);
|
||||
return newNonce;
|
||||
},
|
||||
|
||||
// Convert a Uint8Array to a base64 string
|
||||
bufferToBase64(buf) {
|
||||
function mapFn(ch) {
|
||||
return String.fromCharCode(ch);
|
||||
}
|
||||
const binaryString = Array.prototype.map.call(buf, mapFn).join('');
|
||||
return dcodeIO.ByteBuffer.btoa(binaryString);
|
||||
},
|
||||
|
||||
// Convert BigInteger to Uint8Array of length NONCE_LEN
|
||||
bigIntToUint8Array(bigInt) {
|
||||
const arr = new Uint8Array(NONCE_LEN);
|
||||
let n;
|
||||
for (let idx = NONCE_LEN - 1; idx >= 0; idx -= 1) {
|
||||
n = NONCE_LEN - (idx + 1);
|
||||
// 256 ** n is the value of one bit in arr[idx], modulus to carry over
|
||||
// (bigInt / 256**n) % 256;
|
||||
const denominator = JSBI.exponentiate(JSBI.BigInt('256'), JSBI.BigInt(n));
|
||||
const fraction = JSBI.divide(bigInt, denominator);
|
||||
const uint8Val = JSBI.remainder(fraction, JSBI.BigInt(256));
|
||||
arr[idx] = JSBI.toNumber(uint8Val);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
|
||||
// Compare two Uint8Arrays, return true if arr1 is > arr2
|
||||
greaterThan(arr1, arr2) {
|
||||
// Early exit if lengths are not equal. Should never happen
|
||||
if (arr1.length !== arr2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0, len = arr1.length; i < len; i += 1) {
|
||||
if (arr1[i] > arr2[i]) {
|
||||
return true;
|
||||
}
|
||||
if (arr1[i] < arr2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// Return nonce that hashes together with payload lower than the target
|
||||
async calcPoW(timestamp, ttl, pubKey, data, _difficulty = null, increment = 1, startNonce = 0) {
|
||||
const payload = new Uint8Array(
|
||||
dcodeIO.ByteBuffer.wrap(
|
||||
timestamp.toString() + ttl.toString() + pubKey + data,
|
||||
'binary'
|
||||
).toArrayBuffer()
|
||||
);
|
||||
|
||||
const difficulty = _difficulty || FALLBACK_DIFFICULTY;
|
||||
const target = pow.calcTarget(ttl, payload.length, difficulty);
|
||||
|
||||
let nonce = new Uint8Array(NONCE_LEN);
|
||||
nonce = pow.incrementNonce(nonce, startNonce); // initial value
|
||||
let trialValue = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]);
|
||||
const initialHash = new Uint8Array(await crypto.subtle.digest('SHA-512', payload));
|
||||
const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN);
|
||||
innerPayload.set(initialHash, NONCE_LEN);
|
||||
let resultHash;
|
||||
let nextNonce = nonce;
|
||||
while (pow.greaterThan(trialValue, target)) {
|
||||
nonce = nextNonce;
|
||||
nextNonce = pow.incrementNonce(nonce, increment);
|
||||
innerPayload.set(nonce);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
resultHash = await crypto.subtle.digest('SHA-512', innerPayload);
|
||||
trialValue = new Uint8Array(dcodeIO.ByteBuffer.wrap(resultHash, 'hex').toArrayBuffer()).slice(
|
||||
0,
|
||||
NONCE_LEN
|
||||
);
|
||||
}
|
||||
return pow.bufferToBase64(nonce);
|
||||
},
|
||||
|
||||
calcTarget(ttl, payloadLen, difficulty = FALLBACK_DIFFICULTY) {
|
||||
// payloadLength + NONCE_LEN
|
||||
const totalLen = JSBI.add(JSBI.BigInt(payloadLen), JSBI.BigInt(NONCE_LEN));
|
||||
// ttl converted to seconds
|
||||
const ttlSeconds = JSBI.divide(JSBI.BigInt(ttl), JSBI.BigInt(1000));
|
||||
// ttl * totalLen
|
||||
const ttlMult = JSBI.multiply(ttlSeconds, JSBI.BigInt(totalLen));
|
||||
// 2^16 - 1
|
||||
const two16 = JSBI.subtract(
|
||||
JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(16)), // 2^16
|
||||
JSBI.BigInt(1)
|
||||
);
|
||||
// ttlMult / two16
|
||||
const innerFrac = JSBI.divide(ttlMult, two16);
|
||||
// totalLen + innerFrac
|
||||
const lenPlusInnerFrac = JSBI.add(totalLen, innerFrac);
|
||||
// difficulty * lenPlusInnerFrac
|
||||
const denominator = JSBI.multiply(JSBI.BigInt(difficulty), lenPlusInnerFrac);
|
||||
// 2^64 - 1
|
||||
const two64 = JSBI.subtract(
|
||||
JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(64)), // 2^64
|
||||
JSBI.BigInt(1)
|
||||
);
|
||||
// two64 / denominator
|
||||
const targetNum = JSBI.divide(two64, denominator);
|
||||
return pow.bigIntToUint8Array(targetNum);
|
||||
},
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": false,
|
||||
"mocha": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "script"
|
||||
},
|
||||
"rules": {
|
||||
"strict": "off",
|
||||
"more/no-then": "off"
|
||||
},
|
||||
"globals": {
|
||||
"assert": true,
|
||||
"clearDatabase": true
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/* global window, mocha, chai, assert, Whisper */
|
||||
|
||||
mocha
|
||||
.setup('bdd')
|
||||
.fullTrace()
|
||||
.timeout(10000);
|
||||
window.assert = chai.assert;
|
||||
window.PROTO_ROOT = '../../protos';
|
||||
|
||||
const OriginalReporter = mocha._reporter;
|
||||
|
||||
const SauceReporter = function Constructor(runner) {
|
||||
const failedTests = [];
|
||||
|
||||
runner.on('end', () => {
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', (test, err) => {
|
||||
const flattenTitles = item => {
|
||||
const titles = [];
|
||||
while (item.parent.title) {
|
||||
titles.push(item.parent.title);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
item = item.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test),
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new OriginalReporter(runner);
|
||||
};
|
||||
|
||||
SauceReporter.prototype = OriginalReporter.prototype;
|
||||
|
||||
mocha.reporter(SauceReporter);
|
||||
|
||||
// Override the database id.
|
||||
window.Whisper = window.Whisper || {};
|
||||
window.Whisper.Database = window.Whisper.Database || {};
|
||||
Whisper.Database.id = 'test';
|
||||
|
||||
/*
|
||||
* global helpers for tests
|
||||
*/
|
||||
window.clearDatabase = async () => {
|
||||
await window.Signal.Data.removeAll();
|
||||
};
|
@ -1,43 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>libloki test runner</title>
|
||||
<link rel="stylesheet" href="../../node_modules/mocha/mocha.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="mocha">
|
||||
</div>
|
||||
<div id="tests">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="test.js"></script>
|
||||
<script type="text/javascript" src="components.js"></script>
|
||||
|
||||
<script type="text/javascript" src="../../libloki/proof-of-work.js"></script>
|
||||
<script type="text/javascript" src="../../libtextsecure/helpers.js" data-cover></script>
|
||||
<script type="text/javascript" src="../../libtextsecure/storage.js" data-cover></script>
|
||||
<script type="text/javascript" src="../../libtextsecure/libsignal-protocol.js"></script>
|
||||
<script type="text/javascript" src="../../libtextsecure/protobufs.js" data-cover></script>
|
||||
<script type="text/javascript" src="../../libtextsecure/stringview.js" data-cover></script>
|
||||
|
||||
<script type="text/javascript" src="../crypto.js" data-cover></script>
|
||||
<script type="text/javascript" src="../service_nodes.js" data-cover></script>
|
||||
<script type="text/javascript" src="../storage.js" data-cover></script>
|
||||
|
||||
<script type="text/javascript" src="proof-of-work_test.js"></script>
|
||||
<script type="text/javascript" src="messages.js"></script>
|
||||
|
||||
<!-- Comment out to turn off code coverage. Useful for getting real callstacks. -->
|
||||
<!-- NOTE: blanket doesn't support modern syntax and will choke until we find a replacement. :0( -->
|
||||
<!-- <script type="text/javascript" src="blanket_mocha.js"></script> -->
|
||||
|
||||
<!-- Uncomment to start tests without code coverage enabled -->
|
||||
<script type="text/javascript">
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,181 +0,0 @@
|
||||
/* global dcodeIO, Plotly */
|
||||
let jobId = 0;
|
||||
let currentTrace = 0;
|
||||
let plotlyDiv;
|
||||
const workers = [];
|
||||
async function run(messageLength, numWorkers = 1, difficulty = 100, ttl = 72) {
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const pubKey = '05ec8635a07a13743516c7c9b3412f3e8252efb7fcaf67eb1615ffba62bebc6802';
|
||||
const message = randomString(messageLength);
|
||||
const messageBuffer = dcodeIO.ByteBuffer.wrap(message, 'utf8').toArrayBuffer();
|
||||
const data = dcodeIO.ByteBuffer.wrap(messageBuffer).toString('base64');
|
||||
const promises = [];
|
||||
const t0 = performance.now();
|
||||
for (let w = 0; w < numWorkers; w += 1) {
|
||||
const worker = new Worker('../../js/util_worker.js');
|
||||
workers.push(worker);
|
||||
jobId += 1;
|
||||
const increment = numWorkers;
|
||||
const index = w;
|
||||
worker.postMessage([
|
||||
jobId,
|
||||
'calcPoW',
|
||||
timestamp,
|
||||
ttl * 60 * 60 * 1000,
|
||||
pubKey,
|
||||
data,
|
||||
false,
|
||||
difficulty,
|
||||
increment,
|
||||
index,
|
||||
]);
|
||||
const p = new Promise(resolve => {
|
||||
worker.onmessage = nonce => {
|
||||
resolve(nonce);
|
||||
};
|
||||
});
|
||||
promises.push(p);
|
||||
}
|
||||
await Promise.race(promises);
|
||||
const t1 = performance.now();
|
||||
const duration = (t1 - t0) / 1000;
|
||||
addPoint(duration);
|
||||
// clean up
|
||||
workers.forEach(worker => worker.terminate());
|
||||
}
|
||||
|
||||
async function runPoW({ iteration, difficulty, numWorkers, messageLength = 50, ttl = 72 }) {
|
||||
const name = `W:${numWorkers} - NT: ${difficulty} - L:${messageLength} - TTL:${ttl}`;
|
||||
Plotly.addTraces(plotlyDiv, {
|
||||
y: [],
|
||||
type: 'box',
|
||||
boxpoints: 'all',
|
||||
name,
|
||||
});
|
||||
for (let i = 0; i < iteration; i += 1) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await run(messageLength, numWorkers, difficulty, ttl);
|
||||
}
|
||||
currentTrace += 1;
|
||||
|
||||
// eslint-disable-next-line
|
||||
console.log(`done for ${name}`);
|
||||
}
|
||||
|
||||
function randomString(length) {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function addPoint(duration) {
|
||||
Plotly.extendTraces(plotlyDiv, { y: [[duration]] }, [currentTrace]);
|
||||
}
|
||||
async function startMessageLengthRun() {
|
||||
const iteration0 = parseFloat(document.getElementById('iteration0').value);
|
||||
const difficulty0 = parseFloat(document.getElementById('difficulty0').value);
|
||||
const numWorkers0 = parseFloat(document.getElementById('numWorkers0').value);
|
||||
const messageLengthStart0 = parseFloat(document.getElementById('messageLengthStart0').value);
|
||||
const messageLengthStop0 = parseFloat(document.getElementById('messageLengthStop0').value);
|
||||
const messageLengthStep0 = parseFloat(document.getElementById('messageLengthStep0').value);
|
||||
const TTL0 = parseFloat(document.getElementById('TTL0').value);
|
||||
for (let l = messageLengthStart0; l < messageLengthStop0; l += messageLengthStep0) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await runPoW({
|
||||
iteration: iteration0,
|
||||
difficulty: difficulty0,
|
||||
numWorkers: numWorkers0,
|
||||
messageLength: l,
|
||||
ttl: TTL0,
|
||||
});
|
||||
}
|
||||
}
|
||||
async function startNumWorkerRun() {
|
||||
const iteration1 = parseFloat(document.getElementById('iteration1').value);
|
||||
const difficulty1 = parseFloat(document.getElementById('difficulty1').value);
|
||||
const numWorkersStart1 = parseFloat(document.getElementById('numWorkersStart1').value);
|
||||
const numWorkersEnd1 = parseFloat(document.getElementById('numWorkersEnd1').value);
|
||||
const messageLength1 = parseFloat(document.getElementById('messageLength1').value);
|
||||
const TTL1 = parseFloat(document.getElementById('TTL1').value);
|
||||
for (let numWorkers = numWorkersStart1; numWorkers <= numWorkersEnd1; numWorkers += 1) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await runPoW({
|
||||
iteration: iteration1,
|
||||
difficulty: difficulty1,
|
||||
numWorkers,
|
||||
messageLength: messageLength1,
|
||||
ttl: TTL1,
|
||||
});
|
||||
}
|
||||
}
|
||||
async function startDifficultyRun() {
|
||||
const iteration2 = parseFloat(document.getElementById('iteration2').value);
|
||||
const messageLength2 = parseFloat(document.getElementById('messageLength2').value);
|
||||
const numWorkers2 = parseFloat(document.getElementById('numWorkers2').value);
|
||||
const difficultyStart2 = parseFloat(document.getElementById('difficultyStart2').value);
|
||||
const difficultyStop2 = parseFloat(document.getElementById('difficultyStop2').value);
|
||||
const difficultyStep2 = parseFloat(document.getElementById('difficultyStep2').value);
|
||||
const TTL2 = parseFloat(document.getElementById('TTL2').value);
|
||||
for (let n = difficultyStart2; n < difficultyStop2; n += difficultyStep2) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await runPoW({
|
||||
iteration: iteration2,
|
||||
difficulty: n,
|
||||
numWorkers: numWorkers2,
|
||||
messageLength: messageLength2,
|
||||
ttl: TTL2,
|
||||
});
|
||||
}
|
||||
}
|
||||
async function starTTLRun() {
|
||||
const iteration3 = parseFloat(document.getElementById('iteration3').value);
|
||||
const difficulty3 = parseFloat(document.getElementById('difficulty3').value);
|
||||
const messageLength3 = parseFloat(document.getElementById('messageLength3').value);
|
||||
const numWorkers3 = parseFloat(document.getElementById('numWorkers3').value);
|
||||
const TTLStart3 = parseFloat(document.getElementById('TTLStart3').value);
|
||||
const TTLStop3 = parseFloat(document.getElementById('TTLStop3').value);
|
||||
const TTLStep3 = parseFloat(document.getElementById('TTLStep3').value);
|
||||
for (let ttl = TTLStart3; ttl < TTLStop3; ttl += TTLStep3) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await runPoW({
|
||||
iteration: iteration3,
|
||||
difficulty: difficulty3,
|
||||
numWorkers: numWorkers3,
|
||||
messageLength: messageLength3,
|
||||
ttl,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async function start(index) {
|
||||
const data = [];
|
||||
const layout = {};
|
||||
const options = {
|
||||
responsive: true,
|
||||
};
|
||||
plotlyDiv = `plotly${index}`;
|
||||
currentTrace = 0;
|
||||
window.chart = Plotly.newPlot(plotlyDiv, data, layout, options);
|
||||
workers.forEach(worker => worker.terminate());
|
||||
|
||||
switch (index) {
|
||||
case 0:
|
||||
await startMessageLengthRun();
|
||||
break;
|
||||
case 1:
|
||||
await startNumWorkerRun();
|
||||
break;
|
||||
case 2:
|
||||
await startDifficultyRun();
|
||||
break;
|
||||
case 3:
|
||||
await starTTLRun();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
// For reference: https://github.com/airbnb/javascript
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
mocha: true,
|
||||
browser: true,
|
||||
},
|
||||
|
||||
globals: {
|
||||
check: true,
|
||||
gen: true,
|
||||
},
|
||||
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
|
||||
rules: {
|
||||
// We still get the value of this rule, it just allows for dev deps
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
},
|
||||
],
|
||||
|
||||
// We want to keep each test structured the same, even if its contents are tiny
|
||||
'arrow-body-style': 'off',
|
||||
},
|
||||
};
|
@ -1,99 +0,0 @@
|
||||
/* global assert, JSBI, pow */
|
||||
|
||||
const { calcTarget, incrementNonce, bufferToBase64, bigIntToUint8Array, greaterThan } = pow;
|
||||
|
||||
describe('Proof of Work', () => {
|
||||
describe('#incrementNonce', () => {
|
||||
it('should increment a Uint8Array nonce correctly', () => {
|
||||
const arr1Before = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
const arr1After = incrementNonce(arr1Before);
|
||||
assert.strictEqual(arr1After[0], 0);
|
||||
assert.strictEqual(arr1After[1], 0);
|
||||
assert.strictEqual(arr1After[2], 0);
|
||||
assert.strictEqual(arr1After[3], 0);
|
||||
assert.strictEqual(arr1After[4], 0);
|
||||
assert.strictEqual(arr1After[5], 0);
|
||||
assert.strictEqual(arr1After[6], 0);
|
||||
assert.strictEqual(arr1After[7], 1);
|
||||
});
|
||||
|
||||
it('should increment a Uint8Array nonce correctly in a loop', () => {
|
||||
let arr = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
assert.deepEqual(incrementNonce(arr), new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1]));
|
||||
arr = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
for (let i = 0; i <= 255; i += 1) {
|
||||
arr = incrementNonce(arr);
|
||||
}
|
||||
assert.deepEqual(arr, new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]));
|
||||
arr = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]);
|
||||
assert.deepEqual(incrementNonce(arr), new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#calcTarget', () => {
|
||||
it('should calculate a correct difficulty target', () => {
|
||||
// These values will need to be updated if we adjust the difficulty settings
|
||||
let payloadLen = 625;
|
||||
const ttl = 86400000;
|
||||
let expectedTarget = new Uint8Array([0, 4, 119, 164, 35, 224, 222, 64]);
|
||||
|
||||
let actualTarget = calcTarget(ttl, payloadLen, 10);
|
||||
assert.deepEqual(actualTarget, expectedTarget);
|
||||
payloadLen = 6597;
|
||||
expectedTarget = new Uint8Array([0, 0, 109, 145, 174, 146, 124, 3]);
|
||||
actualTarget = calcTarget(ttl, payloadLen, 10);
|
||||
assert.deepEqual(actualTarget, expectedTarget);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#greaterThan', () => {
|
||||
it('should correclty compare two Uint8Arrays', () => {
|
||||
let arr1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
let arr2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
assert.isFalse(greaterThan(arr1, arr2));
|
||||
arr1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
|
||||
arr2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
assert.isTrue(greaterThan(arr1, arr2));
|
||||
arr1 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255]);
|
||||
arr2 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 254]);
|
||||
assert.isTrue(greaterThan(arr1, arr2));
|
||||
arr1 = new Uint8Array([254, 255, 255, 255, 255, 255, 255, 255, 255, 255]);
|
||||
arr2 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255]);
|
||||
assert.isFalse(greaterThan(arr1, arr2));
|
||||
arr1 = new Uint8Array([0]);
|
||||
arr2 = new Uint8Array([0, 0]);
|
||||
assert.isFalse(greaterThan(arr1, arr2));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#bufferToBase64', () => {
|
||||
it('should correclty convert a Uint8Array to a base64 string', () => {
|
||||
let arr = new Uint8Array([1, 2, 3]);
|
||||
let expected = 'AQID';
|
||||
assert.strictEqual(bufferToBase64(arr), expected);
|
||||
arr = new Uint8Array([123, 25, 3, 121, 45, 87, 24, 111]);
|
||||
expected = 'exkDeS1XGG8=';
|
||||
assert.strictEqual(bufferToBase64(arr), expected);
|
||||
arr = new Uint8Array([]);
|
||||
expected = '';
|
||||
assert.strictEqual(bufferToBase64(arr), expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#bigIntToUint8Array', () => {
|
||||
it('should correclty convert a BigInteger to a Uint8Array', () => {
|
||||
let bigInt = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
|
||||
let expected = new Uint8Array([0, 31, 255, 255, 255, 255, 255, 255]);
|
||||
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
|
||||
bigInt = JSBI.BigInt('0');
|
||||
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
|
||||
bigInt = JSBI.BigInt('255');
|
||||
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 255]);
|
||||
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
|
||||
bigInt = JSBI.BigInt('256');
|
||||
expected = new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]);
|
||||
assert.deepEqual(bigIntToUint8Array(bigInt), expected);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,66 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>pow calculations</title>
|
||||
<style>
|
||||
input[type=number] {
|
||||
width: 50px;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Influence of message length</h2>
|
||||
<label for="iteration0">Iterations:</label><input name="iteration0" id ="iteration0" type="number" value="20"/>
|
||||
<label for="nonceTrials0">Nonce Trials:</label><input name="nonceTrials0" id ="nonceTrials0" type="number" value="100"/>
|
||||
<label for="numWorkers0">Number of workers:</label><input name="numWorkers0" id ="numWorkers0" type="number" value="1"/>
|
||||
<label for="TTL0">TTL:</label><input name="TTL0" id ="TTL0" type="number" value="72"/>
|
||||
<label for="messageLengthStart0">Message length start:</label><input name="messageLengthStart0" id ="messageLengthStart0" type="number" value="50"/>
|
||||
<label for="messageLengthStop0">Message length stop:</label><input name="messageLengthStop0" id ="messageLengthStop0" type="number" value="1000"/>
|
||||
<label for="messageLengthStep0">Message length step:</label><input name="messageLengthStep0" id ="messageLengthStep0" type="number" value="100"/>
|
||||
<br>
|
||||
<button onclick="start(0)">Run</button>
|
||||
<div id="plotly0"></div>
|
||||
|
||||
<h2>Influence of workers</h2>
|
||||
<label for="iteration1">Iterations:</label><input name="iteration1" id ="iteration1" type="number" value="20"/>
|
||||
<label for="nonceTrials1">Nonce Trials:</label><input name="nonceTrials1" id ="nonceTrials1" type="number" value="100"/>
|
||||
<label for="TTL1">TTL:</label><input name="TTL1" id ="TTL1" type="number" value="72"/>
|
||||
<label for="numWorkersStart1">Number of workers start:</label><input name="numWorkersStart1" id ="numWorkersStart1" type="number" value="1"/>
|
||||
<label for="numWorkersEnd1">Number of workers end:</label><input name="numWorkersEnd1" id ="numWorkersEnd1" type="number" value="4"/>
|
||||
<label for="messageLength1">Message length stop:</label><input name="messageLength1" id ="messageLength1" type="number" value="100"/>
|
||||
<br>
|
||||
<button onclick="start(1)">Run</button>
|
||||
<div id="plotly1"></div>
|
||||
|
||||
<h2>Influence of NonceTrials</h2>
|
||||
<label for="iteration2">Iterations:</label><input name="iteration2" id ="iteration2" type="number" value="20"/>
|
||||
<label for="messageLength2">Message length:</label><input name="messageLength2" id ="messageLength2" type="number" value="100"/>
|
||||
<label for="numWorkers2">Number of workers:</label><input name="numWorkers2" id ="numWorkers2" type="number" value="1"/>
|
||||
<label for="TTL2">TTL:</label><input name="TTL2" id ="TTL2" type="number" value="72"/>
|
||||
<label for="nonceTrialsStart2">Nonce Trials start:</label><input name="nonceTrialsStart2" id ="nonceTrialsStart2" type="number" value="10"/>
|
||||
<label for="nonceTrialsStop2">Nonce Trials stop:</label><input name="nonceTrialsStop2" id ="nonceTrialsStop2" type="number" value="100"/>
|
||||
<label for="nonceTrialsStep2">Nonce Trials step:</label><input name="nonceTrialsStep2" id ="nonceTrialsStep2" type="number" value="10"/>
|
||||
<br>
|
||||
<button onclick="start(2)">Run</button>
|
||||
<div id="plotly2"></div>
|
||||
|
||||
<h2>Influence of TTL</h2>
|
||||
<label for="iteration3">Iterations:</label><input name="iteration3" id ="iteration3" type="number" value="20"/>
|
||||
<label for="nonceTrials3">Nonce Trials:</label><input name="nonceTrials3" id ="nonceTrials3" type="number" value="100"/>
|
||||
<label for="messageLength3">Message length:</label><input name="messageLength3" id ="messageLength3" type="number" value="100"/>
|
||||
<label for="numWorkers3">Number of workers:</label><input name="numWorkers3" id ="numWorkers3" type="number" value="1"/>
|
||||
<label for="TTLStart3">TTL start:</label><input name="TTLStart3" id ="TTLStart3" type="number" value="12"/>
|
||||
<label for="TTLStop3">TTL stop:</label><input name="TTLStop3" id ="TTLStop3" type="number" value="96"/>
|
||||
<label for="TTLStep3">TTL step:</label><input name="TTLStep3" id ="TTLStep3" type="number" value="12"/>
|
||||
<br>
|
||||
<button onclick="start(3)">Run</button>
|
||||
<div id="plotly3"></div>
|
||||
|
||||
<script type="text/javascript" src="libloki/test/components.js"></script>
|
||||
<script type="text/javascript" src="libloki/proof-of-work.js"></script>
|
||||
|
||||
<script type="text/javascript" src="libloki/test/metrics.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,65 +0,0 @@
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
// you can pass the parameter in the command line. e.g. node static_server.js 3000
|
||||
const port = process.argv[3] || 9000;
|
||||
const hostname = process.argv[2] || 'localhost';
|
||||
// maps file extention to MIME types
|
||||
const mimeType = {
|
||||
'.ico': 'image/x-icon',
|
||||
'.html': 'text/html',
|
||||
'.js': 'text/javascript',
|
||||
'.json': 'application/json',
|
||||
'.css': 'text/css',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.wav': 'audio/wav',
|
||||
'.mp3': 'audio/mpeg',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.pdf': 'application/pdf',
|
||||
'.doc': 'application/msword',
|
||||
'.eot': 'appliaction/vnd.ms-fontobject',
|
||||
'.ttf': 'aplication/font-sfnt',
|
||||
};
|
||||
http
|
||||
.createServer((req, res) => {
|
||||
// console.log(`${req.method} ${req.url}`);
|
||||
// parse URL
|
||||
const parsedUrl = url.parse(req.url);
|
||||
// extract URL path
|
||||
// Avoid https://en.wikipedia.org/wiki/Directory_traversal_attack
|
||||
// e.g curl --path-as-is http://localhost:9000/../fileInDanger.txt
|
||||
// by limiting the path to current directory only
|
||||
const sanitizePath = path.normalize(parsedUrl.pathname).replace(/^(\.\.[/\\])+/, '');
|
||||
let pathname = path.join(__dirname, sanitizePath);
|
||||
fs.exists(pathname, exist => {
|
||||
if (!exist) {
|
||||
// if the file is not found, return 404
|
||||
res.statusCode = 404;
|
||||
res.end(`File ${pathname} not found!`);
|
||||
return;
|
||||
}
|
||||
// if is a directory, then look for index.html
|
||||
if (fs.statSync(pathname).isDirectory()) {
|
||||
pathname += '/index.html';
|
||||
}
|
||||
// read file from file system
|
||||
fs.readFile(pathname, (err, data) => {
|
||||
if (err) {
|
||||
res.statusCode = 500;
|
||||
res.end(`Error getting the file: ${err}.`);
|
||||
} else {
|
||||
// based on the URL path, extract the file extention. e.g. .js, .doc, ...
|
||||
const { ext } = path.parse(pathname);
|
||||
// if the file is found, set Content-type and send data
|
||||
res.setHeader('Content-type', mimeType[ext] || 'text/plain');
|
||||
res.end(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.listen(parseInt(port, 10), hostname);
|
||||
|
||||
// eslint-disable-next-line
|
||||
console.log(`metrics running on http://${hostname}:${port}/metrics.html`);
|
@ -0,0 +1,81 @@
|
||||
import _ from 'lodash';
|
||||
import { Snode } from '../onions';
|
||||
import { SendParams, storeOnNode } from '../snode_api/serviceNodeAPI';
|
||||
import { getSnodesFor } from '../snode_api/snodePool';
|
||||
import { firstTrue } from '../utils/Promise';
|
||||
|
||||
const DEFAULT_CONNECTIONS = 3;
|
||||
|
||||
async function openSendConnection(snode: Snode, params: SendParams) {
|
||||
// TODO: Revert back to using snode address instead of IP
|
||||
const successfulSend = await storeOnNode(snode, params);
|
||||
if (successfulSend) {
|
||||
return snode;
|
||||
}
|
||||
// should we mark snode as bad if it can't store our message?
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refactor note: We should really clean this up ... it's very messy
|
||||
*
|
||||
* We need to split it into 2 sends:
|
||||
* - Snodes
|
||||
* - Open Groups
|
||||
*
|
||||
* Mikunj:
|
||||
* Temporarily i've made it so `MessageSender` handles open group sends and calls this function for regular sends.
|
||||
*/
|
||||
|
||||
export async function sendMessage(
|
||||
pubKey: string,
|
||||
data: Uint8Array,
|
||||
messageTimeStamp: number,
|
||||
ttl: number,
|
||||
options: {
|
||||
isPublic?: boolean;
|
||||
} = {}
|
||||
): Promise<void> {
|
||||
const { isPublic = false } = options;
|
||||
|
||||
if (isPublic) {
|
||||
window.log.warn('this sendMessage() should not be called anymore with an open group message');
|
||||
return;
|
||||
}
|
||||
|
||||
const data64 = window.dcodeIO.ByteBuffer.wrap(data).toString('base64');
|
||||
|
||||
// Using timestamp as a unique identifier
|
||||
const swarm = await getSnodesFor(pubKey);
|
||||
|
||||
// send parameters
|
||||
const params = {
|
||||
pubKey,
|
||||
ttl: ttl.toString(),
|
||||
timestamp: messageTimeStamp.toString(),
|
||||
data: data64,
|
||||
};
|
||||
|
||||
const usedNodes = _.slice(swarm, 0, DEFAULT_CONNECTIONS);
|
||||
|
||||
const promises = usedNodes.map(snodeConnection => openSendConnection(snodeConnection, params));
|
||||
|
||||
let snode;
|
||||
try {
|
||||
// eslint-disable-next-line more/no-then
|
||||
snode = await firstTrue(promises);
|
||||
} catch (e) {
|
||||
const snodeStr = snode ? `${snode.ip}:${snode.port}` : 'null';
|
||||
window.log.warn(
|
||||
`loki_message:::sendMessage - ${e.code} ${e.message} to ${pubKey} via snode:${snodeStr}`
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
if (!snode) {
|
||||
throw new window.textsecure.EmptySwarmError(pubKey, 'Ran out of swarm nodes to query');
|
||||
} else {
|
||||
window.log.info(
|
||||
`loki_message:::sendMessage - Successfully stored message to ${pubKey} via ${snode.ip}:${snode.port}`
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// TS 3.8 supports export * as X from 'Y'
|
||||
import * as MessageSender from './MessageSender';
|
||||
export { MessageSender };
|
||||
import * as LokiMessageApi from './LokiMessageApi';
|
||||
export { MessageSender, LokiMessageApi };
|
||||
|
||||
export * from './PendingMessageCache';
|
||||
export * from './MessageQueue';
|
||||
|
Loading…
Reference in New Issue