move mnemonic.js to typescript
parent
553f264d12
commit
35e57f2509
@ -1,6 +0,0 @@
|
||||
export interface RecoveryPhraseUtil {
|
||||
mn_encode(str: string, wordset_name: string): string;
|
||||
mn_decode(str: string, wordset_name: string): string;
|
||||
get_languages(): Array<string>;
|
||||
pubkey_to_secret_words(pubKey?: string): string;
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
const crc32 = require('buffer-crc32');
|
||||
|
||||
module.exports = {
|
||||
mn_encode,
|
||||
mn_decode,
|
||||
get_languages,
|
||||
pubkey_to_secret_words,
|
||||
};
|
||||
class MnemonicError extends Error {}
|
||||
|
||||
/*
|
||||
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 new 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 verify the checksum word'
|
||||
);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Note: the value is the prefix_len
|
||||
const languages = {
|
||||
chinese_simplified: 1,
|
||||
dutch: 4,
|
||||
electrum: 0,
|
||||
english: 3,
|
||||
esperanto: 4,
|
||||
french: 4,
|
||||
german: 4,
|
||||
italian: 4,
|
||||
japanese: 3,
|
||||
lojban: 4,
|
||||
portuguese: 4,
|
||||
russian: 4,
|
||||
spanish: 4,
|
||||
};
|
||||
|
||||
let mn_words = {};
|
||||
for (let [language, prefix_len] of Object.entries(languages)) {
|
||||
mn_words[language] = {
|
||||
prefix_len,
|
||||
words: require(`../../mnemonic_languages/${language}`),
|
||||
};
|
||||
}
|
||||
|
||||
function get_languages() {
|
||||
return Object.keys(mn_words);
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pubkey_to_secret_words(pubKey) {
|
||||
return mn_encode(pubKey.slice(2), 'english')
|
||||
.split(' ')
|
||||
.slice(0, 3)
|
||||
.join(' ');
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
import crc32 from 'buffer-crc32';
|
||||
|
||||
class MnemonicError extends Error {}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
const MN_DEFAULT_WORDSET = 'english';
|
||||
|
||||
function mn_get_checksum_index(words: Array<string>, prefixLen: number) {
|
||||
let trimmedWords = '';
|
||||
// tslint:disable-next-line: prefer-for-of
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
trimmedWords += words[i].slice(0, prefixLen);
|
||||
}
|
||||
const checksum = crc32.unsigned(trimmedWords as any);
|
||||
const index = checksum % words.length;
|
||||
return index;
|
||||
}
|
||||
|
||||
export function mn_encode(
|
||||
str: string,
|
||||
wordsetName: string = MN_DEFAULT_WORDSET
|
||||
): string {
|
||||
const wordset = mnWords[wordsetName];
|
||||
let out = [] as Array<any>;
|
||||
const n = wordset.words.length;
|
||||
let strCopy = str;
|
||||
for (let j = 0; j < strCopy.length; j += 8) {
|
||||
strCopy =
|
||||
strCopy.slice(0, j) +
|
||||
mn_swap_endian_4byte(strCopy.slice(j, j + 8)) +
|
||||
strCopy.slice(j + 8);
|
||||
}
|
||||
for (let i = 0; i < strCopy.length; i += 8) {
|
||||
const x = parseInt(strCopy.substr(i, 8), 16);
|
||||
const w1 = x % n;
|
||||
const w2 = (Math.floor(x / n) + w1) % n;
|
||||
const w3 = (Math.floor(Math.floor(x / n) / n) + w2) % n;
|
||||
out = out.concat([wordset.words[w1], wordset.words[w2], wordset.words[w3]]);
|
||||
}
|
||||
if (wordset.prefixLen > 0) {
|
||||
out.push(out[mn_get_checksum_index(out, wordset.prefixLen)]);
|
||||
}
|
||||
return out.join(' ');
|
||||
}
|
||||
|
||||
function mn_swap_endian_4byte(str: string) {
|
||||
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);
|
||||
}
|
||||
|
||||
export function mn_decode(
|
||||
str: string,
|
||||
wordsetName: string = MN_DEFAULT_WORDSET
|
||||
): string {
|
||||
const wordset = mnWords[wordsetName];
|
||||
let out = '';
|
||||
const n = wordset.words.length;
|
||||
const wlist = str.split(' ');
|
||||
let checksumWord = '';
|
||||
if (wlist.length < 12) {
|
||||
throw new MnemonicError("You've entered too few words, please try again");
|
||||
}
|
||||
if (
|
||||
(wordset.prefixLen === 0 && wlist.length % 3 !== 0) ||
|
||||
(wordset.prefixLen > 0 && wlist.length % 3 === 2)
|
||||
) {
|
||||
throw new MnemonicError("You've entered too few words, please try again");
|
||||
}
|
||||
if (wordset.prefixLen > 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.prefixLen > 0) {
|
||||
// Pop checksum from mnemonic
|
||||
checksumWord = wlist.pop() as string;
|
||||
}
|
||||
// Decode mnemonic
|
||||
for (let i = 0; i < wlist.length; i += 3) {
|
||||
// tslint:disable-next-line: one-variable-per-declaration
|
||||
let w1, w2, w3;
|
||||
if (wordset.prefixLen === 0) {
|
||||
w1 = wordset.words.indexOf(wlist[i]);
|
||||
w2 = wordset.words.indexOf(wlist[i + 1]);
|
||||
w3 = wordset.words.indexOf(wlist[i + 2]);
|
||||
} else {
|
||||
w1 = wordset.truncWords.indexOf(wlist[i].slice(0, wordset.prefixLen));
|
||||
w2 = wordset.truncWords.indexOf(wlist[i + 1].slice(0, wordset.prefixLen));
|
||||
w3 = wordset.truncWords.indexOf(wlist[i + 2].slice(0, wordset.prefixLen));
|
||||
}
|
||||
if (w1 === -1 || w2 === -1 || w3 === -1) {
|
||||
throw new MnemonicError('invalid word in mnemonic');
|
||||
}
|
||||
// tslint:disable-next-line: restrict-plus-operands
|
||||
const 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.prefixLen > 0) {
|
||||
const index = mn_get_checksum_index(wlist, wordset.prefixLen);
|
||||
const expectedChecksumWord = wlist[index];
|
||||
if (
|
||||
expectedChecksumWord.slice(0, wordset.prefixLen) !==
|
||||
checksumWord.slice(0, wordset.prefixLen)
|
||||
) {
|
||||
throw new MnemonicError(
|
||||
'Your private key could not be verified, please verify the checksum word'
|
||||
);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Note: the value is the prefix_len
|
||||
const languages = {
|
||||
chinese_simplified: 1,
|
||||
dutch: 4,
|
||||
electrum: 0,
|
||||
english: 3,
|
||||
esperanto: 4,
|
||||
french: 4,
|
||||
german: 4,
|
||||
italian: 4,
|
||||
japanese: 3,
|
||||
lojban: 4,
|
||||
portuguese: 4,
|
||||
russian: 4,
|
||||
spanish: 4,
|
||||
};
|
||||
|
||||
const mnWords = {} as Record<
|
||||
string,
|
||||
{
|
||||
prefixLen: number;
|
||||
words: any;
|
||||
truncWords: Array<any>;
|
||||
}
|
||||
>;
|
||||
for (const [language, prefixLen] of Object.entries(languages)) {
|
||||
mnWords[language] = {
|
||||
prefixLen,
|
||||
// tslint:disable-next-line: non-literal-require
|
||||
words: require(`../../../mnemonic_languages/${language}`),
|
||||
truncWords: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function get_languages(): Array<string> {
|
||||
return Object.keys(mnWords);
|
||||
}
|
||||
// tslint:disable: prefer-for-of
|
||||
// tslint:disable: no-for-in
|
||||
for (const i in mnWords) {
|
||||
if (mnWords.hasOwnProperty(i)) {
|
||||
if (mnWords[i].prefixLen === 0) {
|
||||
continue;
|
||||
}
|
||||
for (let j = 0; j < mnWords[i].words.length; ++j) {
|
||||
mnWords[i].truncWords.push(
|
||||
mnWords[i].words[j].slice(0, mnWords[i].prefixLen)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue