助记词转换

This commit is contained in:
2022-10-19 17:19:56 +08:00
parent 153e28aa4e
commit 2ddccb3f9d
15 changed files with 537 additions and 43 deletions

View File

@@ -25,6 +25,10 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
compileSdkVersion flutter.compileSdkVersion
@@ -42,14 +46,30 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "site.zhchain.chat"
minSdkVersion flutter.minSdkVersion
// minSdkVersion flutter.minSdkVersion
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
debug {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
@@ -57,6 +77,18 @@ android {
signingConfig signingConfigs.debug
}
}
repositories {
flatDir {
dirs 'libs'
}
}
// 打包失败,加的配置
lintOptions {
checkReleaseBuilds false
abortOnError false
}
}
flutter {

10
android/app/key/key.md Executable file
View File

@@ -0,0 +1,10 @@
别名 jzbg
密码 zhchain
keytool -genkey -alias jzbg -keyalg RSA -keysize 2048 -validity 36500 -keystore jzbg.keystore
keytool -list -v -keystore jzbg.keystore
应用签名 ef8ecfa4e4ddae833195fd53ac9fc0b1
MD5 EF:8E:CF:A4:E4:DD:AE:83:31:95:FD:53:AC:9F:C0:B1
SHA1 0D:4C:73:E3:8D:AF:6B:D3:A3:07:0E:A4:2C:BB:BD:C8:EA:EA:17:AE

BIN
assets/transits/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 KiB

BIN
assets/transits/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

View File

@@ -1,10 +1,16 @@
import 'package:chat/middleware/auth_middleware.dart';
import 'package:chat/views/auth/index_page.dart';
import 'package:chat/views/auth/create/index_page.dart';
import 'package:chat/views/auth/create/verify_page.dart';
import 'package:chat/views/auth/import/index_page.dart';
import 'package:chat/views/auth/index/index_page.dart';
import 'package:get/get.dart';
abstract class AuthRoutes {
/// 身份验证页面
static const String index = '/auth';
static const String create = '/auth/create';
static const String createVerify = '/auth/create/verify';
static const String import = '/auth/import';
static GetPage router = GetPage(
name: AuthRoutes.index,
@@ -12,5 +18,21 @@ abstract class AuthRoutes {
EnsureNotAuthMiddleware(),
],
page: () => const AuthPage(),
children: [
GetPage(
name: '/create',
page: () => const AuthCreatePage(),
children: [
GetPage(
name: '/verify',
page: () => const AuthCreateVerifyPage(),
),
],
),
GetPage(
name: '/import',
page: () => const AuthImportPage(),
),
],
);
}

47
lib/utils/hd_wallet.dart Normal file
View File

@@ -0,0 +1,47 @@
import 'dart:typed_data';
import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39_multi_language/bip39.dart' as bip39;
import 'package:chat/utils/ui_tools.dart';
import 'package:fast_base58/fast_base58.dart';
import 'package:hash/hash.dart';
class HDWallet {
/// 将助记词转换成BTY地址
static String? mnemonicToAddress(String mnemonic) {
var li = mnemonic.split(' ');
li.removeWhere((e) => e.isEmpty);
mnemonic = li.join(' ');
if (!bip39.validateMnemonic(
mnemonic,
language: _languageDetect('mnemonic'),
)) {
UiTools.toast('不符合标准的助记词');
return null;
}
var seed = bip39.mnemonicToSeed(mnemonic);
var root = bip32.BIP32.fromSeed(seed);
var child = root.derivePath("m/44'/13107'/0'/0/0");
var step3 = SHA256().update(child.publicKey).digest();
List<int> step4 = List.from(RIPEMD160().update(step3).digest());
step4.insert(0, 0x00);
List<int> step5 = step4;
Uint8List step6 = SHA256().update(step5).digest();
Uint8List step7 = SHA256().update(step6).digest();
step5.addAll(step7.sublist(0, 4));
Uint8List step8 = Uint8List.fromList(step5);
return Base58Encode(step8);
}
/// 判断是否是英文助记词
static String _languageDetect(String str) {
if (str.startsWith(RegExp(r'[a-z]'))) {
return 'english';
} else {
return 'chinese';
}
}
}

15
lib/utils/ui_tools.dart Normal file
View File

@@ -0,0 +1,15 @@
import 'package:chat/configs/app_colors.dart';
import 'package:fluttertoast/fluttertoast.dart';
class UiTools {
/// 普通的消息提醒
static void toast(String message) {
Fluttertoast.showToast(
msg: message,
gravity: ToastGravity.CENTER,
toastLength: Toast.LENGTH_SHORT,
backgroundColor: AppColors.active.withAlpha(0x90),
fontSize: 14,
);
}
}

View File

@@ -0,0 +1,172 @@
import 'package:bip39_multi_language/bip39.dart' as bip39;
import 'package:chat/configs/app_colors.dart';
import 'package:chat/routes/auth_routes.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AuthCreatePage extends StatefulWidget {
const AuthCreatePage({Key? key}) : super(key: key);
@override
State<AuthCreatePage> createState() => _AuthCreatePageState();
}
class _AuthCreatePageState extends State<AuthCreatePage>
with SingleTickerProviderStateMixin {
late final TabController _tabController;
List<String> _englishMnemonic = [];
List<String> _chineseMnemonic = [];
@override
void initState() {
super.initState();
_tabController = TabController(
length: 2,
vsync: this,
);
_generateMnemonic();
}
void _generateMnemonic() {
String _englishBip39Str = bip39.generateMnemonic(language: 'english');
String _chineseBip39Str = bip39.generateMnemonic(language: 'chinese');
setState(() {
_englishMnemonic = _englishBip39Str.split(' ');
_chineseMnemonic = _chineseBip39Str.split(' ');
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('备份助记词'),
actions: [
TextButton(
onPressed: () {},
child: const Text('跳过备份'),
),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TabBar(
controller: _tabController,
isScrollable: true,
indicatorColor: AppColors.primary,
indicatorWeight: 5,
indicatorSize: TabBarIndicatorSize.label,
tabs: const [
Tab(
text: 'English',
),
Tab(
text: '中文',
),
],
),
const Padding(
padding: EdgeInsets.only(right: 8.0),
child: Text(
'请务必抄下助记词,确定之后将进行校验',
style: TextStyle(
fontSize: 12,
),
),
),
],
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_moArea(_englishMnemonic),
_moArea(_chineseMnemonic),
],
),
),
Container(
height: Get.height * 0.382,
padding: const EdgeInsets.all(16),
child: const Text(
'提示:请勿截图!如果有人获取您的助记词将直接获取您的资产,请抄写助记词并存放在安全的地方,我们会在下一屏幕进行校验,',
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
_generateMnemonic();
},
child: const Text('更换助记词'),
),
ElevatedButton(
onPressed: () {
var language = _tabController.index;
Get.toNamed(
AuthRoutes.createVerify,
arguments: {
'language': language == 0 ? 'english' : 'chinese',
'mnemonic':
language == 0 ? _englishMnemonic : _chineseMnemonic
},
);
},
child: const Text('开始备份'),
),
],
),
const SizedBox(
height: 16,
),
],
),
);
}
Widget _moArea(List<String> mnemonic) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 4.5 / 1,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: mnemonic.length,
itemBuilder: (_, i) {
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(
color: AppColors.border,
width: 1,
),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'${i + 1}. ${mnemonic[i]}',
style: const TextStyle(
fontSize: 16,
),
),
);
},
),
);
}
}

View File

@@ -0,0 +1,65 @@
import 'package:chat/configs/app_colors.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AuthCreateVerifyPage extends StatefulWidget {
const AuthCreateVerifyPage({Key? key}) : super(key: key);
@override
_AuthCreateVerifyPageState createState() => _AuthCreateVerifyPageState();
}
class _AuthCreateVerifyPageState extends State<AuthCreateVerifyPage> {
late final List<String> _mnemonicList;
@override
void initState() {
super.initState();
Get.arguments['language'];
var m = Get.arguments['mnemonic'] as List<String>;
m.shuffle();
_mnemonicList = m;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('校验助记词'),
),
body: _moArea(_mnemonicList),
);
}
Widget _moArea(List<String> mnemonic) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 4.5 / 1,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: mnemonic.length,
itemBuilder: (_, i) {
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(
color: AppColors.border,
width: 1,
),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'${i + 1}. ${mnemonic[i]}',
style: const TextStyle(
fontSize: 16,
),
),
);
},
),
);
}
}

View File

@@ -0,0 +1,58 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/utils/hd_wallet.dart';
import 'package:flutter/material.dart';
class AuthImportPage extends StatefulWidget {
const AuthImportPage({Key? key}) : super(key: key);
@override
State<AuthImportPage> createState() => _AuthImportPageState();
}
class _AuthImportPageState extends State<AuthImportPage> {
final TextEditingController _editingController = TextEditingController();
@override
void initState() {
super.initState();
}
@override
void dispose() {
_editingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('导入账户'),
),
body: Column(
children: [
TextField(
controller: _editingController,
maxLines: 4,
decoration: const InputDecoration(
hintText: '请输入您的助记词',
border: OutlineInputBorder(
borderSide: BorderSide(
color: AppColors.border,
width: 0.4,
),
),
),
),
const Text('支持导入所有遵循BIP标准生成的助记词'),
ElevatedButton(
onPressed: () {
HDWallet.mnemonicToAddress(_editingController.text);
},
child: const Text('开始导入'),
),
],
),
);
}
}

View File

@@ -0,0 +1,37 @@
import 'package:chat/routes/auth_routes.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AuthPage extends StatefulWidget {
const AuthPage({Key? key}) : super(key: key);
@override
State<AuthPage> createState() => _AuthPageState();
}
class _AuthPageState extends State<AuthPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ZH-CHAT'),
),
body: Column(
children: [
ElevatedButton(
onPressed: () {
Get.toNamed(AuthRoutes.create);
},
child: const Text('创建账户'),
),
ElevatedButton(
onPressed: () {
Get.toNamed(AuthRoutes.import);
},
child: const Text('导入账户'),
),
],
),
);
}
}

View File

@@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
class AuthPage extends StatefulWidget {
const AuthPage({Key? key}) : super(key: key);
@override
State<AuthPage> createState() => _AuthPageState();
}
class _AuthPageState extends State<AuthPage> {
@override
Widget build(BuildContext context) {
return Container();
}
}

View File

@@ -15,12 +15,24 @@ class TransitPage extends StatefulWidget {
/// 这里的加载图片,应该是可以请求网络图片的,但是要考虑网络图片的加载周期,还有网络环境因素等
class _TransitPageState extends State<TransitPage> {
final int _leftTime = 5;
int _leftTime = 5;
late Timer _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(const Duration(seconds: 1), (Timer timer) {
if (mounted) {
setState(() {
_leftTime--;
});
}
if (_leftTime <= 0) {
timer.cancel();
_jumpToRootPage();
}
});
}
void _jumpToRootPage() {

View File

@@ -15,20 +15,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.2"
args:
dependency: transitive
description:
name: args
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.1"
asn1lib:
dependency: transitive
description:
name: asn1lib
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
async:
dependency: transitive
description:
@@ -43,6 +29,22 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
bip32:
dependency: "direct main"
description:
name: bip32
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
bip39_multi_language:
dependency: "direct main"
description:
path: "."
ref: master
resolved-ref: d6ad5f0f7e98d421090e501e39646054b310dd89
url: "https://github.com/cjango/dart-bip39-multi-language.git"
source: git
version: "1.0.7"
boolean_selector:
dependency: transitive
description:
@@ -50,6 +52,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
bs58check:
dependency: transitive
description:
name: bs58check
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
cached_network_image:
dependency: "direct main"
description:
@@ -107,7 +116,7 @@ packages:
source: hosted
version: "3.1.0"
crypto:
dependency: "direct main"
dependency: transitive
description:
name: crypto
url: "https://pub.flutter-io.cn"
@@ -134,13 +143,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.6"
encrypt:
dependency: "direct main"
description:
name: encrypt
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.0.1"
fake_async:
dependency: transitive
description:
@@ -148,6 +150,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
fast_base58:
dependency: "direct main"
description:
name: fast_base58
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1"
ffi:
dependency: transitive
description:
@@ -254,6 +263,20 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.3"
hash:
dependency: "direct main"
description:
name: hash
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.4"
hex:
dependency: transitive
description:
name: hex
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0"
http:
dependency: transitive
description:
@@ -616,6 +639,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
unorm_dart:
dependency: "direct main"
description:
name: unorm_dart
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0"
uuid:
dependency: transitive
description:

View File

@@ -11,8 +11,6 @@ dependencies:
get: ^4.6.5
dio: ^4.0.6
get_storage: ^2.0.3
crypto: ^3.0.2
encrypt: ^5.0.1
qr_flutter: ^4.0.0
azlistview: ^2.0.0
lpinyin: ^2.0.3
@@ -34,6 +32,14 @@ dependencies:
flutter_easyloading: ^3.0.5
proste_indexed_stack: ^0.2.4
image_cropper: ^3.0.0
bip32: ^2.0.0
bip39_multi_language:
git:
url: https://github.com/cjango/dart-bip39-multi-language.git
ref: master
fast_base58: ^0.2.1
hash: ^1.0.4
unorm_dart: ^0.2.0
dev_dependencies:
flutter_test:
@@ -41,3 +47,6 @@ dev_dependencies:
flutter_lints: ^1.0.0
flutter:
uses-material-design: true
assets:
- assets/icons/
- assets/transits/