diff --git a/android/app/build.gradle b/android/app/build.gradle index f11b3df..01c6690 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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 { diff --git a/android/app/key/key.md b/android/app/key/key.md new file mode 100755 index 0000000..0a38a19 --- /dev/null +++ b/android/app/key/key.md @@ -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 \ No newline at end of file diff --git a/assets/transits/1.png b/assets/transits/1.png new file mode 100644 index 0000000..d79b362 Binary files /dev/null and b/assets/transits/1.png differ diff --git a/assets/transits/2.png b/assets/transits/2.png new file mode 100644 index 0000000..262e73d Binary files /dev/null and b/assets/transits/2.png differ diff --git a/lib/routes/auth_routes.dart b/lib/routes/auth_routes.dart index 5dc0a16..ade6975 100644 --- a/lib/routes/auth_routes.dart +++ b/lib/routes/auth_routes.dart @@ -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(), + ), + ], ); } diff --git a/lib/utils/hd_wallet.dart b/lib/utils/hd_wallet.dart new file mode 100644 index 0000000..b2fecb7 --- /dev/null +++ b/lib/utils/hd_wallet.dart @@ -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 step4 = List.from(RIPEMD160().update(step3).digest()); + step4.insert(0, 0x00); + List 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'; + } + } +} diff --git a/lib/utils/ui_tools.dart b/lib/utils/ui_tools.dart new file mode 100644 index 0000000..c154262 --- /dev/null +++ b/lib/utils/ui_tools.dart @@ -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, + ); + } +} diff --git a/lib/views/auth/create/index_page.dart b/lib/views/auth/create/index_page.dart new file mode 100644 index 0000000..32d06e9 --- /dev/null +++ b/lib/views/auth/create/index_page.dart @@ -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 createState() => _AuthCreatePageState(); +} + +class _AuthCreatePageState extends State + with SingleTickerProviderStateMixin { + late final TabController _tabController; + + List _englishMnemonic = []; + List _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 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, + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/views/auth/create/verify_page.dart b/lib/views/auth/create/verify_page.dart new file mode 100644 index 0000000..5f10977 --- /dev/null +++ b/lib/views/auth/create/verify_page.dart @@ -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 { + late final List _mnemonicList; + @override + void initState() { + super.initState(); + Get.arguments['language']; + var m = Get.arguments['mnemonic'] as List; + m.shuffle(); + _mnemonicList = m; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('校验助记词'), + ), + body: _moArea(_mnemonicList), + ); + } + + Widget _moArea(List 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, + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/views/auth/import/index_page.dart b/lib/views/auth/import/index_page.dart new file mode 100644 index 0000000..e209113 --- /dev/null +++ b/lib/views/auth/import/index_page.dart @@ -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 createState() => _AuthImportPageState(); +} + +class _AuthImportPageState extends State { + 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('开始导入'), + ), + ], + ), + ); + } +} diff --git a/lib/views/auth/index/index_page.dart b/lib/views/auth/index/index_page.dart new file mode 100644 index 0000000..9e8514d --- /dev/null +++ b/lib/views/auth/index/index_page.dart @@ -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 createState() => _AuthPageState(); +} + +class _AuthPageState extends State { + @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('导入账户'), + ), + ], + ), + ); + } +} diff --git a/lib/views/auth/index_page.dart b/lib/views/auth/index_page.dart deleted file mode 100644 index 46a0d87..0000000 --- a/lib/views/auth/index_page.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; - -class AuthPage extends StatefulWidget { - const AuthPage({Key? key}) : super(key: key); - - @override - State createState() => _AuthPageState(); -} - -class _AuthPageState extends State { - @override - Widget build(BuildContext context) { - return Container(); - } -} diff --git a/lib/views/public/transit_page.dart b/lib/views/public/transit_page.dart index 665d494..8267a1e 100644 --- a/lib/views/public/transit_page.dart +++ b/lib/views/public/transit_page.dart @@ -15,12 +15,24 @@ class TransitPage extends StatefulWidget { /// 这里的加载图片,应该是可以请求网络图片的,但是要考虑网络图片的加载周期,还有网络环境因素等 class _TransitPageState extends State { - 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() { diff --git a/pubspec.lock b/pubspec.lock index a92e887..a804644 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index 91ed2d6..d2f2c51 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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/