mirror of https://github.com/CympleTech/ESSE.git
17 changed files with 541 additions and 324 deletions
@ -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; |
||||
} |
||||
} |
@ -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; |
@ -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, |
||||
} |
||||
} |
||||
} |
@ -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))) |
||||
}, |
||||
); |
||||
} |
Loading…
Reference in new issue