remove pow

pull/1576/head
Audric Ackermann 4 years ago
parent 8444c7cbfc
commit 0c0da48150
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -44,7 +44,6 @@ module.exports = grunt => {
src: [
'node_modules/bytebuffer/dist/bytebuffer.js',
'components/JSBI/dist/jsbi.mjs',
'libloki/proof-of-work.js',
'node_modules/long/dist/long.js',
'js/util_worker_tasks.js',
],
@ -124,7 +123,6 @@ module.exports = grunt => {
files: [
'node_modules/bytebuffer/dist/bytebuffer.js',
'components/JSBI/dist/jsbi.mjs',
'libloki/proof-of-work.js',
'node_modules/long/dist/long.js',
'js/util_worker_tasks.js',
],
@ -359,13 +357,6 @@ module.exports = grunt => {
}
);
grunt.registerTask('loki-unit-tests', 'Run loki unit tests w/Electron', function thisNeeded() {
const environment = grunt.option('env') || 'test-loki';
const done = this.async();
runTests(environment, done);
});
grunt.registerMultiTask('test-release', 'Test packaged releases', function thisNeeded() {
const dir = grunt.option('dir') || 'release';
const environment = grunt.option('env') || 'production';
@ -449,7 +440,7 @@ module.exports = grunt => {
grunt.registerTask('tx', ['exec:tx-pull-new', 'exec:tx-pull', 'locale-patch']);
grunt.registerTask('dev', ['default', 'watch']);
grunt.registerTask('test', ['unit-tests', 'lib-unit-tests', 'loki-unit-tests']);
grunt.registerTask('test', ['unit-tests', 'lib-unit-tests']);
grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
grunt.registerTask('default', [
'exec:build-protobuf',

@ -3,7 +3,6 @@
"localUrl": "localhost.loki",
"cdnUrl": "random.snode",
"contentProxyUrl": "",
"defaultPoWDifficulty": "1",
"seedNodeList": [
{
"ip_url": "http://116.203.53.213:4433/",

@ -119,7 +119,6 @@
return;
}
const ourKey = libsession.Utils.UserUtils.getOurPubKeyStrFromCache();
window.lokiMessageAPI = new window.LokiMessageAPI();
// singleton to relay events to libtextsecure/message_receiver
window.lokiPublicChatAPI = new window.LokiPublicChatAPI(ourKey);
@ -161,11 +160,6 @@
// Update zoom
window.updateZoomFactor();
const currentPoWDifficulty = storage.get('PoWDifficulty', null);
if (!currentPoWDifficulty) {
storage.put('PoWDifficulty', window.getDefaultPoWDifficulty());
}
// Ensure accounts created prior to 1.0.0-beta8 do have their
// 'primaryDevicePubKey' defined.
if (Whisper.Registration.isDone() && !storage.get('primaryDevicePubKey', null)) {

@ -103,42 +103,4 @@
// yes we know
cb(expiredVersion);
};
const getServerTime = async () => {
let timestamp = NaN;
try {
const res = await window.tokenlessFileServerAdnAPI.serverRequest('loki/v1/time');
if (res.ok) {
timestamp = res.response;
}
} catch (e) {
return timestamp;
}
return Number(timestamp);
};
const getTimeDifferential = async () => {
// Get time differential between server and client in seconds
const serverTime = await getServerTime();
const clientTime = Math.ceil(Date.now() / 1000);
if (Number.isNaN(serverTime)) {
log.error('expire:::getTimeDifferential - serverTime is not valid');
return 0;
}
return serverTime - clientTime;
};
// require for PoW to work
window.setClockParams = async () => {
// Set server-client time difference
const maxTimeDifferential = 30 + 15; // + 15 for onion requests
const timeDifferential = await getTimeDifferential();
log.info('expire:::setClockParams - Clock difference', timeDifferential);
window.clientClockSynced = Math.abs(timeDifferential) < maxTimeDifferential;
return window.clientClockSynced;
};
})();

@ -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,9 +1,8 @@
/* global dcodeIO, pow */
/* global dcodeIO */
/* eslint-disable strict */
const functions = {
arrayBufferToStringBase64,
calcPoW,
};
onmessage = async e => {
@ -37,14 +36,3 @@ function prepareErrorForPostMessage(error) {
function arrayBufferToStringBase64(arrayBuffer) {
return dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');
}
function calcPoW(
timestamp,
ttl,
pubKey,
data,
difficulty = undefined,
increment = 1,
startNonce = 0
) {
return pow.calcPoW(timestamp, ttl, pubKey, data, difficulty, increment, startNonce);
}

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

@ -114,19 +114,6 @@
}
}
function WrongDifficultyError(newDifficulty) {
this.name = 'WrongDifficultyError';
this.newDifficulty = newDifficulty;
Error.call(this, this.name);
// Maintains proper stack trace, where our error was thrown (only available on V8)
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
if (Error.captureStackTrace) {
Error.captureStackTrace(this);
}
}
function TimestampError(message) {
this.name = 'TimeStampError';
@ -156,7 +143,6 @@
window.textsecure.HTTPError = HTTPError;
window.textsecure.NotFoundError = NotFoundError;
window.textsecure.WrongSwarmError = WrongSwarmError;
window.textsecure.WrongDifficultyError = WrongDifficultyError;
window.textsecure.TimestampError = TimestampError;
window.textsecure.PublicChatError = PublicChatError;
})();

@ -11,7 +11,6 @@ export interface LibTextsecure {
HTTPError: any;
NotFoundError: any;
WrongSwarmError: any;
WrongDifficultyError: any;
TimestampError: any;
PublicChatError: any;
createTaskWithTimeout(task: any, id: any, options?: any): Promise<any>;

@ -169,7 +169,6 @@ function prepareURL(pathSegments, moreKeys) {
serverUrl: config.get('serverUrl'),
localUrl: config.get('localUrl'),
cdnUrl: config.get('cdnUrl'),
defaultPoWDifficulty: config.get('defaultPoWDifficulty'),
// one day explain why we need to do this - neuroscr
seedNodeList: JSON.stringify(config.get('seedNodeList')),
environment: config.environment,
@ -354,8 +353,6 @@ async function createWindow() {
mainWindow.loadURL(prepareURL([__dirname, 'test', 'index.html']));
} else if (config.environment === 'test-lib') {
mainWindow.loadURL(prepareURL([__dirname, 'libtextsecure', 'test', 'index.html']));
} else if (config.environment === 'test-loki') {
mainWindow.loadURL(prepareURL([__dirname, 'libloki', 'test', 'index.html']));
} else if (config.environment.includes('test-integration')) {
mainWindow.loadURL(prepareURL([__dirname, 'background_test.html']));
} else {
@ -381,7 +378,6 @@ async function createWindow() {
if (
config.environment === 'test' ||
config.environment === 'test-lib' ||
config.environment === 'test-loki' ||
config.environment.includes('test-integration') ||
(mainWindow.readyForShutdown && windowState.shouldQuit())
) {
@ -639,7 +635,6 @@ app.on('ready', async () => {
if (
process.env.NODE_ENV !== 'test' &&
process.env.NODE_ENV !== 'test-lib' &&
process.env.NODE_ENV !== 'test-loki' &&
!process.env.NODE_ENV.includes('test-integration')
) {
installFileHandler({
@ -811,7 +806,6 @@ app.on('window-all-closed', () => {
process.platform !== 'darwin' ||
config.environment === 'test' ||
config.environment === 'test-lib' ||
config.environment === 'test-loki' ||
config.environment.includes('test-integration')
) {
app.quit();

@ -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`);

@ -32,10 +32,9 @@
"test": "yarn test-node && yarn test-electron",
"test-view": "NODE_ENV=test yarn run start",
"test-lib-view": "NODE_ENV=test-lib yarn run start",
"test-loki-view": "NODE_ENV=test-loki yarn run start",
"test-electron": "yarn grunt test",
"test-integration": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --full-trace --timeout 10000 ts/test/session/integration/integration_itest.js",
"test-node": "mocha --recursive --exit --timeout 10000 test/app test/modules \"./ts/test/**/*_test.js\" libloki/test/node ",
"test-node": "mocha --recursive --exit --timeout 10000 test/app test/modules \"./ts/test/**/*_test.js\" ",
"eslint-full": "eslint . --cache",
"lint-full": "yarn format-full && yarn lint-files-full",
"lint-files-full": "yarn eslint-full && yarn tslint",
@ -44,7 +43,6 @@
"transpile": "tsc --incremental",
"transpile:watch": "tsc -w",
"clean-transpile": "rimraf ts/**/*.js && rimraf ts/*.js && rimraf tsconfig.tsbuildinfo;",
"pow-metrics": "node metrics_app.js localhost 9000",
"ready": "yarn clean-transpile; yarn grunt && yarn lint-full && yarn test"
},
"dependencies": {

@ -35,7 +35,6 @@ window.displayNameRegex = /[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-
window.semver = semver;
window.platform = process.platform;
window.getDefaultPoWDifficulty = () => config.defaultPoWDifficulty;
window.getTitle = () => title;
window.getEnvironment = () => config.environment;
window.isDev = () => config.environment === 'development';
@ -321,14 +320,9 @@ window.Signal = Signal.setup({
});
if (process.env.USE_STUBBED_NETWORK) {
const StubMessageAPI = require('./ts/test/session/integration/stubs/stub_message_api');
window.LokiMessageAPI = StubMessageAPI;
const StubAppDotNetAPI = require('./ts/test/session/integration/stubs/stub_app_dot_net_api');
window.LokiAppDotNetServerAPI = StubAppDotNetAPI;
} else {
window.LokiMessageAPI = require('./js/modules/loki_message_api');
window.LokiAppDotNetServerAPI = require('./js/modules/loki_app_dot_net_api');
}
window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api');

@ -142,7 +142,6 @@ export const ActionsPanel = () => {
// this maxi useEffect is called only once: when the component is mounted.
// For the action panel, it means this is called only one per app start/with a user loggedin
useEffect(() => {
void window.setClockParams();
if (window.lokiFeatureFlags.useOnionRequests || window.lokiFeatureFlags.useFileOnionRequests) {
// Initialize paths for onion requests
void OnionPaths.getInstance().buildNewOnionPaths();

@ -88,15 +88,6 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
this.closeOverlay = this.closeOverlay.bind(this);
}
public componentDidMount() {
window.Whisper.events.on('calculatingPoW', this.closeOverlay);
}
public componentWillUnmount() {
this.updateSearch('');
window.Whisper.events.off('calculatingPoW', this.closeOverlay);
}
public renderRow = ({ index, key, style }: RowRendererParamsType): JSX.Element => {
const { conversations, openConversationExternal } = this.props;

@ -757,19 +757,6 @@ export class SessionCompositionBox extends React.Component<Props, State> {
ToastUtils.pushMessageBodyMissing();
return;
}
if (!window.clientClockSynced) {
let clockSynced = false;
if (window.setClockParams) {
// Check to see if user has updated their clock to current time
clockSynced = await window.setClockParams();
} else {
window.log.info('setClockParams not loaded yet');
}
if (clockSynced) {
ToastUtils.pushClockOutOfSync();
return;
}
}
if (!isPrivate && left) {
ToastUtils.pushYouLeftTheGroup();

@ -36,7 +36,7 @@ export async function copyPublicKey(convoId: string) {
ToastUtils.pushCopiedToClipBoard();
return;
}
window.log.warn('coy to pubkey no roomInfo');
window.log.warn('copy to pubkey no roomInfo');
return;
}

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

@ -7,24 +7,16 @@ import { MessageEncrypter } from '../crypto';
import pRetry from 'p-retry';
import { PubKey } from '../types';
import { UserUtils } from '../utils';
import { VisibleMessage } from '../messages/outgoing/visibleMessage/VisibleMessage';
import { OpenGroupRequestCommonType } from '../../opengroup/opengroupV2/ApiUtil';
import { postMessage } from '../../opengroup/opengroupV2/OpenGroupAPIV2';
import { OpenGroupMessageV2 } from '../../opengroup/opengroupV2/OpenGroupMessageV2';
import { padPlainTextBuffer } from '../crypto/MessageEncrypter';
import { fromUInt8ArrayToBase64 } from '../utils/String';
import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
import * as LokiMessageApi from './LokiMessageApi';
// ================ Regular ================
/**
* Check if we can send to service nodes.
*/
export function canSendToSnode(): boolean {
// Seems like lokiMessageAPI is not always guaranteed to be initialized
return Boolean(window.lokiMessageAPI);
}
/**
* Send a message via service nodes.
*
@ -36,10 +28,6 @@ export async function send(
attempts: number = 3,
retryMinTimeout?: number // in ms
): Promise<Uint8Array> {
if (!canSendToSnode()) {
throw new Error('lokiMessageAPI is not initialized.');
}
const device = PubKey.cast(message.device);
const { plainTextBuffer, encryption, timestamp, ttl } = message;
const { envelopeType, cipherText } = await MessageEncrypter.encrypt(
@ -53,7 +41,7 @@ export async function send(
return pRetry(
async () => {
await window.lokiMessageAPI.sendMessage(device.key, data, timestamp, ttl);
await LokiMessageApi.sendMessage(device.key, data, timestamp, ttl);
return data;
},
{

@ -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';

@ -13,12 +13,11 @@ const { remote } = Electron;
import { snodeRpc } from './lokiRpc';
import { sendOnionRequestLsrpcDest, snodeHttpsAgent, SnodeResponse } from './onions';
import { sleepFor } from '../../../js/modules/loki_primitives';
export { sendOnionRequestLsrpcDest };
import { getRandomSnodeAddress, markNodeUnreachable, Snode, updateSnodesFor } from './snodePool';
import { Constants } from '..';
import { sleepFor } from '../utils/Promise';
/**
* Currently unused. If we need it again, be sure to update it to onion routing rather
@ -231,13 +230,12 @@ export async function getSnodesFromSeedUrl(urlObj: URL): Promise<Array<any>> {
}
}
interface SendParams {
export type SendParams = {
pubKey: string;
ttl: string;
nonce: string;
timestamp: string;
data: string;
}
};
// get snodes for pubkey from random snode. Uses an existing snode
export async function requestSnodesForPubkey(pubKey: string): Promise<Array<Snode>> {
@ -333,12 +331,6 @@ function checkResponse(response: SnodeResponse): void {
const newSwarm = json.snodes ? json.snodes : [];
throw new textsecure.WrongSwarmError(newSwarm);
}
// Wrong PoW difficulty
if (response.status === 432) {
log.error('Wrong POW', json);
throw new textsecure.WrongDifficultyError(json.difficulty);
}
}
export async function storeOnNode(targetNode: Snode, params: SendParams): Promise<boolean> {
@ -355,10 +347,7 @@ export async function storeOnNode(targetNode: Snode, params: SendParams): Promis
await sleepFor(successiveFailures * 500);
try {
const result = await snodeRpc('store', params, targetNode);
// succcessful messages should look like
// `{\"difficulty\":1}`
// but so does invalid pow, so be careful!
console.warn('snode storeOnNode result', result);
// do not return true if we get false here...
if (result === false) {
@ -379,12 +368,6 @@ export async function storeOnNode(targetNode: Snode, params: SendParams): Promis
return false;
}
const json = JSON.parse(snodeRes.body);
// Make sure we aren't doing too much PoW
const currentDifficulty = window.storage.get('PoWDifficulty', null);
if (json && json.difficulty && json.difficulty !== parseInt(currentDifficulty, 10)) {
window.storage.put('PoWDifficulty', json.difficulty);
}
return true;
} catch (e) {
log.warn(
@ -397,19 +380,6 @@ export async function storeOnNode(targetNode: Snode, params: SendParams): Promis
const { newSwarm } = e;
await updateSnodesFor(params.pubKey, newSwarm);
return false;
} else if (e instanceof textsecure.WrongDifficultyError) {
const { newDifficulty } = e;
// difficulty of 100 happens when a snode restarts. We have to exit the loop and markNodeUnreachable()
if (newDifficulty === 100) {
log.warn('loki_message:::storeOnNode - invalid new difficulty:100. Marking node as bad.');
successiveFailures = MAX_ACCEPTABLE_FAILURES;
continue;
}
if (!Number.isNaN(newDifficulty)) {
window.storage.put('PoWDifficulty', newDifficulty);
}
throw e;
} else if (e instanceof textsecure.NotFoundError) {
// TODO: Handle resolution error
} else if (e instanceof textsecure.TimestampError) {
@ -452,8 +422,7 @@ export async function retrieveNextMessages(
const res = result as SnodeResponse;
// NOTE: Retrieve cannot result in "wrong POW", but we call
// `checkResponse` to check for "wrong swarm"
// NOTE: we call `checkResponse` to check for "wrong swarm"
try {
checkResponse(res);
} catch (e) {

@ -1,8 +1,6 @@
import semver from 'semver';
import _ from 'lodash';
import { abortableIterator } from '../../../js/modules/loki_primitives';
import { getSnodesFromSeedUrl, requestSnodesForPubkey } from './serviceNodeAPI';
import { getSwarmNodesForPubkey, updateSwarmNodesForPubkey } from '../../../ts/data/data';
@ -22,7 +20,6 @@ export interface Snode {
// This should be renamed to `allNodes` or something
let randomSnodePool: Array<Snode> = [];
let stopGetAllVersionPromiseControl: any = false;
// We only store nodes' identifiers here,
const nodesForPubkey: Map<string, Array<SnodeEdKey>> = new Map();
@ -164,45 +161,6 @@ export function getNodesMinVersion(minVersion: string): Array<Snode> {
return randomSnodePool.filter((node: any) => node.version && semver.gt(node.version, minVersion));
}
/**
* Currently unused as it makes call over insecure node fetch and we don't need
* to filter out nodes by versions anymore.
*
* now get version for all snodes
* also acts an early online test/purge of bad nodes
*/
export async function getAllVersionsForRandomSnodePool(): Promise<void> {
const { log } = window;
// let count = 0;
// const verionStart = Date.now();
// const total = this.randomSnodePool.length;
// const noticeEvery = parseInt(total / 10, 10);
const loop = abortableIterator(randomSnodePool, async (node: any) => {
try {
await requestVersion(node);
} catch (e) {
log.error('LokiSnodeAPI::_getAllVersionsForRandomSnodePool - error', e.code, e.message);
throw e;
}
});
// make abortable accessible outside this scope
stopGetAllVersionPromiseControl = loop.stop;
await loop.start(true);
stopGetAllVersionPromiseControl = false; // clear lock
// an array of objects
const versions = randomSnodePool.reduce((curVal: any, node: any) => {
if (curVal.indexOf(node.version) === -1) {
curVal.push(node.version);
}
return curVal;
}, []);
log.debug(
`LokiSnodeAPI::_getAllVersionsForRandomSnodePool - ${versions.length} versions retrieved from network!:`,
versions.join(',')
);
}
async function getSnodeListFromLokidSeednode(
seedNodes = window.seedNodeList,
retries = 0
@ -242,11 +200,6 @@ async function getSnodeListFromLokidSeednode(
async function refreshRandomPoolDetail(seedNodes: Array<any>): Promise<void> {
const { log } = window;
// are we running any _getAllVersionsForRandomSnodePool
if (stopGetAllVersionPromiseControl !== false) {
// we are, stop them
stopGetAllVersionPromiseControl();
}
let snodes = [];
try {
snodes = await getSnodeListFromLokidSeednode(seedNodes);
@ -266,9 +219,6 @@ async function refreshRandomPoolDetail(seedNodes: Array<any>): Promise<void> {
randomSnodePool.length,
'snodes'
);
// Warning: the call below will call getVersions to all existing nodes.
// And not with onion routing
// void getAllVersionsForRandomSnodePool();
} catch (e) {
log.warn('LokiSnodeAPI::refreshRandomPool - error', e.code, e.message);
/*

@ -1,3 +1,5 @@
import { Snode } from '../onions';
type SimpleFunction<T> = (arg: T) => void;
type Return<T> = Promise<T> | T;
@ -15,6 +17,7 @@ export class TaskTimedOutError extends Error {
// one action resolves all
const snodeGlobalLocks: Record<string, Promise<any>> = {};
export async function allowOnlyOneAtATime(
name: string,
process: () => Promise<any>,
@ -28,7 +31,7 @@ export async function allowOnlyOneAtATime(
let timeoutTimer = null;
if (timeoutMs) {
timeoutTimer = setTimeout(() => {
window.log.warn(`loki_primitives:::allowOnlyOneAtATime - TIMEDOUT after ${timeoutMs}s`);
window.log.warn(`allowOnlyOneAtATime - TIMEDOUT after ${timeoutMs}s`);
// tslint:disable-next-line: no-dynamic-delete
delete snodeGlobalLocks[name]; // clear lock
reject();
@ -40,9 +43,9 @@ export async function allowOnlyOneAtATime(
innerRetVal = await process();
} catch (e) {
if (typeof e === 'string') {
window.log.error(`loki_primitives:::allowOnlyOneAtATime - error ${e}`);
window.log.error(`allowOnlyOneAtATime - error ${e}`);
} else {
window.log.error(`loki_primitives:::allowOnlyOneAtATime - error ${e.code} ${e.message}`);
window.log.error(`allowOnlyOneAtATime - error ${e.code} ${e.message}`);
}
// clear timeout timer
@ -68,7 +71,6 @@ export async function allowOnlyOneAtATime(
// release the kraken
resolve(innerRetVal);
});
} else {
}
return snodeGlobalLocks[name];
}
@ -201,3 +203,26 @@ export async function delay(timeoutMs: number = 2000): Promise<Boolean> {
}, timeoutMs);
});
}
// tslint:disable: no-string-based-set-timeout
export const sleepFor = async (ms: number) =>
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
export const firstTrue = async (ps: Array<Promise<any>>) => {
const newPs = ps.map(
async p =>
new Promise(
// eslint-disable more/no-then
// tslint:disable: no-void-expression
(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) as Promise<Snode>;
};

@ -163,10 +163,6 @@ export function pushUnblockToSendGroup() {
pushToastInfo('unblockGroupToSend', window.i18n('unblockGroupToSend'));
}
export function pushClockOutOfSync() {
pushToastError('clockOutOfSync', window.i18n('clockOutOfSync'));
}
export function pushYouLeftTheGroup() {
pushToastError('youLeftTheGroup', window.i18n('youLeftTheGroup'));
}

@ -2,8 +2,7 @@ import { expect } from 'chai';
import * as crypto from 'crypto';
import * as sinon from 'sinon';
import { toNumber } from 'lodash';
import { MessageSender } from '../../../../session/sending';
import LokiMessageAPI from '../../../../../js/modules/loki_message_api';
import { LokiMessageApi, MessageSender } from '../../../../session/sending';
import { TestUtils } from '../../../test-utils';
import { MessageEncrypter } from '../../../../session/crypto';
import { SignalService } from '../../../../protobuf';
@ -20,33 +19,14 @@ describe('MessageSender', () => {
TestUtils.restoreStubs();
});
describe('canSendToSnode', () => {
it('should return the correct value', () => {
const stub = TestUtils.stubWindow('lokiMessageAPI', undefined);
expect(MessageSender.canSendToSnode()).to.equal(
false,
'We cannot send if lokiMessageAPI is not set'
);
stub.set(sandbox.createStubInstance(LokiMessageAPI));
expect(MessageSender.canSendToSnode()).to.equal(true, 'We can send if lokiMessageAPI is set');
});
});
// tslint:disable-next-line: max-func-body-length
describe('send', () => {
const ourNumber = '0123456789abcdef';
let lokiMessageAPISendStub: sinon.SinonStub<
[string, Uint8Array, number, number],
Promise<void>
>;
let lokiMessageAPISendStub: sinon.SinonStub<any>;
let encryptStub: sinon.SinonStub<[PubKey, Uint8Array, EncryptionType]>;
beforeEach(() => {
// We can do this because LokiMessageAPI has a module export in it
lokiMessageAPISendStub = sandbox.stub<[string, Uint8Array, number, number], Promise<void>>();
TestUtils.stubWindow('lokiMessageAPI', {
sendMessage: lokiMessageAPISendStub,
});
lokiMessageAPISendStub = sandbox.stub(LokiMessageApi, 'sendMessage').resolves();
encryptStub = sandbox.stub(MessageEncrypter, 'encrypt').resolves({
envelopeType: SignalService.Envelope.Type.UNIDENTIFIED_SENDER,

5
ts/window.d.ts vendored

@ -1,11 +1,9 @@
import { LocalizerType } from '../types/Util';
import { LokiMessageAPIInterface } from '../../js/modules/loki_message_api';
import { LibsignalProtocol } from '../../libtextsecure/libsignal-protocol';
import { SignalInterface } from '../../js/modules/signal';
import { Libloki } from '../libloki';
import { LokiPublicChatFactoryInterface } from '../js/modules/loki_public_chat_api';
import { LokiAppDotNetServerInterface } from '../js/modules/loki_app_dot_net_api';
import { LokiMessageInterface } from '../js/modules/loki_message_api';
import { SwarmPolling } from './session/snode_api/swarmPolling';
import { LibTextsecure } from '../libtextsecure';
@ -64,7 +62,6 @@ declare global {
padOutgoingAttachments: boolean;
};
lokiFileServerAPI: LokiFileServerInstance;
lokiMessageAPI: LokiMessageInterface;
lokiPublicChatAPI: LokiPublicChatFactoryInterface;
lokiSnodeAPI: LokiSnodeAPI;
onLogin: any;
@ -96,8 +93,6 @@ declare global {
dataURLToBlobSync: any;
autoOrientImage: any;
contextMenuShown: boolean;
setClockParams: any;
clientClockSynced: number | undefined;
inboxStore?: Store;
actionsCreators: any;
extension: {

Loading…
Cancel
Save