Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WASM: add support for mnemonics #5700

Merged
merged 1 commit into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
65 changes: 65 additions & 0 deletions ironfish-rust-wasm/src/keys/mnemonics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* 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 {
// These are the same language codes used by `bip39`
English = "en",
ChineseSimplified = "zh-hans",
ChineseTraditional = "zh-hant",
French = "fr",
Italian = "it",
Japanese = "ja",
Korean = "ko",
Spanish = "es",
}

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,
Language::__Invalid => unreachable!(),
}
}
}

#[wasm_bindgen]
impl Language {
#[wasm_bindgen(js_name = "fromLanguageCode")]
pub fn from_language_code(code: &str) -> Result<Self, IronfishError> {
Self::from_str(code).ok_or_else(|| IronfishErrorKind::InvalidLanguageEncoding.into())
}

#[wasm_bindgen(getter, js_name = "languageCode")]
pub fn language_code(self) -> String {
self.to_str().to_string()
}
}
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