Merge pull request #18 from sachaaaaa/mnemonic

Allow registering using a mnemonic word list
pull/22/head
sachaaaaa 7 years ago committed by GitHub
commit bbabeb34b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -571,6 +571,15 @@
<div class='inner'>
<div class='step-body'>
<div class='header'>Create your Loki Messenger Account</div>
<input class='form-control' type='text' id='mnemonic' placeholder='Mnemonic' autocomplete='off' spellcheck='false'>
<div class='clearfix'>
<a class='button' id='register-mnemonic'>Register using mnenomic</a>
</div>
<div id='error' class='collapse'></div>
<div id=status></div>
</div>
<div class='nav'>
<a class='button' id='register' data-loading-text='Please wait...'>Register</a>

@ -0,0 +1,2 @@
use emscripten to convert c to javascript:
`emcc sc_reduce32.c -o sc_reduce32.js -s EXPORTED_FUNCTIONS="['_sc_reduce32']" -s WASM=0 -s ENVIRONMENT=node -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall"]'`

@ -0,0 +1,134 @@
#include <stdint.h>
uint64_t load_3(const unsigned char *in) {
uint64_t result;
result = (uint64_t) in[0];
result |= ((uint64_t) in[1]) << 8;
result |= ((uint64_t) in[2]) << 16;
return result;
}
uint64_t load_4(const unsigned char *in)
{
uint64_t result;
result = (uint64_t) in[0];
result |= ((uint64_t) in[1]) << 8;
result |= ((uint64_t) in[2]) << 16;
result |= ((uint64_t) in[3]) << 24;
return result;
}
void sc_reduce32(unsigned char *s) {
int64_t s0 = 2097151 & load_3(s);
int64_t s1 = 2097151 & (load_4(s + 2) >> 5);
int64_t s2 = 2097151 & (load_3(s + 5) >> 2);
int64_t s3 = 2097151 & (load_4(s + 7) >> 7);
int64_t s4 = 2097151 & (load_4(s + 10) >> 4);
int64_t s5 = 2097151 & (load_3(s + 13) >> 1);
int64_t s6 = 2097151 & (load_4(s + 15) >> 6);
int64_t s7 = 2097151 & (load_3(s + 18) >> 3);
int64_t s8 = 2097151 & load_3(s + 21);
int64_t s9 = 2097151 & (load_4(s + 23) >> 5);
int64_t s10 = 2097151 & (load_3(s + 26) >> 2);
int64_t s11 = (load_4(s + 28) >> 7);
int64_t s12 = 0;
int64_t carry0;
int64_t carry1;
int64_t carry2;
int64_t carry3;
int64_t carry4;
int64_t carry5;
int64_t carry6;
int64_t carry7;
int64_t carry8;
int64_t carry9;
int64_t carry10;
int64_t carry11;
carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21;
carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21;
carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21;
carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21;
carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21;
carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21;
carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;
carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21;
carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21;
carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21;
carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21;
carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21;
carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21;
carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21;
carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21;
carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21;
carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21;
carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21;
carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21;
carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21;
carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21;
carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21;
carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21;
carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21;
carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21;
carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21;
carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21;
carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21;
carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21;
s[0] = s0 >> 0;
s[1] = s0 >> 8;
s[2] = (s0 >> 16) | (s1 << 5);
s[3] = s1 >> 3;
s[4] = s1 >> 11;
s[5] = (s1 >> 19) | (s2 << 2);
s[6] = s2 >> 6;
s[7] = (s2 >> 14) | (s3 << 7);
s[8] = s3 >> 1;
s[9] = s3 >> 9;
s[10] = (s3 >> 17) | (s4 << 4);
s[11] = s4 >> 4;
s[12] = s4 >> 12;
s[13] = (s4 >> 20) | (s5 << 1);
s[14] = s5 >> 7;
s[15] = (s5 >> 15) | (s6 << 6);
s[16] = s6 >> 2;
s[17] = s6 >> 10;
s[18] = (s6 >> 18) | (s7 << 3);
s[19] = s7 >> 5;
s[20] = s7 >> 13;
s[21] = s8 >> 0;
s[22] = s8 >> 8;
s[23] = (s8 >> 16) | (s9 << 5);
s[24] = s9 >> 3;
s[25] = s9 >> 11;
s[26] = (s9 >> 19) | (s10 << 2);
s[27] = s10 >> 6;
s[28] = (s10 >> 14) | (s11 << 7);
s[29] = s11 >> 1;
s[30] = s11 >> 9;
s[31] = s11 >> 17;
}

File diff suppressed because it is too large Load Diff

@ -31,15 +31,28 @@
'click #request-sms': 'requestSMSVerification',
'change #code': 'onChangeCode',
'click #register': 'register',
'click #register-mnemonic': 'registerWithMnemonic',
'change #mnemonic': 'onChangeMnemonic',
},
register() {
this.accountManager
.registerSingleDevice()
.registerSingleDevice(this.$('#mnemonic').val())
.then(() => {
this.$el.trigger('openInbox');
})
.catch(this.log.bind(this));
},
registerWithMnemonic() {
const words = this.$('#mnemonic').val();
if (!words) {
this.log('Please provide a mnemonic word list');
} else {
this.register();
}
},
onChangeMnemonic() {
this.$('#status').html('');
},
log(s) {
window.log.info(s);
this.$('#status').text(s);

@ -0,0 +1,162 @@
const crc32 = require('buffer-crc32');
const sc_reduce32_module = require('../components/sc_reduce32/sc_reduce32');
module.exports = {
mn_encode,
mn_decode,
sc_reduce32,
};
class MnemonicError extends Error {}
function hexToUint8Array(e) {
if (e.length % 2 != 0)
throw "Hex string has invalid length!";
for (var t = new Uint8Array(e.length / 2), r = 0; r < e.length / 2; ++r)
t[r] = parseInt(e.slice(2 * r, 2 * r + 2), 16);
return t
}
function Uint8ArrayToHex(e) {
for (var t = [], r = 0; r < e.length; ++r)
t.push(("0" + e[r].toString(16)).slice(-2));
return t.join("")
}
function sc_reduce32(e) {
var t = hexToUint8Array(e);
if (32 !== t.length)
throw "Invalid input length";
var r = sc_reduce32_module._malloc(32);
sc_reduce32_module.HEAPU8.set(t, r),
sc_reduce32_module.ccall("sc_reduce32", "void", ["number"], [r]);
var o = sc_reduce32_module.HEAPU8.subarray(r, r + 32);
return sc_reduce32_module._free(r),Uint8ArrayToHex(o);
}
/*
mnemonic.js : Converts between 4-byte aligned strings and a human-readable
sequence of words. Uses 1626 common words taken from wikipedia article:
http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry
Originally written in python special for Electrum (lightweight Bitcoin client).
This version has been reimplemented in javascript and placed in public domain.
*/
var mn_default_wordset = 'english';
function mn_get_checksum_index(words, prefix_len) {
var trimmed_words = "";
for (var i = 0; i < words.length; i++) {
trimmed_words += words[i].slice(0, prefix_len);
}
var checksum = crc32.unsigned(trimmed_words);
var index = checksum % words.length;
return index;
}
function mn_encode(str, wordset_name) {
'use strict';
wordset_name = wordset_name || mn_default_wordset;
var wordset = mn_words[wordset_name];
var out = [];
var n = wordset.words.length;
for (var j = 0; j < str.length; j += 8) {
str = str.slice(0, j) + mn_swap_endian_4byte(str.slice(j, j + 8)) + str.slice(j + 8);
}
for (var i = 0; i < str.length; i += 8) {
var x = parseInt(str.substr(i, 8), 16);
var w1 = (x % n);
var w2 = (Math.floor(x / n) + w1) % n;
var w3 = (Math.floor(Math.floor(x / n) / n) + w2) % n;
out = out.concat([wordset.words[w1], wordset.words[w2], wordset.words[w3]]);
}
if (wordset.prefix_len > 0) {
out.push(out[mn_get_checksum_index(out, wordset.prefix_len)]);
}
return out.join(' ');
}
function mn_swap_endian_4byte(str) {
'use strict';
if (str.length !== 8) throw new MnemonicError('Invalid input length: ' + str.length);
return str.slice(6, 8) + str.slice(4, 6) + str.slice(2, 4) + str.slice(0, 2);
}
function mn_decode(str, wordset_name) {
'use strict';
wordset_name = wordset_name || mn_default_wordset;
var wordset = mn_words[wordset_name];
var out = '';
var n = wordset.words.length;
var wlist = str.split(' ');
var checksum_word = '';
if (wlist.length < 12) throw new MnemonicError("You've entered too few words, please try again");
if ((wordset.prefix_len === 0 && (wlist.length % 3 !== 0)) ||
(wordset.prefix_len > 0 && (wlist.length % 3 === 2))) throw new MnemonicError("You've entered too few words, please try again");
if (wordset.prefix_len > 0 && (wlist.length % 3 === 0)) throw new MnemonicError("You seem to be missing the last word in your private key, please try again");
if (wordset.prefix_len > 0) {
// Pop checksum from mnemonic
checksum_word = wlist.pop();
}
// Decode mnemonic
for (var i = 0; i < wlist.length; i += 3) {
var w1, w2, w3;
if (wordset.prefix_len === 0) {
w1 = wordset.words.indexOf(wlist[i]);
w2 = wordset.words.indexOf(wlist[i + 1]);
w3 = wordset.words.indexOf(wlist[i + 2]);
} else {
w1 = wordset.trunc_words.indexOf(wlist[i].slice(0, wordset.prefix_len));
w2 = wordset.trunc_words.indexOf(wlist[i + 1].slice(0, wordset.prefix_len));
w3 = wordset.trunc_words.indexOf(wlist[i + 2].slice(0, wordset.prefix_len));
}
if (w1 === -1 || w2 === -1 || w3 === -1) {
throw MnemonicError("invalid word in mnemonic");
}
var x = w1 + n * (((n - w1) + w2) % n) + n * n * (((n - w2) + w3) % n);
if (x % n != w1) throw new MnemonicError('Something went wrong when decoding your private key, please try again');
out += mn_swap_endian_4byte(('0000000' + x.toString(16)).slice(-8));
}
// Verify checksum
if (wordset.prefix_len > 0) {
var index = mn_get_checksum_index(wlist, wordset.prefix_len);
var expected_checksum_word = wlist[index];
if (expected_checksum_word.slice(0, wordset.prefix_len) !== checksum_word.slice(0, wordset.prefix_len)) {
throw new MnemonicError("Your private key could not be verified, please try again");
}
}
return out;
}
var mn_words = {
'electrum': {
prefix_len: 0,
words: require('../mnemonic_languages/electrum'),
},
'english': {
prefix_len: 3,
words: require('../mnemonic_languages/english'),
},
'spanish': {
prefix_len: 4,
words: require('../mnemonic_languages/spanish'),
},
'portuguese': {
prefix_len: 4,
words: require('../mnemonic_languages/portuguese'),
},
'japanese': {
prefix_len: 3,
words: require('../mnemonic_languages/japanese'),
}
};
for (var i in mn_words) {
if (mn_words.hasOwnProperty(i)) {
if (mn_words[i].prefix_len === 0) {
continue;
}
mn_words[i].trunc_words = [];
for (var j = 0; j < mn_words[i].words.length; ++j) {
mn_words[i].trunc_words.push(mn_words[i].words[j].slice(0, mn_words[i].prefix_len));
}
}
}

@ -44,14 +44,25 @@
requestSMSVerification(number) {
return this.server.requestVerificationSMS(number);
},
registerSingleDevice() {
registerSingleDevice(mnemonic) {
const createAccount = this.createAccount.bind(this);
const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
const generateKeys = this.generateKeys.bind(this, 0);
const confirmKeys = this.confirmKeys.bind(this);
const registrationDone = this.registrationDone.bind(this);
return this.queueTask(() =>
libsignal.KeyHelper.generateIdentityKeyPair().then(identityKeyPair => {
let generateKeypair;
if (mnemonic) {
generateKeypair = () => {
const seedHex = window.mnemonic.mn_decode(mnemonic);
const privKeyHex = window.mnemonic.sc_reduce32(seedHex);
const privKey = dcodeIO.ByteBuffer.wrap(privKeyHex, 'hex').toArrayBuffer();
return libsignal.Curve.async.createKeyPair(privKey);
};
} else {
generateKeypair = libsignal.KeyHelper.generateIdentityKeyPair;
}
return this.queueTask(() =>
generateKeypair().then(identityKeyPair => {
return createAccount(
identityKeyPair,
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -206,6 +206,8 @@ window.LokiAPI = initializeLokiAPI({
url: config.serverUrl,
});
window.mnemonic = require('./libloki/mnemonic');
// Linux seems to periodically let the event loop stop, so this is a global workaround
setInterval(() => {
window.nodeSetImmediate(() => {});

Loading…
Cancel
Save