Browse Source

BREAKING: move mnemonic to rust

pull/18/head
Sun 4 years ago
parent
commit
2964d2c1dc
  1. 3
      Cargo.toml
  2. 118
      lib/account.dart
  3. 10
      lib/l10n/localizations.dart
  4. 20
      lib/l10n/localizations_en.dart
  5. 20
      lib/l10n/localizations_zh.dart
  6. 36
      lib/pages/account_generate.dart
  7. 25
      lib/pages/account_quick.dart
  8. 41
      lib/pages/account_restore.dart
  9. 266
      lib/utils/mnemonic.dart
  10. 91
      src/account.rs
  11. 2
      src/apps.rs
  12. 6
      src/apps/wallet/mod.rs
  13. 69
      src/apps/wallet/models.rs
  14. 72
      src/apps/wallet/rpc.rs
  15. 16
      src/group.rs
  16. 3
      src/migrate/account.rs
  17. 67
      src/rpc.rs

3
Cargo.toml

@ -33,8 +33,9 @@ aes-gcm = "0.8" @@ -33,8 +33,9 @@ aes-gcm = "0.8"
sysinfo = "0.16"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
web3 = { version = "0.17", default-features = false, features = ["http-tls", "signing"] }
tdn = { version = "0.5", default-features = false, features = ["full"] }
tdn_did = { git = "https://github.com/cypherlink/tdn_did", branch="main" }
tdn_did = { version = "0.5" }
tdn_storage = { git = "https://github.com/cypherlink/tdn_storage", branch="main" }
group-chat_types = { git = "https://github.com/cympletech/esse_types", branch="main" }
domain_types = { git = "https://github.com/cympletech/esse_types", branch="main" }

118
lib/account.dart

@ -1,10 +1,128 @@ @@ -1,10 +1,128 @@
import 'package:flutter/material.dart';
import 'dart:ui' show Locale;
import 'dart:convert';
import 'dart:typed_data';
import 'package:esse/l10n/localizations.dart';
import 'package:esse/widgets/avatar.dart';
final MNEMONIC_LANGUAGE = [
Language.English,
Language.SimplifiedChinese,
Language.TraditionalChinese,
Language.French,
Language.Italian,
Language.Japanese,
Language.Korean,
Language.Spanish,
Language.Portuguese,
Language.Czech,
];
enum Language {
English,
SimplifiedChinese,
TraditionalChinese,
Czech,
French,
Italian,
Japanese,
Korean,
Spanish,
Portuguese,
}
extension LanguageExtension on Language {
String localizations(BuildContext context) {
switch (this) {
case Language.English:
return AppLocalizations.of(context).english;
case Language.SimplifiedChinese:
return AppLocalizations.of(context).simplifiedChinese;
case Language.TraditionalChinese:
return AppLocalizations.of(context).traditionalChinese;
case Language.Czech:
return AppLocalizations.of(context).czech;
case Language.French:
return AppLocalizations.of(context).french;
case Language.Italian:
return AppLocalizations.of(context).italian;
case Language.Japanese:
return AppLocalizations.of(context).japanese;
case Language.Korean:
return AppLocalizations.of(context).korean;
case Language.Spanish:
return AppLocalizations.of(context).spanish;
case Language.Portuguese:
return AppLocalizations.of(context).portuguese;
}
}
int toInt() {
switch (this) {
case Language.English:
return 0;
case Language.SimplifiedChinese:
return 1;
case Language.TraditionalChinese:
return 2;
case Language.Czech:
return 3;
case Language.French:
return 4;
case Language.Italian:
return 5;
case Language.Japanese:
return 6;
case Language.Korean:
return 7;
case Language.Spanish:
return 8;
case Language.Portuguese:
return 9;
}
}
static Language fromInt(int a) {
switch (a) {
case 0:
return Language.English;
case 1:
return Language.SimplifiedChinese;
case 2:
return Language.TraditionalChinese;
case 3:
return Language.Czech;
case 4:
return Language.French;
case 5:
return Language.Italian;
case 6:
return Language.Japanese;
case 7:
return Language.Korean;
case 8:
return Language.Spanish;
case 9:
return Language.Portuguese;
default:
return Language.English;
}
}
static Language fromLocale(Locale locale) {
switch (locale.languageCode) {
case 'en':
return Language.English;
case 'zh':
return Language.SimplifiedChinese;
default:
return Language.English;
}
}
}
class Account {
String gid = '';
String name = '';

10
lib/l10n/localizations.dart

@ -110,6 +110,16 @@ abstract class AppLocalizations { @@ -110,6 +110,16 @@ abstract class AppLocalizations {
// langs
String get lang;
String get english;
String get simplifiedChinese;
String get traditionalChinese;
String get czech;
String get french;
String get italian;
String get japanese;
String get korean;
String get spanish;
String get portuguese;
// security page (did)
String get loginChooseAccount;

20
lib/l10n/localizations_en.dart

@ -140,6 +140,26 @@ class AppLocalizationsEn extends AppLocalizations { @@ -140,6 +140,26 @@ class AppLocalizationsEn extends AppLocalizations {
// langs
@override
String get lang => 'Language';
@override
String get english => 'English';
@override
String get simplifiedChinese => 'Simplified Chinese';
@override
String get traditionalChinese => 'Traditional Chinese';
@override
String get czech => 'Czech';
@override
String get french => 'French';
@override
String get italian => 'Italian';
@override
String get japanese => 'Japanese';
@override
String get korean => 'Korean';
@override
String get spanish => 'Spanish';
@override
String get portuguese => 'Portuguese';
// security page (did)
@override

20
lib/l10n/localizations_zh.dart

@ -140,6 +140,26 @@ class AppLocalizationsZh extends AppLocalizations { @@ -140,6 +140,26 @@ class AppLocalizationsZh extends AppLocalizations {
// langs
@override
String get lang => '语言';
@override
String get english => '英语';
@override
String get simplifiedChinese => '简体中文';
@override
String get traditionalChinese => '繁体中文';
@override
String get czech => '捷克语';
@override
String get french => '法语';
@override
String get italian => '意大利语';
@override
String get japanese => '日语';
@override
String get korean => '韩语';
@override
String get spanish => '西班牙语';
@override
String get portuguese => '葡萄牙语';
// security page (did)
@override

36
lib/pages/account_generate.dart

@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; @@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:esse/l10n/localizations.dart';
import 'package:esse/utils/mnemonic.dart';
import 'package:esse/utils/device_info.dart';
import 'package:esse/widgets/button_text.dart';
import 'package:esse/widgets/shadow_dialog.dart';
@ -30,7 +29,7 @@ class AccountGeneratePage extends StatefulWidget { @@ -30,7 +29,7 @@ class AccountGeneratePage extends StatefulWidget {
}
class _AccountGeneratePageState extends State<AccountGeneratePage> {
int _selectedMnemonicLang = 1;
Language _selectedLang = Language.English;
String _mnemoicWords = "";
bool _mnemonicChecked = false;
@ -69,10 +68,15 @@ class _AccountGeneratePageState extends State<AccountGeneratePage> { @@ -69,10 +68,15 @@ class _AccountGeneratePageState extends State<AccountGeneratePage> {
}
void genMnemonic() async {
final lang = MnemonicLangExtension.fromInt(_selectedMnemonicLang);
this._mnemoicWords = await generateMnemonic(lang: lang);
this._mnemonicChecked = true;
setState(() {});
final res = await httpPost(Global.httpRpc, 'account-generate', [_selectedLang.toInt()]);
if (res.isOk) {
this._mnemoicWords = res.params[0];
this._mnemonicChecked = true;
setState(() {});
} else {
// TODO tostor error
print(res.error);
}
}
void registerNewAction(String title) async {
@ -94,7 +98,9 @@ class _AccountGeneratePageState extends State<AccountGeneratePage> { @@ -94,7 +98,9 @@ class _AccountGeneratePageState extends State<AccountGeneratePage> {
Navigator.of(context).pop();
// send to core node service by rpc.
final res = await httpPost(Global.httpRpc,
'account-create', [name, lock, mnemonic, avatar, info[0], info[1]]);
'account-create', [
_selectedLang.toInt(), mnemonic, "", name, lock, avatar, info[0], info[1]
]);
if (res.isOk) {
// save this User
@ -160,22 +166,22 @@ class _AccountGeneratePageState extends State<AccountGeneratePage> { @@ -160,22 +166,22 @@ class _AccountGeneratePageState extends State<AccountGeneratePage> {
data: Theme.of(context).copyWith(
canvasColor: color.surface,
),
child: DropdownButton<int>(
child: DropdownButton<Language>(
hint: Text(lang.loginChooseAccount,
style: TextStyle(fontSize: 16)),
iconEnabledColor: Color(0xFFADB0BB),
value: _selectedMnemonicLang,
onChanged: (int? m) {
value: _selectedLang,
onChanged: (Language? m) {
if (m != null) {
setState(() {
_selectedMnemonicLang = m;
_selectedLang = m;
});
}
},
items: MNEMONIC_LANGS.map((MnemonicLang m) {
return DropdownMenuItem<int>(
value: m.toInt(),
child: Text(m.localizations(),
items: MNEMONIC_LANGUAGE.map((Language m) {
return DropdownMenuItem<Language>(
value: m,
child: Text(m.localizations(context),
style: TextStyle(fontSize: 16)));
}).toList(),
)),

25
lib/pages/account_quick.dart

@ -7,7 +7,6 @@ import 'package:flutter/material.dart'; @@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:esse/l10n/localizations.dart';
import 'package:esse/utils/mnemonic.dart';
import 'package:esse/utils/device_info.dart';
import 'package:esse/widgets/button_text.dart';
import 'package:esse/widgets/shadow_dialog.dart';
@ -113,13 +112,24 @@ class _AccountQuickPageState extends State<AccountQuickPage> { @@ -113,13 +112,24 @@ class _AccountQuickPageState extends State<AccountQuickPage> {
);
}
Future<String> _getMnemonic(Locale locale) async {
final lang = MnemonicLangExtension.fromLocale(locale);
return await generateMnemonic(lang: lang);
Future<List?> _getMnemonic(Locale locale) async {
final language = LanguageExtension.fromLocale(locale);
final res = await httpPost(Global.httpRpc, 'account-generate', [language.toInt()]);
if (res.isOk) {
return [language, res.params[0]];
} else {
// TODO tostor error
print(res.error);
}
}
void registerNewAction(Locale locale) async {
final mnemonic = await _getMnemonic(locale);
final lang_mnemonic = await _getMnemonic(locale);
if (lang_mnemonic == null) {
return;
}
final Language language = lang_mnemonic[0];
final String mnemonic = lang_mnemonic[1];
final name = _nameController.text;
final avatar = _imageBytes != null ? base64.encode(_imageBytes!) : "";
final info = await deviceInfo();
@ -130,8 +140,9 @@ class _AccountQuickPageState extends State<AccountQuickPage> { @@ -130,8 +140,9 @@ class _AccountQuickPageState extends State<AccountQuickPage> {
}
// send to core node service by rpc.
final res = await httpPost(Global.httpRpc,
'account-create', [name, lock, mnemonic, avatar, info[0], info[1]]);
final res = await httpPost(Global.httpRpc, 'account-create', [
language.toInt(), mnemonic, "", name, lock, avatar, info[0], info[1]
]);
if (res.isOk) {
// save this User

41
lib/pages/account_restore.dart

@ -34,6 +34,7 @@ class _AccountRestorePageState extends State<AccountRestorePage> { @@ -34,6 +34,7 @@ class _AccountRestorePageState extends State<AccountRestorePage> {
bool _addrOnline = false;
bool _addrChecked = false;
Language _selectedLang = Language.English;
TextEditingController _wordController = new TextEditingController();
FocusNode _wordFocus = new FocusNode();
List<String> _mnemoicWords = [];
@ -185,7 +186,40 @@ class _AccountRestorePageState extends State<AccountRestorePage> { @@ -185,7 +186,40 @@ class _AccountRestorePageState extends State<AccountRestorePage> {
style: TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16.0),
Container(
width: 600.0,
height: 45.0,
padding: const EdgeInsets.only(left: 20, right: 10),
margin: const EdgeInsets.symmetric(vertical: 16.0),
decoration: BoxDecoration(
color: color.surface,
borderRadius: BorderRadius.circular(10.0)),
child: DropdownButtonHideUnderline(
child: Theme(
data: Theme.of(context).copyWith(
canvasColor: color.surface,
),
child: DropdownButton<Language>(
hint: Text(lang.loginChooseAccount,
style: TextStyle(fontSize: 16)),
iconEnabledColor: Color(0xFFADB0BB),
value: _selectedLang,
onChanged: (Language? m) {
if (m != null) {
setState(() {
_selectedLang = m;
});
}
},
items: MNEMONIC_LANGUAGE.map((Language m) {
return DropdownMenuItem<Language>(
value: m,
child: Text(m.localizations(context),
style: TextStyle(fontSize: 16)));
}).toList(),
)),
),
),
Container(
width: 600.0,
alignment: Alignment.center,
@ -370,8 +404,9 @@ class _AccountRestorePageState extends State<AccountRestorePage> { @@ -370,8 +404,9 @@ class _AccountRestorePageState extends State<AccountRestorePage> {
callback: (key, lock) async {
Navigator.of(context).pop();
// send to core node service by rpc.
final res = await httpPost(Global.httpRpc, 'account-restore',
[this._name, lock, mnemonic, addr, info[0], info[1]]);
final res = await httpPost(Global.httpRpc, 'account-restore', [
_selectedLang.toInt(), mnemonic, "", this._name, lock, addr, info[0], info[1]
]);
if (res.isOk) {
// save this User

266
lib/utils/mnemonic.dart

@ -1,266 +0,0 @@ @@ -1,266 +0,0 @@
import 'dart:async';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui' show Locale;
import 'package:flutter/services.dart' show rootBundle;
import 'package:crypto/crypto.dart';
import 'package:unorm_dart/unorm_dart.dart';
enum MnemonicLang {
NONE,
CHINESE_SIMPLIFIED,
CHINESE_TRADITIONAL,
ENGLISH,
FRENCH,
ITALIAN,
JAPANESE,
KOREAN,
SPANISH,
}
final MNEMONIC_LANGS = [
MnemonicLang.ENGLISH,
MnemonicLang.CHINESE_SIMPLIFIED,
];
final MNEMONIC_LANGS_NO_DEFAULT = [
MnemonicLang.NONE,
MnemonicLang.ENGLISH,
MnemonicLang.CHINESE_SIMPLIFIED,
];
extension MnemonicLangExtension on MnemonicLang {
String localizations() {
switch (this) {
case MnemonicLang.NONE:
return '';
case MnemonicLang.CHINESE_SIMPLIFIED:
return '简体中文';
case MnemonicLang.ENGLISH:
return 'English';
default:
return 'English';
}
}
int toInt() {
switch (this) {
case MnemonicLang.ENGLISH:
return 1;
case MnemonicLang.CHINESE_SIMPLIFIED:
return 2;
default:
return 0;
}
}
static MnemonicLang fromInt(int a) {
switch (a) {
case 1:
return MnemonicLang.ENGLISH;
case 2:
return MnemonicLang.CHINESE_SIMPLIFIED;
default:
return MnemonicLang.NONE;
}
}
static MnemonicLang fromLocale(Locale locale) {
switch (locale.languageCode) {
case 'en':
return MnemonicLang.ENGLISH;
case 'zh':
return MnemonicLang.CHINESE_SIMPLIFIED;
default:
return MnemonicLang.ENGLISH;
}
}
}
final _langCache = Map<MnemonicLang, List<String>>();
const MnemonicLang _DEFAULT_LANG = MnemonicLang.ENGLISH;
const int _SIZE_8BITS = 255;
const String _INVALID_ENTROPY = 'Invalid entropy';
const String _INVALID_MNEMONIC = 'Invalid mnemonic';
const String _INVALID_CHECKSUM = 'Invalid checksum';
String _getMnemonicLangName(MnemonicLang lang) {
switch (lang) {
case MnemonicLang.CHINESE_SIMPLIFIED:
return 'chinese_simplified';
case MnemonicLang.CHINESE_TRADITIONAL:
return 'chinese_traditional';
case MnemonicLang.ENGLISH:
return 'english';
case MnemonicLang.FRENCH:
return 'french';
case MnemonicLang.ITALIAN:
return 'italian';
case MnemonicLang.JAPANESE:
return 'japanese';
case MnemonicLang.KOREAN:
return 'korean';
case MnemonicLang.SPANISH:
return 'spanish';
default:
return 'english';
}
}
int _binaryToByte(String binary) {
return int.parse(binary, radix: 2);
}
String _bytesToBinary(Uint8List bytes) {
return bytes.map((byte) => byte.toRadixString(2).padLeft(8, '0')).join('');
}
String _deriveChecksumBits(Uint8List entropy) {
final ENT = entropy.length * 8;
final CS = ENT ~/ 32;
final hash = sha256.convert(entropy);
return _bytesToBinary(Uint8List.fromList(hash.bytes)).substring(0, CS);
}
typedef Uint8List RandomBytes(int size);
Uint8List _nextBytes(int size) {
final rnd = Random.secure();
final bytes = Uint8List(size);
for (var i = 0; i < size; i++) {
bytes[i] = rnd.nextInt(_SIZE_8BITS);
}
return bytes;
}
/// Converts [mnemonic] code to entropy.
Future<Uint8List> mnemonicToEntropy(String mnemonic, [MnemonicLang lang = _DEFAULT_LANG]) async {
if (lang == MnemonicLang.NONE) {
return Uint8List(0);
}
final wordRes = await _loadMnemonicLang(lang);
final words = nfkd(mnemonic).split(' ');
if (words.length % 3 != 0) {
throw new ArgumentError(_INVALID_MNEMONIC);
}
// convert word indices to 11bit binary strings
final bits = words.map((word) {
final index = wordRes.indexOf(word);
if (index == -1) {
throw ArgumentError(_INVALID_MNEMONIC);
}
return index.toRadixString(2).padLeft(11, '0');
}).join('');
// split the binary string into ENT/CS
final dividerIndex = (bits.length / 33).floor() * 32;
final entropyBits = bits.substring(0, dividerIndex);
final checksumBits = bits.substring(dividerIndex);
final regex = RegExp(r".{1,8}");
final entropyBytes = Uint8List.fromList(regex
.allMatches(entropyBits)
.map((match) => _binaryToByte(match.group(0)!))
.toList(growable: false));
if (entropyBytes.length < 16) {
throw StateError(_INVALID_ENTROPY);
}
if (entropyBytes.length > 32) {
throw StateError(_INVALID_ENTROPY);
}
if (entropyBytes.length % 4 != 0) {
throw StateError(_INVALID_ENTROPY);
}
final newCheckSum = _deriveChecksumBits(entropyBytes);
if (newCheckSum != checksumBits) {
throw StateError(_INVALID_CHECKSUM);
}
return entropyBytes;
}
/// Converts [entropy] to mnemonic code.
Future<String> entropyToMnemonic(Uint8List entropy,
[MnemonicLang lang = _DEFAULT_LANG]) async {
if (lang == MnemonicLang.NONE) {
return "";
}
if (entropy.length < 16) {
throw ArgumentError(_INVALID_ENTROPY);
}
if (entropy.length > 32) {
throw ArgumentError(_INVALID_ENTROPY);
}
if (entropy.length % 4 != 0) {
throw ArgumentError(_INVALID_ENTROPY);
}
final entropyBits = _bytesToBinary(entropy);
final checksumBits = _deriveChecksumBits(entropy);
final bits = entropyBits + checksumBits;
final regex = new RegExp(r".{1,11}", caseSensitive: false, multiLine: false);
final chunks = regex
.allMatches(bits)
.map((match) => match.group(0))
.toList(growable: false);
final wordRes = await _loadMnemonicLang(lang);
return chunks
.map((binary) => wordRes[_binaryToByte(binary!)])
.join(lang == MnemonicLang.JAPANESE ? '\u3000' : ' ');
}
/// Generates a random mnemonic.
///
/// Defaults to 128-bits of entropy.
/// By default it uses [Random.secure()] under the food to get random bytes,
/// but you can swap RNG by providing [randomBytes].
/// Default lang is English, but you can use different lang by providing [lang].
Future<String> generateMnemonic({
int strength = 128,
RandomBytes randomBytes = _nextBytes,
MnemonicLang lang = _DEFAULT_LANG,
}) async {
if (lang == MnemonicLang.NONE) {
return "";
}
assert(strength % 32 == 0);
final entropy = randomBytes(strength ~/ 8);
return await entropyToMnemonic(entropy, lang);
}
Future<List<String>> _loadMnemonicLang(MnemonicLang lang) async {
if (_langCache.containsKey(lang)) {
return _langCache[lang]!;
} else {
final rawWords = await rootBundle
.loadString('assets/mnemonic/${_getMnemonicLangName(lang)}.txt');
final result = rawWords
.split('\n')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toList(growable: false);
_langCache[lang] = result;
return result;
}
}

91
src/account.rs

@ -5,17 +5,51 @@ use tdn::types::{ @@ -5,17 +5,51 @@ use tdn::types::{
group::{EventId, GroupId},
primitive::Result,
};
use tdn_did::{genereate_id, Keypair};
use tdn_did::{generate_id, Keypair, Language};
use tdn_storage::local::{DStorage, DsValue};
fn mnemonic_lang_to_i64(lang: Language) -> i64 {
match lang {
Language::English => 0,
Language::SimplifiedChinese => 1,
Language::TraditionalChinese => 2,
Language::Czech => 3,
Language::French => 4,
Language::Italian => 5,
Language::Japanese => 6,
Language::Korean => 7,
Language::Spanish => 8,
Language::Portuguese => 9,
}
}
pub fn mnemonic_lang_from_i64(u: i64) -> Language {
match u {
0 => Language::English,
1 => Language::SimplifiedChinese,
2 => Language::TraditionalChinese,
3 => Language::Czech,
4 => Language::French,
5 => Language::Italian,
6 => Language::Japanese,
7 => Language::Korean,
8 => Language::Spanish,
9 => Language::Portuguese,
_ => Language::English,
}
}
pub(crate) struct Account {
pub id: i64,
pub gid: GroupId,
pub name: String,
pub lock: String, // hashed-key.
pub index: i64,
pub lang: i64,
pub mnemonic: Vec<u8>, // encrypted value.
pub secret: Vec<u8>, // encrypted value.
pub pass: String,
pub name: String,
pub avatar: Vec<u8>,
pub lock: String, // hashed-key.
pub secret: Vec<u8>, // encrypted value.
pub height: u64,
pub event: EventId,
pub datetime: i64,
@ -24,9 +58,12 @@ pub(crate) struct Account { @@ -24,9 +58,12 @@ pub(crate) struct Account {
impl Account {
pub fn new(
gid: GroupId,
index: i64,
lang: i64,
pass: String,
name: String,
avatar: Vec<u8>,
lock: String,
avatar: Vec<u8>,
mnemonic: Vec<u8>,
secret: Vec<u8>,
) -> Self {
@ -41,6 +78,9 @@ impl Account { @@ -41,6 +78,9 @@ impl Account {
height: 0,
event: EventId::default(),
gid,
index,
lang,
pass,
name,
lock,
mnemonic,
@ -50,14 +90,27 @@ impl Account { @@ -50,14 +90,27 @@ impl Account {
}
}
pub fn lang(&self) -> Language {
mnemonic_lang_from_i64(self.lang)
}
pub fn generate(
index: u32,
skey: &[u8], // &[u8; 32]
name: &str,
lang: i64,
mnemonic: &str,
pass: &str,
name: &str,
lock: &str,
avatar: Vec<u8>,
) -> Result<(Account, Keypair)> {
let (gid, sk) = genereate_id(mnemonic.as_bytes());
let (gid, sk) = generate_id(
mnemonic_lang_from_i64(lang),
mnemonic,
index,
0, // account default multiple address index is 0.
if pass.len() > 0 { Some(pass) } else { None },
)?;
let cipher = Aes256Gcm::new(GenericArray::from_slice(skey)); // 256-bit key.
let hash_nonce = blake3::hash(lock.as_bytes());
@ -74,9 +127,12 @@ impl Account { @@ -74,9 +127,12 @@ impl Account {
Ok((
Account::new(
gid,
index as i64,
lang,
pass.to_string(),
name.to_string(),
avatar,
lock.to_string(),
avatar,
mnemonic_bytes,
sk_bytes,
),
@ -153,6 +209,9 @@ impl Account { @@ -153,6 +209,9 @@ impl Account {
mnemonic: base64::decode(v.pop().unwrap().as_string()).unwrap_or(vec![]),
lock: v.pop().unwrap().as_string(),
name: v.pop().unwrap().as_string(),
pass: v.pop().unwrap().as_string(),
lang: v.pop().unwrap().as_i64(),
index: v.pop().unwrap().as_i64(),
gid: GroupId::from_hex(v.pop().unwrap().as_str()).unwrap_or(GroupId::default()),
id: v.pop().unwrap().as_i64(),
}
@ -160,13 +219,13 @@ impl Account { @@ -160,13 +219,13 @@ impl Account {
pub fn _get(db: &DStorage, gid: &GroupId) -> Result<Option<Account>> {
let sql = format!(
"SELECT id, gid, name, lock, mnemonic, secret, avatar, height, event, datetime FROM accounts WHERE gid = '{}'",
"SELECT id, gid, indx, lang, pass, name, lock, mnemonic, secret, avatar, height, event, datetime FROM accounts WHERE gid = '{}'",
gid.to_hex()
);
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
if values.len() == 10 {
if values.len() == 13 {
return Ok(Some(Account::from_values(values)));
}
}
@ -175,11 +234,11 @@ impl Account { @@ -175,11 +234,11 @@ impl Account {
pub fn all(db: &DStorage) -> Result<Vec<Account>> {
let matrix = db.query(
"SELECT id, gid, name, lock, mnemonic, secret, avatar, height, event, datetime FROM accounts ORDER BY datetime DESC",
"SELECT id, gid, indx, lang, pass, name, lock, mnemonic, secret, avatar, height, event, datetime FROM accounts ORDER BY datetime DESC",
)?;
let mut accounts = vec![];
for values in matrix {
if values.len() == 10 {
if values.len() == 13 {
accounts.push(Account::from_values(values));
}
}
@ -196,8 +255,11 @@ impl Account { @@ -196,8 +255,11 @@ impl Account {
self.id = id;
self.update(db)?;
} else {
let sql = format!("INSERT INTO accounts (gid, name, lock, mnemonic, secret, avatar, height,event, datetime) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', {}, '{}', {})",
let sql = format!("INSERT INTO accounts (gid, indx, lang, pass, name, lock, mnemonic, secret, avatar, height,event, datetime) VALUES ('{}', {}, {}, '{}', '{}', '{}', '{}', '{}', '{}', {}, '{}', {})",
self.gid.to_hex(),
self.index,
self.lang,
self.pass,
self.name,
self.lock,
base64::encode(&self.mnemonic),
@ -214,8 +276,7 @@ impl Account { @@ -214,8 +276,7 @@ impl Account {
}
pub fn update(&self, db: &DStorage) -> Result<usize> {
let sql = format!("UPDATE accounts SET gid='{}', name='{}', lock='{}', mnemonic='{}', secret='{}', avatar='{}', height={}, event='{}', datetime={} WHERE id = {}",
self.gid.to_hex(),
let sql = format!("UPDATE accounts SET name='{}', lock='{}', mnemonic='{}', secret='{}', avatar='{}', height={}, event='{}', datetime={} WHERE id = {}",
self.name,
self.lock,
base64::encode(&self.mnemonic),

2
src/apps.rs

@ -16,6 +16,7 @@ pub(crate) mod device; @@ -16,6 +16,7 @@ pub(crate) mod device;
pub(crate) mod domain;
pub(crate) mod file;
pub(crate) mod group_chat;
pub(crate) mod wallet;
pub(crate) fn app_rpc_inject(handler: &mut RpcHandler<RpcState>) {
device::new_rpc_handler(handler);
@ -24,6 +25,7 @@ pub(crate) fn app_rpc_inject(handler: &mut RpcHandler<RpcState>) { @@ -24,6 +25,7 @@ pub(crate) fn app_rpc_inject(handler: &mut RpcHandler<RpcState>) {
domain::new_rpc_handler(handler);
file::new_rpc_handler(handler);
group_chat::new_rpc_handler(handler);
wallet::new_rpc_handler(handler);
}
pub(crate) async fn app_layer_handle(

6
src/apps/wallet/mod.rs

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
mod models;
mod rpc;
pub const ETH_NODE: &'static str = "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161";
pub(crate) use rpc::new_rpc_handler;

69
src/apps/wallet/models.rs

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
use rand::Rng;
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
primitive::Result,
rpc::{json, RpcParam},
};
use tdn_storage::local::{DStorage, DsValue};
pub(crate) enum ChainToken {
ETH,
ERC20,
ERC721,
BTC,
}
impl ChainToken {
fn to_i64(&self) -> i64 {
match self {
ChainToken::ETH => 1,
ChainToken::ERC20 => 2,
ChainToken::ERC721 => 3,
ChainToken::BTC => 4,
}
}
pub fn from_i64(i: i64) -> Self {
match i {
1 => ChainToken::ETH,
2 => ChainToken::ERC20,
3 => ChainToken::ERC721,
4 => ChainToken::BTC,
_ => ChainToken::ETH,
}
}
}
pub(crate) struct Address {
pub id: i64,
pub index: i64,
pub chain: ChainToken,
pub address: String,
pub public_key: String,
pub secret_key: String,
}
pub(crate) struct Token {
pub id: i64,
pub chain: ChainToken,
pub contract: String,
pub decimal: i64,
}
impl Address {
pub fn generate(index: i64, chain: ChainToken) -> Self {
let address = String::new();
let public_key = String::new();
let secret_key = String::new();
Self {
index,
chain,
address,
public_key,
secret_key,
id: 0,
}
}
}

72
src/apps/wallet/rpc.rs

@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
use std::sync::Arc;
use tdn::types::{
group::GroupId,
primitive::HandleResult,
rpc::{json, RpcError, RpcHandler, RpcParam},
};
use tdn_did::generate_eth_account;
use web3::signing::Key;
use crate::rpc::RpcState;
use super::{models::ChainToken, ETH_NODE};
pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
handler.add_method("wallet-echo", |_, params, _| async move {
Ok(HandleResult::rpc(json!(params)))
});
handler.add_method(
"wallet-generate",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let lock = params[0].as_str().ok_or(RpcError::ParseError)?;
let group_lock = state.group.read().await;
let mnemonic = group_lock.mnemonic(&gid, lock)?;
let account = group_lock.account(&gid)?;
let lang = account.lang();
let pass = account.pass.to_string();
let account_index = account.index as u32;
drop(group_lock);
let pass = if pass.len() > 0 {
Some(pass.as_ref())
} else {
None
};
let index = 0; // TOOD
let sk = generate_eth_account(lang, &mnemonic, account_index, index, pass)?;
let address = (&sk).address();
println!("{:?}", address);
Ok(HandleResult::rpc(json!([mnemonic])))
},
);
handler.add_method(
"wallet-balance",
|_gid: GroupId, params: Vec<RpcParam>, _state: Arc<RpcState>| async move {
let ctoken = ChainToken::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?);
let address = params[0].as_str().ok_or(RpcError::ParseError)?;
match ctoken {
ChainToken::ETH => {
let transport = web3::transports::Http::new(ETH_NODE).unwrap();
let web3 = web3::Web3::new(transport);
let balance = web3
.eth()
.balance(address.parse().unwrap(), None)
.await
.unwrap();
println!("Balance of {:?}: {}", address, balance);
}
ChainToken::ERC20 => {}
ChainToken::ERC721 => {}
_ => {}
}
Ok(HandleResult::rpc(json!(params)))
},
);
}

16
src/group.rs

@ -423,14 +423,26 @@ impl Group { @@ -423,14 +423,26 @@ impl Group {
pub async fn add_account(
&mut self,
name: &str,
lang: i64,
seed: &str,
pass: &str,
name: &str,
lock: &str,
avatar_bytes: Vec<u8>,
device_name: &str,
device_info: &str,
) -> Result<(i64, GroupId)> {
let (mut account, sk) = Account::generate(&self.secret, name, seed, lock, avatar_bytes)?;
let account_index = self.accounts.len() as u32;
let (mut account, sk) = Account::generate(
account_index,
&self.secret,
lang,
seed,
pass,
name,
lock,
avatar_bytes,
)?;
let account_id = account.gid;
if let Some(u) = self.accounts.get(&account_id) {

3
src/migrate/account.rs

@ -3,6 +3,9 @@ pub(super) const ACCOUNT_VERSIONS: [&str; 9] = [ @@ -3,6 +3,9 @@ pub(super) const ACCOUNT_VERSIONS: [&str; 9] = [
"CREATE TABLE IF NOT EXISTS accounts(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
gid TEXT NOT NULL,
indx INTEGER NOT NULL,
lang INTEGER NOT NULL,
pass TEXT NOT NULL,
name TEXT NOT NULL,
lock TEXT NOT NULL,
secret TEXT NOT NULL,

67
src/rpc.rs

@ -7,6 +7,7 @@ use tdn::types::{ @@ -7,6 +7,7 @@ use tdn::types::{
primitive::{HandleResult, PeerAddr, Result},
rpc::{json, rpc_response, RpcError, RpcHandler, RpcParam},
};
use tdn_did::{generate_mnemonic, Count};
use tokio::sync::{
mpsc::{self, error::SendError, Sender},
RwLock,
@ -244,22 +245,45 @@ fn new_rpc_handler( @@ -244,22 +245,45 @@ fn new_rpc_handler(
},
);
handler.add_method(
"account-generate",
|_gid, params: Vec<RpcParam>, _state: Arc<RpcState>| async move {
let lang = params[0].as_i64().ok_or(RpcError::ParseError)?;
let language = crate::account::mnemonic_lang_from_i64(lang);
let words = generate_mnemonic(language, Count::Words12);
Ok(HandleResult::rpc(json!([words])))
},
);
handler.add_method(
"account-create",
|_gid, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let name = params[0].as_str().ok_or(RpcError::ParseError)?;
let lock = params[1].as_str().ok_or(RpcError::ParseError)?;
let seed = params[2].as_str().ok_or(RpcError::ParseError)?;
let avatar = params[3].as_str().ok_or(RpcError::ParseError)?;
let device_name = params[4].as_str().ok_or(RpcError::ParseError)?;
let device_info = params[5].as_str().ok_or(RpcError::ParseError)?;
let avatar_bytes = base64::decode(avatar).unwrap_or(vec![]);
let lang = params[0].as_i64().ok_or(RpcError::ParseError)?;
let seed = params[1].as_str().ok_or(RpcError::ParseError)?;
let pass = params[2].as_str().ok_or(RpcError::ParseError)?;
let name = params[3].as_str().ok_or(RpcError::ParseError)?;
let lock = params[4].as_str().ok_or(RpcError::ParseError)?;
let avatar = params[5].as_str().ok_or(RpcError::ParseError)?;
let device_name = params[6].as_str().ok_or(RpcError::ParseError)?;
let device_info = params[7].as_str().ok_or(RpcError::ParseError)?;
let avatar_bytes = base64::decode(avatar).unwrap_or(vec![]);
let (id, gid) = state
.group
.write()
.await
.add_account(name, seed, lock, avatar_bytes, device_name, device_info)
.add_account(
lang,
seed,
pass,
name,
lock,
avatar_bytes,
device_name,
device_info,
)
.await?;
state.layer.write().await.add_running(&gid, gid, id, 0)?;
@ -275,19 +299,32 @@ fn new_rpc_handler( @@ -275,19 +299,32 @@ fn new_rpc_handler(
handler.add_method(
"account-restore",
|_gid, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let name = params[0].as_str().ok_or(RpcError::ParseError)?;
let lock = params[1].as_str().ok_or(RpcError::ParseError)?;
let seed = params[2].as_str().ok_or(RpcError::ParseError)?;
let lang = params[0].as_i64().ok_or(RpcError::ParseError)?;
let seed = params[1].as_str().ok_or(RpcError::ParseError)?;
let pass = params[2].as_str().ok_or(RpcError::ParseError)?;
let name = params[3].as_str().ok_or(RpcError::ParseError)?;
let lock = params[4].as_str().ok_or(RpcError::ParseError)?;
let some_addr =
PeerAddr::from_hex(params[3].as_str().ok_or(RpcError::ParseError)?).ok();
let device_name = params[4].as_str().ok_or(RpcError::ParseError)?;
let device_info = params[5].as_str().ok_or(RpcError::ParseError)?;
PeerAddr::from_hex(params[5].as_str().ok_or(RpcError::ParseError)?).ok();
let device_name = params[6].as_str().ok_or(RpcError::ParseError)?;
let device_info = params[7].as_str().ok_or(RpcError::ParseError)?;
let (id, gid) = state
.group
.write()
.await
.add_account(name, seed, lock, vec![], device_name, device_info)
.add_account(
lang,
seed,
pass,
name,
lock,
vec![],
device_name,
device_info,
)
.await?;
state.layer.write().await.add_running(&gid, gid, id, 0)?;

Loading…
Cancel
Save