Added lazy instantiation to wordlists.

This commit is contained in:
Richard Moore 2022-11-27 21:45:42 -05:00
parent f24aa17709
commit 7dd9049993
17 changed files with 344 additions and 56 deletions

@ -1,5 +1,8 @@
const Base64 = ")!@#$%^&*(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
/**
* @_ignore
*/
export function decodeBits(width: number, data: string): Array<number> {
const maxValue = (1 << width) - 1;
const result: Array<number> = [ ];

@ -19,6 +19,9 @@ function unfold(words: Array<string>, sep: string): Array<string> {
}, <Array<string>>[]);
}
/**
* @_ignore
*/
export function decode(data: string, subs: string): Array<string> {
// Replace all the substitutions with their expanded form
@ -43,6 +46,9 @@ export function decode(data: string, subs: string): Array<string> {
return unfold(unfold(clumps, ";"), ":");
}
/**
* @_ignore
*/
export function decodeOwl(data: string): Array<string> {
assertArgument(data[0] === "0", "unsupported auwl data", "data", data);

@ -3,6 +3,9 @@ import { assertArgument } from "../utils/index.js";
import { decodeBits } from "./bit-reader.js";
import { decodeOwl } from "./decode-owl.js";
/**
* @_ignore
*/
export function decodeOwlA(data: string, accents: string): Array<string> {
let words = decodeOwl(data).join(",");

@ -1,7 +1,24 @@
/**
* A Wordlist is a set of 2048 words used to encode private keys
* (or other binary data) that is easier for humans to write down,
* transcribe and dictate.
*
* The [[link-bip-39]] standard includes several checksum bits,
* depending on the size of the mnemonic phrase.
*
* A mnemonic phrase may be 12, 15, 18, 21 or 24 words long. For
* most purposes 12 word mnemonics should be used, as including
* additional words increases the difficulty and potential for
* mistakes and does not offer any effective improvement on security.
*
* There are a variety of [[link-bip39-wordlists]] for different
* languages, but for maximal compatibility, the
* [English Wordlist](LangEn) is recommended.
*
* @_section: api/wordlists:Wordlists [wordlists]
*/
export { Wordlist } from "./wordlist.js";
export { langEn, LangEn } from "./lang-en.js";
export { wordlists } from "./wordlists.js";
export { LangEn } from "./lang-en.js";
export { WordlistOwl } from "./wordlist-owl.js";
export { WordlistOwlA } from "./wordlist-owla.js";

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -4,8 +4,30 @@ const words = "0arertoiotadonoaRteirroenaNonaLsolocoiliaralaorrenadaChoN$n0A>Dom
const accents = "aeiou7695@@BZWWavwUJkO@Y-Kn))YEGq#E@O)cI@#ZkMHv$e*))M!!)D**$GW!oKm*Acoh^k&It-pi^SYW)$^n!G)bO!Wkzam(jS#X)Og*^l^RW!bQ#QygBKXfzE))hti!Qm)Cng%%c)mJiI*HJWbmYniCLwNdYyY%WKO^bnT$PuGOr!IvHu&G(GKbtBuhiW&!eO@XMeoYQeCa#!MrTJCq!OW&CHG(WCcW%%)$rfrIegu$)w!G)JGmWWw)MnD%SXXWIT^LWAZuVWB^W)eTL^x&$WGHW(nKWEMA)#$F$x$Waekqs,n7715)W*HM-$WAcCiu(a))VCZ)GG%(*CWWdW%$D!UCO$M";
const checksum = "0xf74fb7092aeacdfbf8959557de22098da512207fb9f109cb526994938cf40300";
let wordlist: null | LangEs = null;
/**
* The [[link-bip-39]] Wordlist for the Spanish (es) language.
*
* @_docloc: api/wordlists
*/
export class LangEs extends WordlistOwlA {
/**
* Creates a new instance of the Spanish language Wordlist.
*
* This should be unnecessary most of the time as the exported
* [[langEs]] should suffice.
*/
constructor() { super("es", words, accents, checksum); }
/**
* Returns a singleton instance of a ``LangEs``, creating it
* if this is the first time being called.
*/
static wordlist(): LangEs {
if (wordlist == null) { wordlist = new LangEs(); }
return wordlist;
}
}
export const langEs = new LangEs();

@ -4,8 +4,29 @@ const words = "0erreleontiteurinueiriet cegeanseali medenel q)eniluxaus ch0Ais}A
const accents = "e7693&)U*o&)Ry^)*)W))))#X^))))@@)#Wf)m%)#!))AG)&IIAQIIIBIIHJNAgBIILIDJGo)))HIQIIIIA(IGgJHH(BIIxX#)Ou)@*IAAPIIIJHQJ)&QIQPYI(HYAQC%)!))QHJJ@)#)^f*^AXCJ))$%CP))%&m)u)@e^A#G#))W@!(IKK%!(I%))O@QA))@GG#e))))WHJIWh))my@IIBT^)!)HAYGETHI*))!QnUDG)))nBoKAC*HwyQh))$&)G&)UGO)G)))(BX#v**)%O,e7686)I))@)&)gdMP()))ud)p#L))I^FIHYdWG))D@DFV)QA)o%MyTh%*)Z)%)n(XANc^R)YS";
const checksum = "0x51deb7ae009149dc61a6bd18a918eb7ac78d2775726c68e598b92d002519b045";
export class LangFr extends WordlistOwlA {
constructor() { super("fr", words, accents, checksum); }
}
let wordlist: null | LangFr = null;
export const langFr = new LangFr();
/**
* The [[link-bip-39]] Wordlist for the French (fr) language.
*
* @_docloc: api/wordlists
*/
export class LangFr extends WordlistOwlA {
/**
* Creates a new instance of the French language Wordlist.
*
* This should be unnecessary most of the time as the exported
* [[langFr]] should suffice.
*/
constructor() { super("fr", words, accents, checksum); }
/**
* Returns a singleton instance of a ``LangFr``, creating it
* if this is the first time being called.
*/
static wordlist(): LangFr {
if (wordlist == null) { wordlist = new LangFr(); }
return wordlist;
}
}

File diff suppressed because one or more lines are too long

@ -35,7 +35,7 @@ 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~~d
let _wordlist: null | Array<string> = null;
function hex(word: string) {
function hex(word: string): string {
return hexlify(toUtf8Bytes(word));
}
@ -131,7 +131,21 @@ function loadWords(): Array<string> {
return wordlist;
}
class LangJa extends Wordlist {
let wordlist: null | LangJa = null;
/**
* The [[link-bip-39]] Wordlist for the Japanese (ja) language.
*
* @_docloc: api/wordlists
*/
export class LangJa extends Wordlist {
/**
* Creates a new instance of the Japanese language Wordlist.
*
* This should be unnecessary most of the time as the exported
* [[langJa]] should suffice.
*/
constructor() { super("ja"); }
getWord(index: number): string {
@ -145,14 +159,21 @@ class LangJa extends Wordlist {
return loadWords().indexOf(word);
}
split(mnemonic: string): Array<string> {
split(phrase: string): Array<string> {
//logger.assertNormalize();
return mnemonic.split(/(?:\u3000| )+/g);
return phrase.split(/(?:\u3000| )+/g);
}
join(words: Array<string>): string {
return words.join("\u3000");
}
}
export const langJa = new LangJa();
/**
* Returns a singleton instance of a ``LangJa``, creating it
* if this is the first time being called.
*/
static wordlist(): LangJa {
if (wordlist == null) { wordlist = new LangJa(); }
return wordlist;
}
}

@ -61,8 +61,21 @@ function loadWords(): Array<string> {
return wordlist;
}
let wordlist: null | LangKo = null;
class LangKo extends Wordlist {
/**
* The [[link-bip-39]] Wordlist for the Korean (ko) language.
*
* @_docloc: api/wordlists
*/
export class LangKo extends Wordlist {
/**
* Creates a new instance of the Korean language Wordlist.
*
* This should be unnecessary most of the time as the exported
* [[langKo]] should suffice.
*/
constructor() {
super("ko");
}
@ -77,6 +90,13 @@ class LangKo extends Wordlist {
getWordIndex(word: string): number {
return loadWords().indexOf(word);
}
}
export const langKo = new LangKo();
/**
* Returns a singleton instance of a ``LangKo``, creating it
* if this is the first time being called.
*/
static wordlist(): LangKo {
if (wordlist == null) { wordlist = new LangKo(); }
return wordlist;
}
}

File diff suppressed because one or more lines are too long

@ -58,8 +58,31 @@ function loadWords(locale: string): Array<string> {
return wordlist;
}
class LangZh extends Wordlist {
constructor(country: string) { super("zh_" + country); }
const wordlists: Record<string, LangZh> = { };
/**
* The [[link-bip-39]] Wordlist for the Chinese language.
*
* This Wordlist supports both simplified and traditional
* character set, depending on which is specified in the
* constructor.
*
* For the ``zh_cn`` language use ``"cn"`` and for the ``zh_tw``
* langauge, use ``"tw"``.
*
* @_docloc: api/wordlists
*/
export class LangZh extends Wordlist {
/**
* Creates a new instance of the Chinese language Wordlist for
* the %%dialect%%, either ``"cn"`` or ``"tw"`` for simplified
* or traditional, respectively.
*
* This should be unnecessary most of the time as the exported
* [[langZhCn]] and [[langZhTw]] should suffice.
*/
constructor(dialect: string) { super("zh_" + dialect); }
getWord(index: number): string {
const words = loadWords(this.locale);
@ -72,11 +95,19 @@ class LangZh extends Wordlist {
return loadWords(this.locale).indexOf(word);
}
split(mnemonic: string): Array<string> {
mnemonic = mnemonic.replace(/(?:\u3000| )+/g, "");
return mnemonic.split("");
split(phrase: string): Array<string> {
phrase = phrase.replace(/(?:\u3000| )+/g, "");
return phrase.split("");
}
/**
* Returns a singleton instance of a ``LangZh`` for %%dialect%%,
* creating it if this is the first time being called.
*/
static wordlist(dialect: string): LangZh {
if (wordlists[dialect] == null) {
wordlists[dialect] = new LangZh(dialect);
}
return wordlists[dialect];
}
}
export const langZhCn = new LangZh("cn");
export const langZhTw = new LangZh("tw");

@ -8,10 +8,26 @@ import { assertArgument } from "../utils/index.js";
import { decodeOwl } from "./decode-owl.js";
import { Wordlist } from "./wordlist.js";
/**
* An OWL format Wordlist is an encoding method that exploits
* the general locality of alphabetically sorted words to
* achieve a simple but effective means of compression.
*
* This class is generally not useful to most developers as
* it is used mainly internally to keep Wordlists for languages
* based on ASCII-7 small.
*
* If necessary, there are tools within the ``generation/`` folder
* to create these necessary data.
*/
export class WordlistOwl extends Wordlist {
#data: string;
#checksum: string;
/**
* Creates a new Wordlist for %%locale%% using the OWL %%data%%
* and validated against the %%checksum%%.
*/
constructor(locale: string, data: string, checksum: string) {
super(locale);
this.#data = data;

@ -2,6 +2,18 @@
import { WordlistOwl } from "./wordlist-owl.js";
import { decodeOwlA } from "./decode-owla.js";
/**
* An OWL-A format Wordlist extends the OWL format to add an
* overlay onto an OWL format Wordlist to support diacritic
* marks.
*
* This class is generally not useful to most developers as
* it is used mainly internally to keep Wordlists for languages
* based on latin-1 small.
*
* If necessary, there are tools within the ``generation/`` folder
* to create these necessary data.
*/
export class WordlistOwlA extends WordlistOwl {
#accent: string;

@ -1,22 +1,59 @@
import { defineProperties } from "../utils/index.js";
/**
* A Wordlist represents a collection of language-specific
* words used to encode and devoce [[BIP-39]] encoded data
* by mapping words to 11-bit values and vice versa.
*/
export abstract class Wordlist {
locale!: string;
/**
* Creates a new Wordlist instance.
*
* Sub-classes MUST call this if they provide their own constructor,
* passing in the locale string of the language.
*
* Generally there is no need to create instances of a Wordlist,
* since each language-specific Wordlist creates an instance and
* there is no state kept internally, so they are safe to share.
*/
constructor(locale: string) {
defineProperties<Wordlist>(this, { locale });
}
// Subclasses may override this
split(mnemonic: string): Array<string> {
return mnemonic.toLowerCase().split(/ +/g)
/**
* Sub-classes may override this to provide a language-specific
* method for spliting %%phrase%% into individual words.
*
* By default, %%phrase%% is split using any sequences of
* white-space as defined by regular expressions (i.e. ``/\s+/``).
*/
split(phrase: string): Array<string> {
return phrase.toLowerCase().split(/\s+/g)
}
// Subclasses may override this
/**
* Sub-classes may override this to provider a language-specific
* method for joining %%words%% into a phrase.
*
* By default, %%words%% are joined by a single space.
*/
join(words: Array<string>): string {
return words.join(" ");
}
/**
* Maps an 11-bit value into its coresponding word in the list.
*
* Sub-classes MUST override this.
*/
abstract getWord(index: number): string;
/**
* Maps a word to its corresponding 11-bit value.
*
* Sub-classes MUST override this.
*/
abstract getWordIndex(word: string): number;
}

@ -1,15 +1,9 @@
import { langCz as cz } from "./lang-cz.js";
import { langEs as es } from "./lang-es.js";
import { langFr as fr } from "./lang-fr.js";
import { langJa as ja } from "./lang-ja.js";
import { langKo as ko } from "./lang-ko.js";
import { langIt as it } from "./lang-it.js";
import { langPt as pt } from "./lang-pt.js";
import { langZhCn as zh_cn, langZhTw as zh_tw } from "./lang-zh.js";
import type { Wordlist } from "./wordlist.js";
export const wordlists: Record<string, Wordlist> = Object.freeze({
cz, es, fr, ja, ko, it, pt, zh_cn, zh_tw
});
export { LangCz } from "./lang-cz.js";
export { LangEs } from "./lang-es.js";
export { LangFr } from "./lang-fr.js";
export { LangJa } from "./lang-ja.js";
export { LangKo } from "./lang-ko.js";
export { LangIt } from "./lang-it.js";
export { LangPt } from "./lang-pt.js";
export { LangZh } from "./lang-zh.js";