Added sanity checksums to all BIP39 wordlists on load.

This commit is contained in:
Richard Moore 2018-07-14 20:51:51 -04:00
parent e6c8db88bd
commit 8d6fa3dc93
No known key found for this signature in database
GPG Key ID: 525F70A6FCABC295
6 changed files with 97 additions and 62 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,15 +1,11 @@
import { register, Wordlist } from './wordlist'; import { check, register, Wordlist } from './wordlist';
import { id } from '../utils/hash';
import { hexlify } from '../utils/bytes'; import { hexlify } from '../utils/bytes';
import { toUtf8Bytes, toUtf8String } from '../utils/utf8'; import { toUtf8Bytes, toUtf8String } from '../utils/utf8';
import * as errors from '../utils/errors'; import * as errors from '../utils/errors';
const CheckId = "0xe561f52ac5f1fe957460337bc400302f30af56ac8da6adc7311efa89fbbc0777";
const data = [ const data = [
// 4-kana words // 4-kana words
@ -37,7 +33,7 @@ const data = [
// Maps each character into its kana value (the index) // Maps each character into its kana value (the index)
const mapping = "~~AzB~X~a~KN~Q~D~S~C~G~E~Y~p~L~I~O~eH~g~V~hxyumi~~U~~Z~~v~~s~~dkoblPjfnqwMcRTr~W~~~F~~~~~Jt" const mapping = "~~AzB~X~a~KN~Q~D~S~C~G~E~Y~p~L~I~O~eH~g~V~hxyumi~~U~~Z~~v~~s~~dkoblPjfnqwMcRTr~W~~~F~~~~~Jt"
let words: Array<string> = null; let wordlist: Array<string> = null;
function hex(word: string) { function hex(word: string) {
return hexlify(toUtf8Bytes(word)); return hexlify(toUtf8Bytes(word));
@ -46,13 +42,10 @@ function hex(word: string) {
const KiYoKu = '0xe3818de38284e3818f'; const KiYoKu = '0xe3818de38284e3818f';
const KyoKu = '0xe3818de38283e3818f' const KyoKu = '0xe3818de38283e3818f'
let error: Error = null; function loadWords(lang: Wordlist) {
function loadWords() { if (wordlist !== null) { return; }
if (words !== null) {
if (error) { throw error; } wordlist = [];
return;
}
words = [];
// Transforms for normalizing (sort is a not quite UTF-8) // Transforms for normalizing (sort is a not quite UTF-8)
var transform: { [key: string]: string | boolean } = {}; var transform: { [key: string]: string | boolean } = {};
@ -101,26 +94,25 @@ function loadWords() {
word.push((k & 0x40) ? 130: 129); word.push((k & 0x40) ? 130: 129);
word.push((k & 0x3f) + 128); word.push((k & 0x3f) + 128);
} }
words.push(toUtf8String(word)); wordlist.push(toUtf8String(word));
} }
} }
words.sort(sortJapanese); wordlist.sort(sortJapanese);
// For some reason kyoku and kiyoku are flipped in node (!!). // For some reason kyoku and kiyoku are flipped in node (!!).
// The order SHOULD be: // The order SHOULD be:
// - kyoku // - kyoku
// - kiyoku // - kiyoku
if (hex(words[442]) === KiYoKu && hex(words[443]) === KyoKu) { if (hex(wordlist[442]) === KiYoKu && hex(wordlist[443]) === KyoKu) {
let tmp = words[442]; let tmp = wordlist[442];
words[442] = words[443]; wordlist[442] = wordlist[443];
words[443] = tmp; wordlist[443] = tmp;
} }
let check = id(words.join('\n')); if (check(lang) !== '0xcb36b09e6baa935787fd762ce65e80b0c6a8dabdfbc3a7f86ac0e2c4fd111600') {
if (check !== CheckId) { wordlist = null;
error = new Error('Japanese Wordlist FAILED to load; your browser sort is broken; please contract support@ethers.io.'); throw new Error('BIP39 Wordlist for ja (Japanese) FAILED');
throw error;
} }
} }
@ -130,13 +122,13 @@ class LangJa extends Wordlist {
} }
getWord(index: number): string { getWord(index: number): string {
loadWords(); loadWords(this);
return words[index]; return wordlist[index];
} }
getWordIndex(word: string): number { getWordIndex(word: string): number {
loadWords(); loadWords(this);
return words.indexOf(word); return wordlist.indexOf(word);
} }
split(mnemonic: string): Array<string> { split(mnemonic: string): Array<string> {

@ -1,5 +1,5 @@
import { register, Wordlist } from './wordlist'; import { check, register, Wordlist } from './wordlist';
import { toUtf8String } from '../utils/utf8'; import { toUtf8String } from '../utils/utf8';
@ -28,7 +28,7 @@ function getHangul(code: number): string {
let wordlist: Array<string> = null; let wordlist: Array<string> = null;
function loadWords(): void { function loadWords(lang: Wordlist): void {
if (wordlist != null) { return; } if (wordlist != null) { return; }
wordlist = []; wordlist = [];
@ -45,9 +45,12 @@ function loadWords(): void {
}); });
wordlist.sort(); wordlist.sort();
}
loadWords();
if (check(lang) !== '0xf9eddeace9c5d3da9c93cf7d3cd38f6a13ed3affb933259ae865714e8a3ae71a') {
wordlist = null;
throw new Error('BIP39 Wordlist for ko (Korean) FAILED');
}
}
class LangKo extends Wordlist { class LangKo extends Wordlist {
@ -56,12 +59,12 @@ class LangKo extends Wordlist {
} }
getWord(index: number): string { getWord(index: number): string {
loadWords(); loadWords(this);
return wordlist[index]; return wordlist[index];
} }
getWordIndex(word: string): number { getWordIndex(word: string): number {
loadWords(); loadWords(this);
return wordlist.indexOf(word); return wordlist.indexOf(word);
} }
} }

@ -1,5 +1,5 @@
import { register, Wordlist } from './wordlist'; import { check, register, Wordlist } from './wordlist';
import { toUtf8String } from '../utils/utf8'; import { toUtf8String } from '../utils/utf8';
@ -8,29 +8,47 @@ const deltaData = "FAZDC6BALcLZCA+GBARCW8wNCcDDZ8LVFBOqqDUiou+M42TFAyERXFb7EjhP+
// @TODO: Load lazily // @TODO: Load lazily
const words: { [key: string]: Array<string> } = { const wordlist: { [key: string]: Array<string> } = {
zh_cn: [], zh_cn: null,
zh_tw: [] zh_tw: null
} }
var codes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const Checks: { [key: string]: string } = {
var style = "~!@#$%^&*_-=[]{}|;:,.()<>?" zh_cn: '0x17bcc4d8547e5a7135e365d1ab443aaae95e76d8230c2782c67305d4f21497a1',
zh_tw: '0x51e720e90c7b87bec1d70eb6e74a21a449bd3ec9c020b01d3a40ed991b60ce5d'
}
let deltaOffset = 0; const codes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (var i = 0; i < 2048; i++) { const style = "~!@#$%^&*_-=[]{}|;:,.()<>?"
let s = style.indexOf(data[i * 3]);
let bytes = [
228 + (s >> 2),
128 + codes.indexOf(data[i * 3 + 1]),
128 + codes.indexOf(data[i * 3 + 2]),
];
words.zh_cn.push(toUtf8String(bytes));
let common = s % 4; function loadWords(lang: Wordlist) {
for (let i = common; i < 3; i++) { if (wordlist[lang.locale] !== null) { return; }
bytes[i] = codes.indexOf(deltaData[deltaOffset++]) + ((i == 0) ? 228: 128);
wordlist[lang.locale] = [];
let deltaOffset = 0;
for (var i = 0; i < 2048; i++) {
let s = style.indexOf(data[i * 3]);
let bytes = [
228 + (s >> 2),
128 + codes.indexOf(data[i * 3 + 1]),
128 + codes.indexOf(data[i * 3 + 2]),
];
if (lang.locale === 'zh_tw') {
let common = s % 4;
for (let i = common; i < 3; i++) {
bytes[i] = codes.indexOf(deltaData[deltaOffset++]) + ((i == 0) ? 228: 128);
}
}
wordlist[lang.locale].push(toUtf8String(bytes));
}
if (check(lang) !== Checks[lang.locale]) {
wordlist[lang.locale] = null;
throw new Error('BIP39 Wordlist for ' + lang.locale + ' (Chinese) FAILED');
} }
words.zh_tw.push(toUtf8String(bytes));
} }
class LangZh extends Wordlist { class LangZh extends Wordlist {
@ -39,11 +57,13 @@ class LangZh extends Wordlist {
} }
getWord(index: number): string { getWord(index: number): string {
return words[this.locale][index]; loadWords(this);
return wordlist[this.locale][index];
} }
getWordIndex(word: string): number { getWordIndex(word: string): number {
return words[this.locale].indexOf(word); loadWords(this);
return wordlist[this.locale].indexOf(word);
} }
split(mnemonic: string): Array<string> { split(mnemonic: string): Array<string> {

@ -2,10 +2,22 @@
// This gets overriddenby gulp during bip39-XX // This gets overriddenby gulp during bip39-XX
var exportWordlist = false; var exportWordlist = false;
import { id } from '../utils/hash';
import { defineReadOnly } from '../utils/properties'; import { defineReadOnly } from '../utils/properties';
import { Wordlist as _Wordlist } from '../utils/types'; import { Wordlist as _Wordlist } from '../utils/types';
export function check(wordlist: _Wordlist) {
var words = [];
for (let i = 0; i < 2048; i++) {
let word = wordlist.getWord(i);
if (i !== wordlist.getWordIndex(word)) { return '0x'; }
words.push(word);
}
return id(words.join('\n') + '\n');
}
export abstract class Wordlist implements _Wordlist { export abstract class Wordlist implements _Wordlist {
locale: string; locale: string;