Skip to content

Commit 3f3115f

Browse files
authored
Added multi-language support (#7)
1 parent b248e20 commit 3f3115f

File tree

4 files changed

+108
-21
lines changed

4 files changed

+108
-21
lines changed

Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "py-bip39-bindings"
33
description = "Python bindings for tiny-bip39 RUST crate"
44
authors=["Stichting Polkascan (Polkascan Foundation)"]
5-
version = "0.1.8"
5+
version = "0.1.9"
66
repository = "https://github.com/polkascan/py-bip39-bindings"
77
homepage = "https://github.com/polkascan/py-bip39-bindings"
88
license = "Apache-2.0"
@@ -12,8 +12,8 @@ edition = "2018"
1212
[dependencies]
1313
hmac = "0.7.0"
1414
pbkdf2 = { version = "0.3.0", default-features = false }
15-
sha2 = "0.8.0"
16-
tiny-bip39 = { version = "0.6.2", default-features = false }
15+
sha2 = "0.8.2"
16+
tiny-bip39 = { version = "0.8.2", default-features = true }
1717

1818
[lib]
1919
name = "bip39"
@@ -34,5 +34,6 @@ classifier = [
3434
"Programming Language :: Python :: 3.6",
3535
"Programming Language :: Python :: 3.7",
3636
"Programming Language :: Python :: 3.8",
37-
"Programming Language :: Python :: 3.9"
37+
"Programming Language :: Python :: 3.9",
38+
"Programming Language :: Python :: 3.10"
3839
]

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ pip install py-bip39-bindings
2121
pip install -r requirements.txt
2222
maturin develop
2323
```
24-
### Build wheelhouses
24+
### Build wheels
2525
```shell script
2626
pip install -r requirements.txt
2727

28-
# Build local OS wheelhouse
28+
# Build local OS wheel
2929
maturin build
3030

31-
# Build manylinux1 wheelhouse
31+
# Build manylinux1 wheel
3232
docker build . --tag polkasource/maturin
3333
docker run --rm -i -v $(pwd):/io polkasource/maturin build
3434

@@ -48,5 +48,22 @@ seed_hex = binascii.hexlify(bytearray(seed_array)).decode("ascii")
4848

4949
```
5050

51+
## Multi-language support
52+
53+
The following language codes are supported: 'en', 'zh-hans', 'zh-hant', 'fr', 'it', 'jap', 'ko', 'es'. Defaults to 'en'
54+
55+
```python
56+
mnemonic = bip39_generate(12, 'fr')
57+
# 'moufle veinard tronc magasin merle amour toboggan admettre biotype décembre régalien billard'
58+
bip39_validate(mnemonic, 'fr')
59+
60+
seed_array = bip39_to_mini_secret(mnemonic, "", 'fr')
61+
62+
mnemonic = bip39_generate(12, 'zh-hans')
63+
# '观 敲 荣 硬 责 雪 专 宴 醇 飞 图 菌'
64+
```
65+
66+
67+
5168
## License
5269
https://github.com/polkascan/py-bip39-bindings/blob/master/LICENSE

src/lib.rs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,22 @@ use sha2::Sha512;
3535
///
3636
/// * `phrase` - Mnemonic phrase
3737
/// * `password` - Use empty string for no password
38+
/// * `language_code` - The language to use, valid values are: 'en', 'zh-hans', 'zh-hant', 'fr', 'it', 'jap', 'ko', 'es'. Defaults to 'en'
3839
///
3940
/// # Returns
4041
///
4142
/// Returns the 32-bytes mini-secret via entropy
4243
#[pyfunction]
43-
#[text_signature = "(phrase, password)"]
44-
pub fn bip39_to_mini_secret(phrase: &str, password: &str) -> PyResult<Vec<u8>> {
44+
#[text_signature = "(phrase, password, language_code, /)"]
45+
pub fn bip39_to_mini_secret(phrase: &str, password: &str, language_code: Option<&str>) -> PyResult<Vec<u8>> {
4546
let salt = format!("mnemonic{}", password);
46-
let mnemonic = match Mnemonic::from_phrase(phrase, Language::English) {
47+
48+
let language = match Language::from_language_code(language_code.unwrap_or("en")) {
49+
Some(language) => language,
50+
None => return Err(exceptions::ValueError::py_err("Invalid language_code"))
51+
};
52+
53+
let mnemonic = match Mnemonic::from_phrase(phrase, language) {
4754
Ok(some_mnemomic) => some_mnemomic,
4855
Err(err) => return Err(exceptions::ValueError::py_err(format!("Invalid mnemonic: {}", err.to_string())))
4956
};
@@ -59,20 +66,26 @@ pub fn bip39_to_mini_secret(phrase: &str, password: &str) -> PyResult<Vec<u8>> {
5966
/// # Arguments
6067
///
6168
/// * `words` - The amount of words to generate, valid values are 12, 15, 18, 21 and 24
69+
6270
///
6371
/// # Returns
6472
///
6573
/// A string containing the mnemonic words.
6674
#[pyfunction]
67-
#[text_signature = "(words)"]
68-
pub fn bip39_generate(words: u32) -> PyResult<String> {
75+
#[text_signature = "(words, language_code, /)"]
76+
pub fn bip39_generate(words: u32, language_code: Option<&str>) -> PyResult<String> {
77+
78+
let language = match Language::from_language_code(language_code.unwrap_or("en")) {
79+
Some(language) => language,
80+
None => return Err(exceptions::ValueError::py_err("Invalid language_code"))
81+
};
6982

7083
let word_count_type = match MnemonicType::for_word_count(words as usize) {
7184
Ok(some_work_count) => some_work_count,
7285
Err(err) => return Err(exceptions::ValueError::py_err(err.to_string()))
7386
};
7487

75-
let phrase = Mnemonic::new(word_count_type, Language::English).into_phrase();
88+
let phrase = Mnemonic::new(word_count_type, language).into_phrase();
7689

7790
assert_eq!(phrase.split(" ").count(), words as usize);
7891

@@ -85,14 +98,21 @@ pub fn bip39_generate(words: u32) -> PyResult<String> {
8598
///
8699
/// * `phrase` - Mnemonic phrase
87100
/// * `password` - Use empty string for no password
101+
/// * `language_code` - The language to use, valid values are: 'en', 'zh-hans', 'zh-hant', 'fr', 'it', 'jap', 'ko', 'es'. Defaults to 'en'
88102
///
89103
/// # Returns
90104
///
91105
/// Returns a 32-bytes seed
92106
#[pyfunction]
93-
#[text_signature = "(phrase, password)"]
94-
pub fn bip39_to_seed(phrase: &str, password: &str) -> PyResult<Vec<u8>> {
95-
let mnemonic = match Mnemonic::from_phrase(phrase, Language::English) {
107+
#[text_signature = "(phrase, password, language_code, /)"]
108+
pub fn bip39_to_seed(phrase: &str, password: &str, language_code: Option<&str>) -> PyResult<Vec<u8>> {
109+
110+
let language = match Language::from_language_code(language_code.unwrap_or("en")) {
111+
Some(language) => language,
112+
None => return Err(exceptions::ValueError::py_err("Invalid language_code"))
113+
};
114+
115+
let mnemonic = match Mnemonic::from_phrase(phrase, language) {
96116
Ok(some_mnemomic) => some_mnemomic,
97117
Err(err) => return Err(exceptions::ValueError::py_err(format!("Invalid mnemonic: {}", err.to_string())))
98118
};
@@ -108,16 +128,22 @@ pub fn bip39_to_seed(phrase: &str, password: &str) -> PyResult<Vec<u8>> {
108128
/// # Arguments
109129
///
110130
/// * `phrase` - Mnemonic phrase
131+
/// * `language_code` - The language to use, valid values are: 'en', 'zh-hans', 'zh-hant', 'fr', 'it', 'jap', 'ko', 'es'. Defaults to 'en'
111132
///
112133
/// # Returns
113134
///
114135
/// Returns boolean with validation result
115136
#[pyfunction]
116-
#[text_signature = "(phrase)"]
117-
pub fn bip39_validate(phrase: &str) -> bool {
118-
match Mnemonic::validate(phrase, Language::English) {
119-
Err(_) => false,
120-
_ => true
137+
#[text_signature = "(phrase, language_code, /)"]
138+
pub fn bip39_validate(phrase: &str, language_code: Option<&str>) -> PyResult<bool> {
139+
let language = match Language::from_language_code(language_code.unwrap_or("en")) {
140+
Some(language) => language,
141+
None => return Err(exceptions::ValueError::py_err("Invalid language_code"))
142+
};
143+
144+
match Mnemonic::validate(phrase, language) {
145+
Err(_) => Ok(false),
146+
_ => Ok(true)
121147
}
122148
}
123149

tests.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,70 @@ def test_generate_mnemonic(self):
3030
mnemonic = bip39.bip39_generate(12)
3131
self.assertTrue(bip39.bip39_validate(mnemonic))
3232

33+
def test_generate_mnemonic_french(self):
34+
mnemonic = bip39.bip39_generate(12, 'fr')
35+
self.assertTrue(bip39.bip39_validate(mnemonic, 'fr'))
36+
3337
def test_generate_invalid_mnemonic(self):
3438
self.assertRaises(ValueError, bip39.bip39_generate, 13)
3539

3640
def test_validate_mnemonic(self):
3741
self.assertTrue(bip39.bip39_validate(self.mnemonic))
3842

43+
def test_validate_mnemonic_zh_hans(self):
44+
self.assertTrue(bip39.bip39_validate('观 敲 荣 硬 责 雪 专 宴 醇 飞 图 菌', 'zh-hans'))
45+
46+
def test_validate_mnemonic_fr(self):
47+
self.assertTrue(bip39.bip39_validate(
48+
'moufle veinard tronc magasin merle amour toboggan admettre biotype décembre régalien billard', 'fr'
49+
))
50+
3951
def test_invalidate_mnemonic(self):
4052
self.assertFalse(bip39.bip39_validate("invalid mnemonic"))
4153

4254
def test_mini_seed(self):
4355
self.assertEqual(self.mini_secret, bip39.bip39_to_mini_secret(self.mnemonic, ''))
4456

57+
def test_mini_seed_zh_hans(self):
58+
59+
mini_secret = bip39.bip39_to_mini_secret('观 敲 荣 硬 责 雪 专 宴 醇 飞 图 菌', '', 'zh-hans')
60+
self.assertEqual(
61+
[60, 215, 169, 79, 32, 218, 203, 59, 53, 155, 18, 234, 160, 215, 97, 30, 176, 243, 224, 103, 240, 114, 170,
62+
26, 4, 63, 250, 164, 88, 148, 41, 68], mini_secret)
63+
4564
def test_invalid_mini_seed(self):
4665
self.assertRaises(ValueError, bip39.bip39_to_mini_secret, 'invalid mnemonic', '')
4766

4867
def test_seed(self):
4968
self.assertEqual(self.seed, bip39.bip39_to_seed(self.mnemonic, ''))
5069

70+
def test_seed_zh_hans(self):
71+
mnemonic = '旅 滨 昂 园 扎 点 郎 能 指 死 爬 根'
72+
seed = bip39.bip39_to_seed(mnemonic, '', 'zh-hans')
73+
74+
self.assertEqual(
75+
'3e349679fd7fb457810d578a8b63237c6ba1fd09b39d7f33650c0f879a2cdc46',
76+
bytes(seed).hex()
77+
)
78+
79+
def test_seed_fr(self):
80+
mnemonic = 'moufle veinard tronc magasin merle amour toboggan admettre biotype décembre régalien billard'
81+
seed = bip39.bip39_to_seed(mnemonic, '', 'fr')
82+
83+
self.assertEqual(
84+
'fe7ca72e2de46c24f121cf649057202ffdd9a51e63fc9fd98f8614fc68c6bbff',
85+
bytes(seed).hex()
86+
)
87+
5188
def test_invalid_seed(self):
5289
self.assertRaises(ValueError, bip39.bip39_to_seed, 'invalid mnemonic', '')
5390

91+
def test_invalid_language_code(self):
92+
with self.assertRaises(ValueError) as e:
93+
bip39.bip39_generate(12, "unknown")
94+
95+
self.assertEqual('Invalid language_code', str(e.exception))
96+
5497

5598
if __name__ == '__main__':
5699
unittest.main()

0 commit comments

Comments
 (0)