Skip to content

Commit

Permalink
WASM: add support for mnemonics
Browse files Browse the repository at this point in the history
  • Loading branch information
andiflabs committed Dec 18, 2024
1 parent c713738 commit 60417cc
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 14 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ironfish-rust-nodejs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub fn spending_key_to_words(private_key: String, language_code: LanguageCode) -

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

Expand Down
1 change: 1 addition & 0 deletions ironfish-rust-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ ironfish-jubjub = "0.1.0"
ironfish_zkp = { version = "0.2.0", path = "../ironfish-zkp" }
rand = "0.8.5"
rayon = { version = "1.8.1", features = ["web_spin_lock"] } # need to explicitly enable the `web_spin_lock` in order to run in a browser
tiny-bip39 = "1.0"
wasm-bindgen = "0.2.95"

[dev-dependencies]
Expand Down
76 changes: 76 additions & 0 deletions ironfish-rust-wasm/src/keys/mnemonics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::errors::IronfishError;
use ironfish::errors::IronfishErrorKind;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Language {
English,
ChineseSimplified,
ChineseTraditional,
French,
Italian,
Japanese,
Korean,
Spanish,
}

impl From<bip39::Language> for Language {
fn from(x: bip39::Language) -> Self {
match x {
bip39::Language::English => Self::English,
bip39::Language::ChineseSimplified => Self::ChineseSimplified,
bip39::Language::ChineseTraditional => Self::ChineseTraditional,
bip39::Language::French => Self::French,
bip39::Language::Italian => Self::Italian,
bip39::Language::Japanese => Self::Japanese,
bip39::Language::Korean => Self::Korean,
bip39::Language::Spanish => Self::Spanish,
}
}
}

impl From<Language> for bip39::Language {
fn from(x: Language) -> Self {
match x {
Language::English => Self::English,
Language::ChineseSimplified => Self::ChineseSimplified,
Language::ChineseTraditional => Self::ChineseTraditional,
Language::French => Self::French,
Language::Italian => Self::Italian,
Language::Japanese => Self::Japanese,
Language::Korean => Self::Korean,
Language::Spanish => Self::Spanish,
}
}
}

#[wasm_bindgen]
impl Language {
#[wasm_bindgen(constructor)]
pub fn from_language_code(code: &str) -> Result<Self, IronfishError> {
bip39::Language::from_language_code(code)
.map(Self::from)
.ok_or_else(|| IronfishErrorKind::InvalidLanguageEncoding.into())
}

#[wasm_bindgen(getter, js_name = "languageCode")]
pub fn language_code(self) -> String {
// For some reason, `bip39` does not provide a way to convert a `Language` back to string.
match self {
Self::English => "en",
Self::ChineseSimplified => "zh-hans",
Self::ChineseTraditional => "zh-hant",
Self::French => "fr",
Self::Italian => "it",
Self::Japanese => "ja",
Self::Korean => "ko",
Self::Spanish => "es",
}
.to_owned()
}
}
2 changes: 2 additions & 0 deletions ironfish-rust-wasm/src/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

mod mnemonics;
mod proof_generation_key;
mod public_address;
mod sapling_key;
mod view_keys;

pub use mnemonics::Language;
pub use proof_generation_key::ProofGenerationKey;
pub use public_address::PublicAddress;
pub use sapling_key::SaplingKey;
Expand Down
35 changes: 32 additions & 3 deletions ironfish-rust-wasm/src/keys/sapling_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use crate::{
errors::IronfishError,
keys::{IncomingViewKey, OutgoingViewKey, ProofGenerationKey, PublicAddress, ViewKey},
keys::{
IncomingViewKey, Language, OutgoingViewKey, ProofGenerationKey, PublicAddress, ViewKey,
},
wasm_bindgen_wrapper,
};
use wasm_bindgen::prelude::*;
Expand Down Expand Up @@ -45,7 +47,21 @@ impl SaplingKey {
self.0.hex_spending_key()
}

// TODO: to/fromWords
#[wasm_bindgen(js_name = fromWords)]
pub fn from_words(lang: Language, words: &str) -> Result<Self, IronfishError> {
ironfish::keys::SaplingKey::from_words(words, lang.into())
.map(|key| key.into())
.map_err(|err| err.into())
}

#[wasm_bindgen(js_name = toWords)]
pub fn to_words(&self, lang: Language) -> String {
self.0
.to_words(lang.into())
.expect("conversion to words failed")
.phrase()
.to_string()
}

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

#[cfg(test)]
mod tests {
use crate::keys::{IncomingViewKey, OutgoingViewKey, ProofGenerationKey, SaplingKey, ViewKey};
use crate::keys::{
IncomingViewKey, Language, OutgoingViewKey, ProofGenerationKey, SaplingKey, ViewKey,
};
use wasm_bindgen_test::wasm_bindgen_test;

macro_rules! assert_serde_ok {
Expand All @@ -102,4 +120,15 @@ mod tests {
assert_serde_ok!(ViewKey, key.view_key());
assert_serde_ok!(ProofGenerationKey, key.proof_generation_key());
}

#[test]
#[wasm_bindgen_test]
fn from_to_words() {
let key = SaplingKey::random();
let lang = Language::English;
assert_eq!(
&key,
&SaplingKey::from_words(lang, key.to_words(lang).as_ref()).unwrap()
);
}
}
34 changes: 31 additions & 3 deletions ironfish-rust-wasm/src/keys/view_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use crate::{
errors::IronfishError,
keys::PublicAddress,
keys::{Language, PublicAddress},
primitives::{Fr, PublicKey},
wasm_bindgen_wrapper,
};
Expand Down Expand Up @@ -39,7 +39,21 @@ impl IncomingViewKey {
self.0.hex_key()
}

// TODO: to/fromWords
#[wasm_bindgen(js_name = fromWords)]
pub fn from_words(lang: Language, words: &str) -> Result<Self, IronfishError> {
ironfish::keys::IncomingViewKey::from_words(lang.language_code().as_ref(), words)
.map(|key| key.into())
.map_err(|err| err.into())
}

#[wasm_bindgen(js_name = toWords)]
pub fn to_words(&self, lang: Language) -> String {
// `words_key()` may fail only if the language code is invalid, but here we're accepting
// `Language`, not an arbitrary input, so the language code is guaranteed to be valid.
self.0
.words_key(lang.language_code().as_ref())
.expect("conversion to words failed")
}

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

// TODO: to/fromWords
#[wasm_bindgen(js_name = fromWords)]
pub fn from_words(lang: Language, words: &str) -> Result<Self, IronfishError> {
ironfish::keys::OutgoingViewKey::from_words(lang.language_code().as_ref(), words)
.map(|key| key.into())
.map_err(|err| err.into())
}

#[wasm_bindgen(js_name = toWords)]
pub fn to_words(&self, lang: Language) -> String {
// `words_key()` may fail only if the language code is invalid, but here we're accepting
// `Language`, not an arbitrary input, so the language code is guaranteed to be valid.
self.0
.words_key(lang.language_code().as_ref())
.expect("conversion to words failed")
}
}

wasm_bindgen_wrapper! {
Expand Down
4 changes: 2 additions & 2 deletions ironfish-rust/src/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ impl SaplingKey {
}

/// Takes a bip-39 phrase as a string and turns it into a SaplingKey instance
pub fn from_words(words: String, language: Language) -> Result<Self, IronfishError> {
let mnemonic = Mnemonic::from_phrase(&words, language)
pub fn from_words(words: &str, language: Language) -> Result<Self, IronfishError> {
let mnemonic = Mnemonic::from_phrase(words, language)
.map_err(|_| IronfishError::new(IronfishErrorKind::InvalidMnemonicString))?;
let bytes = mnemonic.entropy();
let mut byte_arr = [0; SPEND_KEY_SIZE];
Expand Down
2 changes: 1 addition & 1 deletion ironfish-rust/src/keys/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,6 @@ fn test_from_and_to_words() {

// Convert from words
let key =
SaplingKey::from_words(words, bip39::Language::English).expect("key should be created");
SaplingKey::from_words(&words, bip39::Language::English).expect("key should be created");
assert_eq!(key.spending_key, key_bytes);
}
8 changes: 4 additions & 4 deletions ironfish-rust/src/keys/view_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ impl IncomingViewKey {
}

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

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

0 comments on commit 60417cc

Please sign in to comment.