Skip to content

Commit 56fdf55

Browse files
committed
WASM: add support for mnemonics
1 parent c713738 commit 56fdf55

File tree

10 files changed

+140
-14
lines changed

10 files changed

+140
-14
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ironfish-rust-nodejs/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ pub fn spending_key_to_words(private_key: String, language_code: LanguageCode) -
109109

110110
#[napi]
111111
pub fn words_to_spending_key(words: String, language_code: LanguageCode) -> Result<String> {
112-
let key = SaplingKey::from_words(words, language_code.into()).map_err(to_napi_err)?;
112+
let key = SaplingKey::from_words(&words, language_code.into()).map_err(to_napi_err)?;
113113
Ok(key.hex_spending_key())
114114
}
115115

ironfish-rust-wasm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ ironfish-jubjub = "0.1.0"
2929
ironfish_zkp = { version = "0.2.0", path = "../ironfish-zkp" }
3030
rand = "0.8.5"
3131
rayon = { version = "1.8.1", features = ["web_spin_lock"] } # need to explicitly enable the `web_spin_lock` in order to run in a browser
32+
tiny-bip39 = "1.0"
3233
wasm-bindgen = "0.2.95"
3334

3435
[dev-dependencies]
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4+
5+
use crate::errors::IronfishError;
6+
use ironfish::errors::IronfishErrorKind;
7+
use wasm_bindgen::prelude::*;
8+
9+
#[wasm_bindgen]
10+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
11+
pub enum Language {
12+
// These are the same language codes used by `bip39`
13+
English = "en",
14+
ChineseSimplified = "zh-hans",
15+
ChineseTraditional = "zh-hant",
16+
French = "fr",
17+
Italian = "it",
18+
Japanese = "ja",
19+
Korean = "ko",
20+
Spanish = "es",
21+
}
22+
23+
impl From<bip39::Language> for Language {
24+
fn from(x: bip39::Language) -> Self {
25+
match x {
26+
bip39::Language::English => Self::English,
27+
bip39::Language::ChineseSimplified => Self::ChineseSimplified,
28+
bip39::Language::ChineseTraditional => Self::ChineseTraditional,
29+
bip39::Language::French => Self::French,
30+
bip39::Language::Italian => Self::Italian,
31+
bip39::Language::Japanese => Self::Japanese,
32+
bip39::Language::Korean => Self::Korean,
33+
bip39::Language::Spanish => Self::Spanish,
34+
}
35+
}
36+
}
37+
38+
impl From<Language> for bip39::Language {
39+
fn from(x: Language) -> Self {
40+
match x {
41+
Language::English => Self::English,
42+
Language::ChineseSimplified => Self::ChineseSimplified,
43+
Language::ChineseTraditional => Self::ChineseTraditional,
44+
Language::French => Self::French,
45+
Language::Italian => Self::Italian,
46+
Language::Japanese => Self::Japanese,
47+
Language::Korean => Self::Korean,
48+
Language::Spanish => Self::Spanish,
49+
Language::__Invalid => unreachable!(),
50+
}
51+
}
52+
}
53+
54+
#[wasm_bindgen]
55+
impl Language {
56+
#[wasm_bindgen(js_name = "fromLanguageCode")]
57+
pub fn from_language_code(code: &str) -> Result<Self, IronfishError> {
58+
Self::from_str(code).ok_or_else(|| IronfishErrorKind::InvalidLanguageEncoding.into())
59+
}
60+
61+
#[wasm_bindgen(getter, js_name = "languageCode")]
62+
pub fn language_code(self) -> String {
63+
self.to_str().to_string()
64+
}
65+
}

ironfish-rust-wasm/src/keys/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
44

5+
mod mnemonics;
56
mod proof_generation_key;
67
mod public_address;
78
mod sapling_key;
89
mod view_keys;
910

11+
pub use mnemonics::Language;
1012
pub use proof_generation_key::ProofGenerationKey;
1113
pub use public_address::PublicAddress;
1214
pub use sapling_key::SaplingKey;

ironfish-rust-wasm/src/keys/sapling_key.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
use crate::{
66
errors::IronfishError,
7-
keys::{IncomingViewKey, OutgoingViewKey, ProofGenerationKey, PublicAddress, ViewKey},
7+
keys::{
8+
IncomingViewKey, Language, OutgoingViewKey, ProofGenerationKey, PublicAddress, ViewKey,
9+
},
810
wasm_bindgen_wrapper,
911
};
1012
use wasm_bindgen::prelude::*;
@@ -45,7 +47,21 @@ impl SaplingKey {
4547
self.0.hex_spending_key()
4648
}
4749

48-
// TODO: to/fromWords
50+
#[wasm_bindgen(js_name = fromWords)]
51+
pub fn from_words(lang: Language, words: &str) -> Result<Self, IronfishError> {
52+
ironfish::keys::SaplingKey::from_words(words, lang.into())
53+
.map(|key| key.into())
54+
.map_err(|err| err.into())
55+
}
56+
57+
#[wasm_bindgen(js_name = toWords)]
58+
pub fn to_words(&self, lang: Language) -> String {
59+
self.0
60+
.to_words(lang.into())
61+
.expect("conversion to words failed")
62+
.phrase()
63+
.to_string()
64+
}
4965

5066
#[wasm_bindgen(getter, js_name = publicAddress)]
5167
pub fn public_address(&self) -> PublicAddress {
@@ -80,7 +96,9 @@ impl SaplingKey {
8096

8197
#[cfg(test)]
8298
mod tests {
83-
use crate::keys::{IncomingViewKey, OutgoingViewKey, ProofGenerationKey, SaplingKey, ViewKey};
99+
use crate::keys::{
100+
IncomingViewKey, Language, OutgoingViewKey, ProofGenerationKey, SaplingKey, ViewKey,
101+
};
84102
use wasm_bindgen_test::wasm_bindgen_test;
85103

86104
macro_rules! assert_serde_ok {
@@ -102,4 +120,15 @@ mod tests {
102120
assert_serde_ok!(ViewKey, key.view_key());
103121
assert_serde_ok!(ProofGenerationKey, key.proof_generation_key());
104122
}
123+
124+
#[test]
125+
#[wasm_bindgen_test]
126+
fn from_to_words() {
127+
let key = SaplingKey::random();
128+
let lang = Language::English;
129+
assert_eq!(
130+
&key,
131+
&SaplingKey::from_words(lang, key.to_words(lang).as_ref()).unwrap()
132+
);
133+
}
105134
}

ironfish-rust-wasm/src/keys/view_keys.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use crate::{
66
errors::IronfishError,
7-
keys::PublicAddress,
7+
keys::{Language, PublicAddress},
88
primitives::{Fr, PublicKey},
99
wasm_bindgen_wrapper,
1010
};
@@ -39,7 +39,21 @@ impl IncomingViewKey {
3939
self.0.hex_key()
4040
}
4141

42-
// TODO: to/fromWords
42+
#[wasm_bindgen(js_name = fromWords)]
43+
pub fn from_words(lang: Language, words: &str) -> Result<Self, IronfishError> {
44+
ironfish::keys::IncomingViewKey::from_words(lang.language_code().as_ref(), words)
45+
.map(|key| key.into())
46+
.map_err(|err| err.into())
47+
}
48+
49+
#[wasm_bindgen(js_name = toWords)]
50+
pub fn to_words(&self, lang: Language) -> String {
51+
// `words_key()` may fail only if the language code is invalid, but here we're accepting
52+
// `Language`, not an arbitrary input, so the language code is guaranteed to be valid.
53+
self.0
54+
.words_key(lang.language_code().as_ref())
55+
.expect("conversion to words failed")
56+
}
4357

4458
#[wasm_bindgen(getter, js_name = publicAddress)]
4559
pub fn public_address(&self) -> PublicAddress {
@@ -74,7 +88,21 @@ impl OutgoingViewKey {
7488
self.0.hex_key()
7589
}
7690

77-
// TODO: to/fromWords
91+
#[wasm_bindgen(js_name = fromWords)]
92+
pub fn from_words(lang: Language, words: &str) -> Result<Self, IronfishError> {
93+
ironfish::keys::OutgoingViewKey::from_words(lang.language_code().as_ref(), words)
94+
.map(|key| key.into())
95+
.map_err(|err| err.into())
96+
}
97+
98+
#[wasm_bindgen(js_name = toWords)]
99+
pub fn to_words(&self, lang: Language) -> String {
100+
// `words_key()` may fail only if the language code is invalid, but here we're accepting
101+
// `Language`, not an arbitrary input, so the language code is guaranteed to be valid.
102+
self.0
103+
.words_key(lang.language_code().as_ref())
104+
.expect("conversion to words failed")
105+
}
78106
}
79107

80108
wasm_bindgen_wrapper! {

ironfish-rust/src/keys/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,8 @@ impl SaplingKey {
182182
}
183183

184184
/// Takes a bip-39 phrase as a string and turns it into a SaplingKey instance
185-
pub fn from_words(words: String, language: Language) -> Result<Self, IronfishError> {
186-
let mnemonic = Mnemonic::from_phrase(&words, language)
185+
pub fn from_words(words: &str, language: Language) -> Result<Self, IronfishError> {
186+
let mnemonic = Mnemonic::from_phrase(words, language)
187187
.map_err(|_| IronfishError::new(IronfishErrorKind::InvalidMnemonicString))?;
188188
let bytes = mnemonic.entropy();
189189
let mut byte_arr = [0; SPEND_KEY_SIZE];

ironfish-rust/src/keys/test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,6 @@ fn test_from_and_to_words() {
127127

128128
// Convert from words
129129
let key =
130-
SaplingKey::from_words(words, bip39::Language::English).expect("key should be created");
130+
SaplingKey::from_words(&words, bip39::Language::English).expect("key should be created");
131131
assert_eq!(key.spending_key, key_bytes);
132132
}

ironfish-rust/src/keys/view_keys.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ impl IncomingViewKey {
5757
}
5858

5959
/// Load a key from a string of words to be decoded into bytes.
60-
pub fn from_words(language_code: &str, value: String) -> Result<Self, IronfishError> {
60+
pub fn from_words(language_code: &str, value: &str) -> Result<Self, IronfishError> {
6161
let language = Language::from_language_code(language_code)
6262
.ok_or_else(|| IronfishError::new(IronfishErrorKind::InvalidLanguageEncoding))?;
63-
let mnemonic = Mnemonic::from_phrase(&value, language)
63+
let mnemonic = Mnemonic::from_phrase(value, language)
6464
.map_err(|_| IronfishError::new(IronfishErrorKind::InvalidPaymentAddress))?;
6565
let bytes = mnemonic.entropy();
6666
let mut byte_arr = [0; 32];
@@ -227,10 +227,10 @@ impl OutgoingViewKey {
227227
}
228228

229229
/// Load a key from a string of words to be decoded into bytes.
230-
pub fn from_words(language_code: &str, value: String) -> Result<Self, IronfishError> {
230+
pub fn from_words(language_code: &str, value: &str) -> Result<Self, IronfishError> {
231231
let language = Language::from_language_code(language_code)
232232
.ok_or_else(|| IronfishError::new(IronfishErrorKind::InvalidLanguageEncoding))?;
233-
let mnemonic = Mnemonic::from_phrase(&value, language)
233+
let mnemonic = Mnemonic::from_phrase(value, language)
234234
.map_err(|_| IronfishError::new(IronfishErrorKind::InvalidPaymentAddress))?;
235235
let bytes = mnemonic.entropy();
236236
let mut view_key = [0; 32];

0 commit comments

Comments
 (0)