Flutter开发笔记2(很多内容)

背景

两部分内容,一个是Flutter环境踩坑和如何借鉴,一个是关于webauthn的一些开发记录,发出来分享给大家,有些图片懒得上传,我直接upload一个pdf版本在末尾,感兴趣下载。

8月开始开发就作为爱好了,虽然很享受一个idea开始,学习一门新语言,新模块,然后把它开发出来,呈现给用户,这种感觉非常棒,类似于黑客松,但比黑客松时间更久,实际这个笔记我大约是不到两个月的业余时间为主,晚上边学边记录的。

但服从社区的整体需要,相比于我的编程能力,我的产品和架构,社区运营,更需要这些,会转移精力到这部分工作上,但还是会有机会参加黑客松,玩票一把。

正文

Flutter开发笔记

6-9

初始化Flutter

Vscode, 下载flutter sdk cmd+shit+p, new flutter project,会让你找sdk位置,定位即可

run: flutter doctor

报出检查结果

                                                                  1Doctor summary (to see all details, run flutter doctor -v):[✓] Flutter (Channel stable, 3.22.1, on macOS 13.3.1 22E772610a darwin-x64, locale en-US)[✗] Android toolchain - develop for Android devices    ✗ Unable to locate Android SDK.      Install Android Studio from: https://developer.android.com/studio/index.html      On first launch it will assist you in installing the Android SDK components.      (or visit https://flutter.dev/docs/get-started/install/macos#android-setup for detailed instructions).      If the Android SDK has been installed to a custom location, please use      `flutter config --android-sdk` to update to that location.​[✗] Xcode - develop for iOS and macOS    ✗ Xcode installation is incomplete; a full installation is necessary for iOS and macOS development.      Download at: https://developer.apple.com/xcode/      Or install Xcode via the App Store.      Once installed, run:        sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer        sudo xcodebuild -runFirstLaunch    ✗ CocoaPods not installed.        CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart        side.        Without CocoaPods, plugins will not work on iOS or macOS.        For more info, see https://flutter.dev/platform-plugins      To install see https://guides.cocoapods.org/using/getting-started.html#installation for instructions.[✓] Chrome - develop for the web[!] Android Studio (not installed)[✓] VS Code (version 1.89.1)[✓] Connected device (2 available)[✓] Network resources​! Doctor found issues in 3 categories.

需要安装

  1. xcode App Store

  2. android studio https://developer.android.com/studio ,android sdk :Preferences>Appearance on Behavior>System Settings>Android SDK, 选择安装sdk tools, 打开ADV,Create Virtual Device

  3. CocoaPods https://guides.cocoapods.org/using/getting-started.html#installation

新建demo后run,报错xcode build 问题

发现需要升级系统到14,然后重新安装xcode(可能有其他方法)

都完成后记得再运行一次:

 flutter doctor

安装vscode flutter,dart高亮插件

cmd+shit+p, new flutter project,run

terminal: open -a simulator

cmd+shit+p, dart: open dev tools

可以UI, widget,性能诊断啥的


web开发:

使用safari浏览器
  • 在启动模拟器后,重新打开safari浏览器

  • 选择(开发),可以看到模拟器选项


使用Google/inspect
  • 将手机与PC通过数据线连接,同时打开手机上的 开发者选项、USB调试

  • 在谷歌浏览器打开 chrome://inspect/#devices,可以看到该手机上的页面信息

安装Charles:https://www.charlesproxy.com/download/

  • 打开Charles

  • 安装证书 Help>SSL Proxying>Install Charles Root Certificate/Install Charles Root Certificate in IOS Simulators

  • 同时将上面的证书加入PC的信任列表

  • 开启 Proxy>macOS Proxy

  • 如果电脑还有其他的网络代理设置,需要在Charles启动之后在开启。否则Charles可能无法代理PC请求

更齐全初始化步骤看这里:https://juejin.cn/post/7207313062541443109

以上,最耗时间的是Mac OS升级,Xcode下载,以及Android studio 安装组件

Game&Docs

https://flutter.cn/gameshttps://github.com/flutter/samples/tree/master_archived/game_template

git clone git@github.com:flutter/samples.git

异常问题,大约消耗了一个小时,解决方案:

sudo /usr/local/Cellar/ruby/3.3.2/bin/gem install cocoapods
 brew install ruby                                                     
==> Fetching dependencies for ruby: libyaml
==> Fetching libyaml
==> Downloading https://mirrors.ustc.edu.cn/homebrew-bottles/bottles/libyaml-0.2
Already downloaded: /Users/jason/Library/Caches/Homebrew/downloads/b57bea533e022a2a6730d2298731682c0516fbe5820cfd5c8f17eedb9245be19--libyaml-0.2.5.sonoma.bottle.tar.gz
==> Fetching ruby
==> Downloading https://mirrors.ustc.edu.cn/homebrew-bottles/bottles/ruby-3.3.2.
Already downloaded: /Users/jason/Library/Caches/Homebrew/downloads/c8687e7886b96e5a9889c28fe50b4ecdb8c37086b0f10ce97f6b49b03ad075f9--ruby-3.3.2.sonoma.bottle.tar.gz
==> Installing dependencies for ruby: libyaml
==> Installing ruby dependency: libyaml
==> Pouring libyaml-0.2.5.sonoma.bottle.tar.gz
🍺  /usr/local/Cellar/libyaml/0.2.5: 11 files, 333.6KB
==> Installing ruby
==> Pouring ruby-3.3.2.sonoma.bottle.tar.gz
==> Caveats
By default, binaries installed by gem will be placed into:
  /usr/local/lib/ruby/gems/3.3.0/bin

You may want to add this to your PATH.

ruby is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have ruby first in your PATH, run:
  echo 'export PATH="/usr/local/opt/ruby/bin:$PATH"' >> ~/.zshrc

For compilers to find ruby you may need to set:
  export LDFLAGS="-L/usr/local/opt/ruby/lib"
  export CPPFLAGS="-I/usr/local/opt/ruby/include"

For pkg-config to find ruby you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/ruby/lib/pkgconfig"
==> Summary
🍺  /usr/local/Cellar/ruby/3.3.2: 19,792 files, 46.5MB
==> Running `brew cleanup ruby`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
==> Caveats
==> ruby
By default, binaries installed by gem will be placed into:
  /usr/local/lib/ruby/gems/3.3.0/bin

You may want to add this to your PATH.

ruby is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have ruby first in your PATH, run:
  echo 'export PATH="/usr/local/opt/ruby/bin:$PATH"' >> ~/.zshrc

For compilers to find ruby you may need to set:
  export LDFLAGS="-L/usr/local/opt/ruby/lib"
  export CPPFLAGS="-I/usr/local/opt/ruby/include"

For pkg-config to find ruby you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/ruby/lib/pkgconfig"
Cellar ➤ cd ruby

文档:https://docs.flutter.cn/

Flutter实战:https://book.flutterchina.club/chapter1/install_flutter.html ,代码:https://github.com/wendux/flutter_in_action_2 , 本身作者开发的组件库:https://github.com/flutterchina/flukit

Flutter 稳定版发布到了3.0,现在已经支持移动端、Web端和PC端,通过Flutter 开发的应用程序能够轻松的在各个平台迁移并获得很好的性能

brew update brew install ruby

Android的部分记得studio需要打开一个隐藏checkbox后选择如下:

  • Android SDK Platform, API 34.0.5

  • Android SDK Command-line Tools

  • Android SDK Build-Tools

  • Android SDK Platform-Tools

  • Android Emulator

才正常

运行几个template看看

另外,运行opclave的github repo

mac下的flutter path问题:

运行flutter doctor报错

Mac系统的环境变量,加载顺序为: a. /etc/profile b. /etc/paths c. ~/.bash_profile d. ~/.bash_login e. ~/.profile f. ~/.bashrc

Desgin

Flutter包括三个主要部分:

第1部分

和AirAccount的SDK交互。主要负责比如说账号的初始化,账号的绑定以及获取账号的基础信息,余额,提交交易数据(包括D2FA的签名+基础交易数据)提交给AirAccount

第2部分

和Racks交互的部分,可以单独SDK,或者先API方式,也就是负责这个 NFT数字资产的创作,上架下架管理。

第3部分

主要是游戏和更多部分,单独的一部分,会基于这个做扩展。

这次完成1和2的部分功能即可,可演示,可链上验证即可(iOS 发布需要时间)。

![image-20240610194607038](data:image/svg+xml,%3Csvg xmlns="w3.org/2000/svg" viewBox="0 0 2544 1368"%3E%3C/svg%3E align="left")

关于技术部分

Flutter external package website: https://pub.dev/

https://github.com/tencent/mxflutter ,但是3年前停止维护,到1.2版本,目前flutter3.22

MXFlutter 是一套使用 TypeScript/JavaScript 来开发 Flutter 应用的框架。

框架支持两种开发方式

基于 mxflutter-js 前端框架,使用 TypeScript 语言,以类似 Flutter 的 Widget 组装方式开发UI,借助前端生态的基础能力,开发App。(前端框架已开源: github:mxflutter-js)
不改变现有 Flutter 的开发方式,使用 MXJSCompiler 把现有工程编译为JS,运行在 mxflutter 框架之上。( MXJSCompiler JS编译工具在开源计划中 )
可以安装 Android的包来体验 MXFlutter_v0-9-0.apk。

另外一个思路:

https://tysonchen.me/integrating-javascript-npm-packages-in-flutter/

https://stackoverflow.com/questions/56238514/how-to-use-npm-packages-inside-dart-code

chatgpt答案(待验证)

In Flutter, you can use npm packages by integrating them with your project using the pubspec.yaml file. Here's how you can do it:

  1. Find the package on npm: First, you need to find the npm package that you want to use in your Flutter project. You can search for the package on the npm website or use the pub.dev website to search for Flutter-specific packages.

  2. Add the package to your pubspec.yaml file: Once you've found the package, you need to add it to your pubspec.yaml file. Here's an example:

dependencies:
  flutter:
    sdk: flutter
  my_npm_package:
    # Replace 'my_npm_package' with the name of the package you want to use
    # and 'version_number' with the version of the package you want to use
    version: ^version_number
  1. Install the package: After adding the package to your pubspec.yaml file, run the following command in your terminal to install the package:
flutter pub get

This will download the package and its dependencies and make them available in your Flutter project.

  1. Use the package in your code: Once the package is installed, you can use it in your Flutter code. The way you use the package will depend on the specific package you're using, but here's a general example:
import 'package:my_npm_package/my_npm_package.dart';

void main() {
  // Use the package in your code
  MyNpmPackageClass myObject = MyNpmPackageClass();
  myObject.doSomething();
}

Here are some links that may be helpful:


https://www.thesmythgroup.com/in-development/how-to-use-npm-packages-in-native-ios-apps/

https://developer.aliyun.com/article/748246 分析的不错

  • 从 Web 到 Flutter。就是使用 Web 技术栈来开发,然后对接到 Flutter 上实现跨平台渲染。对 Web 来说是解决性能和跨平台一致性问题,对 Flutter 来说是解决生态复用问题。

  • 从 Flutter 到 Web。就是官方已经实现的 Web support for Flutter,把已经用 Dart 开发好的 App 编译成 HTML/JS/CSS 然后运行在浏览器上,可以用于降级和外投场景。这个功能官方已经实现了,可以把使用 Dart 开发的 App 编译成 Web App 运行在浏览器上,官方文档以介绍用法和 API 为主,我这里简单分析一下内部具体的实现方案。

  •   Flutter Web的基本原理
    
      Flutter Web的基本原理是将Flutter的Dart代码转换为Web平台可执行的JavaScript代码。通过Flutter的编译工具链,开发者可以将Dart代码编译为Web平台上的WASM(WebAssembly)和JavaScript代码。WASM是一种在Web上运行二进制代码的格式,它允许开发者以接近原生的性能运行代码。而JavaScript则是Web平台的原生语言,用于与浏览器进行交互。Flutter通过结合WASM和JavaScript,实现了在Web平台上运行Flutter应用的目标。
      对于需要高度交互性和复杂UI的应用,Flutter Web是一个很好的选择,静态内容不适合
    
  • 相关文章:https://developer.aliyun.com/article/1498510?spm=a2c6h.12873639.article-detail.28.37857391KINHbq ,Flutter Web

  •   Flutter 中 setState 很有 React Native 的既视感,Flutter 中也是通过 state 跨帧实现管理数据状态的,这个后面会详细讲到。
      Flutter 中一切皆 Widget 呈现,通过 build方法返回 Widget,这也是和 React Native 中,通过 render 函数返回需要渲染的 component 一样的模式。
      有人说Flutter抄React,估计有点这个意思
    
  •   有状态的widget的简单实现。
      你需要创建管理的是主要是 State , 通过 State 的 build 方法去构建控件。在 State 中,你可以动态改变数据,这类似 MVVM 实现,在 setState 之后,改变的数据会触发 Widget 重新构建刷新。而下方代码中,是通过延两秒之后,让文本显示为 “这就变了数值”。
      如下代码还可以看出,State 中主要的声明周期有 :
      initState :初始化,理论上只有初始化一次,第二篇中会说特殊情况下。
      didChangeDependencies:在 initState 之后调用,此时可以获取其他 State 。
      dispose :销毁,只会调用一次。
      看到没,Flutter 其实就是这么简单!你的关注点只要在:创建你的 StatelessWidget 或者 StatefulWidget 而已。你需要的就是在 build 中堆积你的布局,然后把数据添加到 Widget 中,最后通过 setState 改变数据,从而实现画面变化。
    
  • 另外一种方式:https://medium.com/yavar/how-to-integrate-node-js-with-flutter-644d5039b4bf,API通信吗,无需解决npm问题

    •   Future<void> _fetchBooks() async {
          final response = await http.get(Uri.parse('http://localhost:3000/api/books'));
          if (response.statusCode == 200) {
            final List<dynamic> json = jsonDecode(response.body);
            setState(() {
            _books = json.map((item) => Book.fromJson(item)).toList();
            });
          } else {
            throw Exception('Failed to load books');
          }
        }
      
  •   flutter upgrade
      flutter packages get获取项目所有的依赖包。
      flutter packages upgrade 获取项目所有依赖包的最新版本。
    
      3)体验热重载 hotload
      打开lib/main.dart文件。
      将字符串 'You have pushed the button this many times:' 更改为 'You have clicked the button this many times:'。
      不要按“停止”按钮; 让您的应用继续运行。
      要查看您的更改,直接保存(cmd+s / ctrl+s),或者点击 热重载按钮 (绿色圆形箭头按钮)即可。
      你会立即在运行的应用程序中看到更新的字符串。
    

Mac

open -a Simulator

苹果开发者enroll:https://developer.apple.com/support/app-account/

6-10

技术方案初步设想(chatgpt给的建议在上面,不一定靠谱)

用dart+API(http)+移动端本地加密签名+远端public key验签

有个问题就是加密部分dart的能力是否和nodejs的airaccount sdk能力相当,这样等于dart版的airaccout sdk了

初期可以只是api,后面再提炼sdk

三部分功能设想

工作拆分:https://www.notion.so/planckerdao/Tokyo-4ecd0e18a0ce4ba6a5cde82adb380190


  1. Account LifeCycle(调用AirAccount的fluter部分)

    1. Email登陆,使用email验证码登陆(中心化验证码),证明拥有这个Email,可以查看关联Account的余额和交易记录(因为这个是公开的,不登陆链上也可以,所以伪造登陆也无所谓)

    2. 查询Account余额,包括ETH,USDT和NFT,以及PNTs,只查询这些,后面可以针对AirAccount的账户进行定时查询链上+后台缓存,目前先链上查询后返回

    3. 提交交易

      1. 交易数据(首先本地是有passkey的,可以对交易数据进行secp256r1的签名

      2.  chainid
         nonce
         data
             这个是交易数据
             例如:Mint,或者上架一个NFT
             Hero NFT data
             price
             status
             等等
         passkey-signature
        
         下面的字段是airaccount或者paymaster relay后台提供(CommunityNode)
         gasPrice
         gasLimit
         to
         value
         paymasterDataAndSignature
         v、r、s(私钥在Community Node)
        
      3. 发送给Community Node的API

      4. 获得反馈

        1. 同步API返回

        2. 异步推送(需要同步调用后等待?)

        3. 错误,报错后的重试和错误抛出

  2. Racks

    1. 两个核心功能:创作者(卖家)和社区玩家(买家),每个人进去都有两个角色(暗含)

创作者

  1. 手机App上进行创作(或者上架)

  2. 目前是拍照+写字(后面AI手绘图变小人动画,HPC接口)后一键NFT

  • NFT可以是艺术作品,也可以是收集分数的商业活动卡

    1. 例如711根据交易数据分析,针对游客,提供了本地艺术家创作的数字NFT拼图

    2. 购买7大类商品,平均金额达到多少,就可以获得随机抽取拼图

    3. 拼图完成,提供大象营、禅修、免费泰语课程等等的系列门票

    4. 任何社区可以注册一个艺术创作者或者生活创作者,进行艺术作品或者生活作品(Event)的NFT发布

    5. CMUBA会联合可信安全的筛选过的创作者,具备交流Party活动,也会以NFT形式发布。

  • 可以配置日常NFT参数

    1. 名字

    2. 价格(PNTs)

    3. 数量(默认1)

    4. 属性(很多,这个先不管)

    5. 技术参数

      1. 存储位置

      2. 图片大小限制

玩家

  1. 普通玩家,社区成员,使用手机进行积分兑换数字资产,例如NFT(Event资格)

  2. 积分可以游戏或者空投等等获取

  3. 安装任何移动端(使用HM开发的移动端),都可以使用账户内的积分来兑换(购买)Racks上架的数字资产


根据以上,定义核心User Case和开发方案

  1. Mint NFT

  2. Item Management

    1. Meta data set

    2. Dynamic data

    3. Item stock number

    4. Item status

  3. Item trade

    1. Seller show QR

      1. Basic trade data
    2. Buyer scan QR

      1. Basic trade data + Community Node trade data + submit

      2. return

  1. Game and more

    1. 暂时先不做,Arcadia在CMUBA已经完成游戏部分设计

    2. 积分体系还未设计

    3. 兑换依赖Racks

开发资源

Flutter 包Package开发

Package类型:

Flutter Package(dart包):Flutter纯Dart插件工程,仅包含Dart层的实现,往往定义一些公共Widget

Flutter Plugin(插件包):一种专用的Dart包,其中包含用Dart代码编写的API,以及针对Android(使用Java或Kotlin)和针对iOS(使用OC或Swift)平台的特定实现

https://juejin.cn/post/6954940703524782117

动画库

https://juejin.cn/post/7169046420505395236?from=search-suggest

地图Demo:https://animated-alfajores-40eb7b.netlify.app/ 代码:https://github.com/extrawest/evc_google_maps_demo?tab=readme-ov-file

Get an API key at https://cloud.google.com/maps-platform/.

Android
Specify your API key in the application manifest android/app/src/main/AndroidManifest.xml:

<manifest ...
  <application ...
    <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR KEY HERE"/>
iOS
Supply your API key in the application delegate ios/Runner/AppDelegate.m:

#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import "GoogleMaps/GoogleMaps.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GMSServices provideAPIKey:@"YOUR KEY HERE"];
  [GeneratedPluginRegistrant registerWithRegistry:self];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

相机Camera

https://github.com/sbis04/flutter_camera_demo

Game 引擎:https://github.com/flame-engine/awesome-flamehttps://flame-engine.org/https://flutter.dev/games

点赞动画:https://github.com/duytq94/flutter-fb-reactions-animation

OpenIM:https://www.cnblogs.com/OpenIM/p/15067721.htmlhttps://github.com/openimsdk/open-im-flutter-demo , 另外一个聊天的:https://github.com/duytq94/flutter-chat-demo , WebRTC聊天:https://github.com/flutter-webrtc , P2P:https://gitcode.com/michal-wrzosek/p2p-chat/overview?utm_source=artical_gitcode

好看UI,widget:https://github.com/FlutterOpen/flutter-ui-nice?tab=readme-ov-filehttps://github.com/FlutterOpen, 还在活跃的:https://github.com/OpenFlutterhttps://github.com/826327700/flutter_plugins_demo

官方Samples案例,可视化:https://flutter.github.io/samples/#

开发实战:https://github.com/CarGuo/gsy_flutter_demo

6-11

  1. 读两章flutter开发

    1. 目前 Flutter 已经支持 iOS、Android、Web、Windows、macOS、Linux、Fuchsia(Google新的自研操作系统)等众多平台

    2. AOT指的是"Ahead Of Time",即编译器在运行之前编译程序的所有代码。与JIT(Just In Time)模式相比,AOT模式在运行时不需要再进行编译,因此可以提高程序的执行速度。在Dart语言中,采用AOT模式可以显著提升程序的性能,并且使得其执行速度远远超过JavaScript语言。

    3. Dart 支持 AOT,Dart 运行时和编译器支持 Flutter 的两个关键特性的组合:

      • 基于 JIT 的快速开发周期:Flutter 在开发阶段采用 JIT 模式,这样就避免了每次改动都要进行编译,极大地节省了开发时间;

      • 基于 AOT 的发布包: Flutter 在发布时可以通过 AOT 生成高效的机器码以保证应用性能。而 JavaScript 则不具有这个能力。

    4. 图1-1

  2. 地图demo加载并能运行起来

    1. https://developers.google.com/profile/u/108223185425827780761,开发者页面

    2. 信用卡不能用,奇怪:https://console.cloud.google.com/freetrial/signup/billing/TH?redirectPath=%2Fgoogle%2Fmaps-apis%2Fonboard;flow%3Djust-ask-flow;step%3Djust_ask&project=jason-1209

    3. clone了,需要api key,nnd,暂放

  3. 尝试加入account相关场景和能力

    1. 约定下上面的API

6-16

flutter 包:https://pub.dev/

flutter黑客松作品:https://flutterhack.devpost.com/resourceshttps://docs.flutter.dev/ui/animations

B站教程:https://space.bilibili.com/589533168?spm_id_from=333.788.0.0

https://www.bilibili.com/video/BV14M4m1k7L5/?spm_id_from=333.788.recommend_more_video.1&vd_source=0a978d5cb963890b0cab49f66fae30af

Flutter快学快用24讲 https://www.bilibili.com/video/BV1PA411U79T/?spm_id_from=333.337.search-card.all.click&vd_source=0a978d5cb963890b0cab49f66fae30af

其他好玩的:AR(增强现实),https://www.mindar.org/webar-for-beginners/https://github.com/hiukim/mind-ar-js

同时游戏基本框架是根据@M_Studio 麦扣老师的《勇者传说》教程创作,教程非常棒,甚至B站有免费版。

如何看flutter架构
  1. pubspec.yaml, 主要配置文件

  2. lib目录下的,例如api等等

    1. provider(很多类型,例如notifier

    2. config,配置

    3. i18n

    4. routers

    5. pages,路由对应的页面

    6. utils,纯后端调用

    7. widgets,ui公共组件,包括一些功能性的,例如markdown,highlight啥的

  3. 状态管理

    1. redux

    2. provider

    3. getx

功能组件

  1. local_auth | Flutter ,https://github.com/HarrisonQi/flutter-actual-combat-demo/tree/master/fingerprint_verificationhttps://pub.dev/packages/local_auth

  2. 更多组件:https://github.com/jahnli/awesome-flutter-plugins


https://www.bilibili.com/video/BV1Ng4y167cr/?spm_id_from=333.999.0.0&vd_source=0a978d5cb963890b0cab49f66fae30af CPA Correlation Power Analysis ,相关能量分析;(side channel attack侧信道估计)

Flutter 升级:xupdate,源码分享地址:https://gitee.com/radium/flutter_example 文字分享地址:https://juejin.cn/user/3505696643488151 视频分享地址:https://space.bilibili.com/1159595523

不知道是否能用:https://gitee.com/sdwcl/flutter_dragonhttps://www.bilibili.com/video/BV1y3411E7gu/?spm_id_from=333.337.search-card.all.click&vd_source=0a978d5cb963890b0cab49f66fae30af

7-1

应用的核心场景:

  1. App注册passkey(生成keypair),发送给community node public key,做AA账户生成

  2. community node绑定到airaccount(airaccount生成一个local key pair,passkey的publickey作为参数参与账户创建?)

  3. 如果是EOA后来加入的,需要一个图说明下几种情况

  4. public key注册到validator网络(需要么?参数自带public key是不是ok,就不需要注册了)

  5. 对交易进行签名,发送给community node(先中心化)

  6. community node对交易再次签名(存储的私钥),发给validator

  7. validator用公钥进行签名验证


7-4

今天跑demo遇到了问题(下面code,解决了)

  1. 先运行: flutter pub get 然后 dart fix --apply

  2. https://github.com/lauravuo/flutter-webauthn-example 解决方案:https://stackoverflow.com/questions/66644365/cocoapods-on-m1-apple-silicon-fails-with-ffi-wrong-architecture

  3. 不行,又遇到这个问题:https://github.com/CocoaPods/CocoaPods/issues/12392

  4. 试试Ruby管理器(不用系统ruby版本):https://www.cnblogs.com/alerson/p/ruby-ban-ben-guan-lirvm-ruby-version-manager.html

  5. RVM安装报错:error running '__rvm_make "-j8"' m1

  6. 解决办法:

     \curl -sSL https://get.rvm.io | bash 
     不要安装默认版本(屁股不带stable参数)
     然后source zshrc
     rvm list  看看是否是空
     然后指定openssl
     rvm install ruby-3.2.1 --with-openssl-dir=/usr/local/opt/openssl@3.1
     rvm install ruby-stable --with-openssl-dir=/usr/local/opt/openssl@3.1
     rvm use 3.2.1 --default
    
     设定flutter用的gem set
     建立 gemset
     rvm use 1.8.7
     rvm gemset create rails23
     然后可以设定已建立的 gemset 做为当前环境
    
     use 可以用来切换语言或者 gemset
     前提是他们已经被安装 (或者建立)。并可以在 list 命令中看到。
    
     rvm use 1.8.7
     rvm use 1.8.7@rails23
     然后所有安装的 Gem 都是安装在这个 gemset 之下。
    
     列出当前 Ruby 的 gemset
     rvm gemset list
    
  7. 用rvm指定3.0,然后再安装一次cocoapods

  8. 需要修改.zshrc,将原来.bash_profile与.profile中的自定义内容导入过来. 比如支持rvm,应在.zshrc增加:

     [[ -s $HOME/.rvm/scripts/rvm ]] && source $HOME/.rvm/scripts/rvm
    
brew uninstall ruby --force
gem list --local | grep cocoapods
sudo gem uninstall cocoapods
sudo gem uninstall cocoapods-core
sudo gem uninstall cocoapods-downloader

gem list --local | grep cocoapods
remove all

sudo x86_64 gem install ffi
arch -x86_64 gem install ffi
sudo arch -x86_64 gem install cocoapods

then you can run pod install
. Note that I had the Terminal open all the time in standard mode - no Rosetta required.


arch -x86_64 pod install

sudo arch -x86_64 gem install ffi
# go to ios folder then run
arch -x86_64 pod install

sudo gem uninstall ffi && sudo gem install ffi -- --enable-libffi-alloc

rm -rf ~/.cocoapods/repos/trunk/

flutter run

pod repo update
升级
sudo gem update --system
sudo gem install cocoapods
pod setup
sudo arch -x86_64 gem install ffi

arch -arm64 brew install llvm
sudo gem install ffi

cd Project/ios
arch -x86_64 pod install

初步OK

VSC的两个webauthn demo都ok了(flutter),官方的好用。

个人的:https://github.com/mihaita-tinta/flutter-webauthn-demo

官方:

https://github.com/flutter-institute/webauthn/tree/master/example

https://pub.dev/packages/webauthn

其他:

https://www.corbado.com/blog

https://www.corbado.com/category/passkeys-implementation

https://github.com/corbado/example-passkeys-flutter (这个是代码,caorbado的server可以自己启动,他们就是多了一些登录session管理啥的)


7-11

W3C标准:https://www.w3.org/TR/webauthn-2/

This specification defines an API enabling the creation and use of strong, attested, scoped, public key-based credentials by web applications, for the purpose of strongly authenticating users. Conceptually, one or more public key credentials, each scoped to a given WebAuthn Relying Party, are created by and bound to authenticators as requested by the web application. The user agent mediates access to authenticators and their public key credentials in order to preserve user privacy. Authenticators are responsible for ensuring that no operation is performed without user consent. Authenticators provide cryptographic proof of their properties to Relying Parties via attestation. This specification also describes the functional model for WebAuthn conformant authenticators, including their signature and attestation functionality.

节省gas+私钥存储和解密思路

  1. 用户先注册,计算出钱包地址;

  2. 提供passkey的签名和public key给relay

  3. 滞后批量创建AA钱包,AA account的private key加密存储用relay的私钥+passkey私钥加密

因为是38.5W Gwei的创建费,8.8W(88,710,21000)的simple transfer

ERC20 transfer是90,803(65,000)

https://www.stackup.sh/blog/how-much-more-expensive-is-erc-4337

ERC-4337Traditional Wallet
Create Account385,2660
Simple Transfer88,71021,000
ERC-20 Token Transfer90,80365,000
Gas Price RangeToken PriceCreate ERC-4337 AccountSimple TransferERC-20 Transfer
Ethereum130.5 ± 10.6 gwei$2000$23.50 ± $8.17$5.41 ± $1.88$5.54 ± $1.93
Polygon2224 ± 108 gwei$1.10$0.09 ± $0.05$0.02 ± $0.01$0.02 ± $0.01
Optimism3,40.0013 ± 0.008 gwei$2000$0.50 ± $0.18$0.45 ± $0.16$0.46 ± $0.17
Avalanche5,636.4 ± 4.5 nAVAX$20$0.29 ± $0.04$0.06 ± $0.01$0.07 ± $0.01
BnB Smart Chain77.05 ± 0.53 gwei$350$0.95 ± $0.07$0.22 ± $0.02$0.22 ± $0.02
  1. 如果加密存储的任一一个私钥(relay的私钥+passkey私钥)丢失,recovery

  2. 需要使用zk证明我本地拥有私钥,生成proof给到server,验证你拥有私钥,则使用relay的私钥解密

    1.  如何使用 Zero-Knowledge Proof(零知识证明)技术生成拥有的私钥的证明,并将该证明发送给服务器进行验证。
      
       # 1. 引入相应库
       from petlib.bn import Bn
       from petlib.ec import EcGroup
       from petlib.ecdsa import do_ecdsa_sign, do_ecdsa_verify
       import hashlib
      
       # 2. 生成椭圆曲线(Elliptic Curve)和私钥
       group = EcGroup()
       private_key = group.order().random()
      
       # 3. 生成签名
       msg = "This is a message to prove ownership of the private key"
       msg_hash = hashlib.sha256(msg.encode()).digest()
       signature = do_ecdsa_sign(group, private_key, msg_hash)
      
       # 4. 生成零知识证明
       def prove_knowledge(private_key, signature):
           R = signature[2]
           s = signature[3]
      
           # 验证签名
           assert do_ecdsa_verify(group, private_key * group.generator(), signature, msg_hash)
      
           # 生成零知识证明
           alpha = group.order().random()
           A = alpha * group.generator()
           e = Bn.from_binary(hashlib.sha256(A.export() + R.export() + msg_hash).digest())
      
           z = alpha + e * private_key
           return (A, z)
      
       proof = prove_knowledge(private_key, signature)
      
       # 5. 模拟将证明发送给服务器并验证
       def verify_proof(proof, signature, msg_hash):
           A, z = proof
           R = signature[2]
           e = Bn.from_binary(hashlib.sha256(A.export() + R.export() + msg_hash).digest())
      
           assert do_ecdsa_verify(group, e * group.generator() + z * A, signature, msg_hash)
      
       # 6. 服务器验证证明
       try:
           verify_proof(proof, signature, msg_hash)
           print("ok")
       except AssertionError:
           print("proof verification failed")
      
    2. relay的公私钥对的加密用户私钥和解密用户私钥过程

      1.  使用RSA公私钥对对保存在服务器的一个字符串密码进行加密和解密。请注意,以下示例中使用的是Python的cryptography库来进行RSA加密和解密操作。
        
         from cryptography.hazmat.primitives.asymmetric import rsa
         from cryptography.hazmat.primitives import serialization
         from cryptography.hazmat.primitives.asymmetric import padding
         from cryptography.hazmat.primitives import hashes
        
         # 生成RSA密钥对
         private_key = rsa.generate_private_key(
             public_exponent=65537,
             key_size=2048
         )
         public_key = private_key.public_key()
        
         # 将公私钥保存为PEM格式
         private_key_pem = private_key.private_bytes(
             encoding=serialization.Encoding.PEM,
             format=serialization.PrivateFormat.PKCS8,
             encryption_algorithm=serialization.NoEncryption()
         )
         public_key_pem = public_key.public_bytes(
             encoding=serialization.Encoding.PEM,
             format=serialization.PublicFormat.SubjectPublicKeyInfo
         )
        
         # 保存私钥到文件
         with open('private_key.pem', 'wb') as f:
             f.write(private_key_pem)
        
         # 保存公钥到文件
         with open('public_key.pem', 'wb') as f:
             f.write(public_key_pem)
        
         # 要加密的密码字符串
         password = b"password123"
        
         # 加密密码
         encrypted_password = public_key.encrypt(
             password,
             padding.OAEP(
                 mgf=padding.MGF1(algorithm=hashes.SHA256()),
                 algorithm=hashes.SHA256(),
                 label=None
             )
         )
        
         # 将加密后的密码发送到服务器
         # 在实际情况下,这里应该是发送到服务器的操作
        
         # 从服务器接收到加密后的密码
         # 在实际情况下,这里应该是从服务器接收数据的操作
        
         # 解密服务器传回的加密后的密码
         decrypted_password = private_key.decrypt(
             encrypted_password,
             padding.OAEP(
                 mgf=padding.MGF1(algorithm=hashes.SHA256()),
                 algorithm=hashes.SHA256(),
                 label=None
             )
         )
        
         print("Decrypted password:", decrypted_password.decode())
        

输入密码是随便输入

创建凭证

{  "clientDataHash": "LTCT_hWLtJenIgi0oUhkJz7dE8ng-pej-i6YI1QQu60",  "rp": {    "id": "webauthn.io",    "name": "webauthn.io"  },  "user": {    "id": "",    "displayName": "",    "name": ""  },  "requireResidentKey": true,  "requireUserPresence": true,  "requireUserVerification": false,  "credTypesAndPubKeyAlgs": [    [      "public-key",      -7    ]  ],  "excludeCredentials": [    {      "type": "public-key",      "id": "lVGyXHwz6vdYignKyctbkIkJto_ADbYbHhE7-ss_87o",      "transports": null    }  ]}

登录后response:

{"type":"public-key","id":"gDmfYu3PQ4A9o8xivFJ5QHMQwFKJUEL0F7ATnZwLdnU","rawId":"gDmfYu3PQ4A9o8xivFJ5QHMQwFKJUEL0F7ATnZwLdnU","response":{"clientDataJSON":"","attestationObject":"o2hhdXRoRGF0YVikdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAIIA5n2Ltz0OAPaPMYrxSeUBzEMBSiVBC9BewE52cC3Z1pQECAyYgASFYIBzbrVrMRw-oYqG646LGnu_DUAZ4qll6gqy1FI-uDPZJIlggXoLarwT8cf9LOjRH51YeVELM67BL_U1cHCYglfkMI7xjZm10ZnBhY2tlZGdhdHRTdG10omNhbGcmY3NpZ1hHMEUCIQCbjERzMwSaE0diRShkwiQHFfuupGETep_nEEQneQqV1QIgEe_9oRRAxMSqOzwwmYIfg0M8n4G35QbqXuo7rIOkBU4"}}

7-6

前天晚上跑通了Flutter的web3auth

这周要搞定对交易的签名和验证

一个商业公司的例子:

https://github.com/corbado/example-passkeys-flutter

https://docs.corbado.com/overview/corbado-api-structure

一个开源的web page web3auth例子,讲解的不错:

https://webauthn.passwordless.id/

https://github.com/passwordless-id/webauthn?tab=readme-ov-file

另外一个例子,1.4k例子:

https://github.com/MasterKale/SimpleWebAuthn

Go webauthn例子,star和fork也不错:

https://github.com/go-webauthn/webauthn

Python为后端:https://github.com/duo-labs/py_webauthn

https://github.com/duo-labs/webauthn.io?tab=readme-ov-filewebauthn.io

Mozzila: https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Authenticator_data

W3C的webauthn:https://w3c.github.io/webauthn/#backed-up 关于备份的定义


用dart 库来实现,先看文档,再基于官方例子,修改,跑通

Users send UserOperation


Sender 1

Receiver 2

rory

充值控制余额

充值可以干别的


Neal

用我们的优势是啥

  1. 简单调用横向聚合

  2. Demo


https://github.com/flutter-institute/webauthn/tree/master/example https://github.com/MasterKale/SimpleWebAuthn

https://github.com/go-webauthn/webauthn

https://github.com/passkeydeveloper/passkey-authenticator-aaguids


  1. Register(Credential) -->reg API(public key, id)

  2. Login(user name, signature)--> verify API(data, publickey(id))

7-8

Flutter Demo开发设计

Relay API:web page或者App,或者其他支持passkey设备的应用,都可以调用

https://app.apifox.com/project/4751506

用例

  1. Email验证:验证码

  2. 注册passkey:要输入指纹(biometrics)

  3. passkey登录:用Email做name登录,登录需要指纹passkey

  4. 生成付款码(商家):就是部分交易数据(下面有数据结构)

  5. App扫码付款码:解析QR,组装完整交易数据,

  6. 交易本地验证:基于交易数据,提供完整交易信息和安全提示,用户点击确认button后,按指纹或者faceid,签名交易数据生成signature2,完成验证

  7. 交易:对验证后的交易数据,提交

    1. 给paymaster,获得gas代付签名

    2. 再提交给bundler,计算gas,验证交易

    3. bundler提交给EP,扣除代付,回调paymaster合约退多余

    4. 执行合约账户其他调用

    5. 进入mempool(bundler的)等候提交RPC

数据定义和结构

  1. 用户Web2 Account:例如SMS(mobile),Email,Google Account,Telegram Account,Twitter Account等等(更多参考NextId)

    1. SMS和Email不是Web2的,但是就统称为Web2 Account
  2. Passkey 数据

    1. 注册数据

      1. username

      2. reg website/APP(常规来看是一个网站一个passkey,未来是否需要一个应用一个passkey?)

    2. 登录返回数据

  3. AirAccount的绑定记录

  4. 交易数据

    1. useroperation

    2. 批量useroperation

  5. Signature1

  6. Signature2

  7. Signature3(BLS聚合+TSS)

  8. paymasterAndData

时序图

doing


AirAccount Parts

  1. Random private key kept in TEEOS on a Relay Server(Community Node)

  2. Your unique biometrics like fingerprint, faceid or something else.

  3. Your contract account with code hash and state hash on chain.

  4. Your history transactions on chain.


https://www.bilibili.com/video/BV17V411P7tY

Google Account Manager 实现了跨设备同步,基于Chrome的account体系。

这个同步,究竟同步了什么??

  1. Only public key?

  2. 另外一个设备的指纹不是会随机生成一对key pair么

跨设备认证意味着

  1. 没有指纹和其他方式的设备,可以注册时添加外部设备的passkey

  2. 登录时popup QR(FIDO标准)

  3. 让另外一个有passkey的扫码登录


复杂计算利用WebAssembly进入Web,用户使用浏览器即可使用App

https://www.bilibili.com/video/BV1Gp4y1A7RA


7-11

Passkey learn
  1. Note of notion: https://ethresear.ch/t/passkey-based-account-abstraction-signer-for-smart-contract-wallets/15856

The R1 curve is considered more secure than K1 and supports major hardware enclaves. Also, most security enclaves cannot generate K1-based signatures, which are commonly used by EVM blockchains for signing transactions and messages.

A typical ethers signer signs transactions by either having the private key itself or by being connected to a JsonRpcProvider to fetch the signer. However, the Passkey Signer operates differently as it does not possess the private key. Instead, it can sign transactions and messages by sending them to the hardware, and the signed string is provided as the output.

const publicKeyCredentialCreationOptions = {
        //The challenge is a buffer of cryptographically random bytes generated on the server, and is needed to prevent "replay attacks".
    challenge: Uint8Array.from(
        randomStringFromServer, c => c.charCodeAt(0)), 
    rp: {
        name: "Your Name",
        id: "yourname.com",
    },
    user: {
        id: Uint8Array.from(
            "UZSL85T9AFC", c => c.charCodeAt(0)),
        name: "your@name.guide",
        displayName: "ABCD",
    },
//describe the cryptographic public key. -7 is for secp256R1 elliptical curve 
    pubKeyCredParams: [{alg: -7, type: "public-key"}],
    authenticatorSelection: {
        authenticatorAttachment: "cross-platform",
    },
    timeout: 60000,
    attestation: "direct"
};

const credential = await navigator.credentials.create({
    publicKey: publicKeyCredentialCreationOptions
});

//credential object

PublicKeyCredential {
    id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...',
    rawId: ArrayBuffer(59),
    response: AuthenticatorAttestationResponse {
        clientDataJSON: ArrayBuffer(121),
        attestationObject: ArrayBuffer(306),
    },
    type: 'public-key'
}

The public-key is extracted and that is passed to the smart contract wallet.

The publicKeyCredentialCreationOptions object contains a number of required and optional fields that a server specifies to create a new credential for a user.

const publicKeyCredentialRequestOptions = {
    challenge: Uint8Array.from(
        randomStringFromServer, c => c.charCodeAt(0)),
    allowCredentials: [{
        id: Uint8Array.from(
            credentialId, c => c.charCodeAt(0)),
        type: 'public-key',
        transports: ['usb', 'ble', 'nfc'],
    }],
    timeout: 60000,
}

The interaction with the hardware will be done using the webauthn library which allows us to generate new cryptographic keys within the hardware. The public key which is an (x,y) co-ordinate, corresponding to this private key is fetched. These co-ordinates should be stored inside the smart contract wallets and they will act like a owner to the smart contract wallet.

The flow is (this is only for apple, I am not aware of other operating systems):

  1. User setups operating system - setup a OS password (this is called passcode)

  2. User setups fingerprint - this is an additional security with passcode, user can open the secure enclave with either of the two i.e passcode or fingerprint

  3. Application requests webauthn credentials, operating system creates a new secp256r1 public <> private key pair. Store this newly generated pair in secure enclave of the device which can be unlocked by passcode or fingerprint.

  4. Apple’s operating system also backs up this secure enclave with their iCloud keychain escrow for the users so that they can recover this at any given time in future.

So in theory yes if Government get’s access to this encrypted storage from either Apple or Apple decides to go bad they don’t have a 10-attempt limit.

Users can though opt out of this backup and backup these keys manually Remove a passkey or password from your Mac and iCloud Keychain - Apple Support (IN) 10

But I couldn’t find any way to restore the keys.


  1. Note of https://hackmd.io/nJr0h2XFRvS8ReLYMXBWNg

    According to the ethers documentation, a signer is:

    "…an abstraction of an Ethereum Account, which can be used to sign messages and transactions and send signed transactions to the Ethereum Network to execute state-changing operations."

    The PasskeySigner package will extend the abstract signer provided by ethers and offer the functionality to sign transactions, messages, and typed messages for blockchains using passkeys. A passkey is a digital credential tied to a user account and a website or application. Passkeys allow users to authenticate without entering a username or password or providing any additional authentication factor. This technology aims to replace legacy authentication mechanisms such as passwords. Passkeys serve as a replacement for private key management, offering faster sign-ins, ease of use, and improved security.

  2. several advantages:

    User Experience: Onboarding new users to the blockchain is challenging, with seed phrases and private key management being less than ideal. We aim to address this by ensuring even users unfamiliar with security concerns can safely manage their funds. Security: Passkeys provide inherent security by eliminating issues like weak and reused credentials, leaked credentials, and phishing. Plug and Play: Simplify the integration of smart contract wallets by replacing the reliance on Metamask as an externally owned account (EOA) for transaction and message signing. With the passkey module, wallet infrastructure and wallets can seamlessly integrate passkey functionality, streamlining the user experience. Cross-platform support: Extend the solution to devices without biometric scanning but with Trusted Execution Environment (TEE) support. Utilizing QR code scanning, devices perform a secure local key agreement, establish proximity, and enable end-to-end encrypted communication. This ensures robust security against phishing attempts.

  3. Disadvantages Gas cost: On-chain signature verification for passkey-based transactions incurs significant gas costs. Efforts have been made to reduce the gas cost for verification. Opclave utilizes a custom rollup with the "secp256r1 verifier" precompile contract following Optimism's Bedrock Release standards. Ledger is also working on further optimizing gas costs.

    Device dependency: Though passkeys are device dependant there are workarounds. Apple device users can securely back up their passkeys in iCloud Keychain, overcoming this restriction. For other devices, the module will provide multi-device support, allowing users to add multiple owners (devices) to their smart contract wallet.

  4. WebAuthn:

    1. https://github.com/passwordless-id/webauthn?tab=readme-ov-file 比较好看到的js demo,全

    2. https://github.com/google/webauthndemo,ts demo

    3. 聚合:https://webauthn.passwordless.id/

    4.  zeqjnOUkvlcn...a8-tvCZMF85M
       Description:
       device-bound passkey
       Transports:
       ["internal"]
       Provider Information:
       Name: Chrome on Mac
       AAGUID: adce0002-35bc-c60a-648b-0b25f1f05503
      
    5.  其他开发信息
      
       https://webauthn.passwordless.id/demos/authenticators.html
      
    6. ![](data:image/svg+xml,%3Csvg xmlns="w3.org/2000/svg" viewBox="0 0 1388 1406"%3E%3C/svg%3E align="left")

![](data:image/svg+xml,%3Csvg xmlns="w3.org/2000/svg" viewBox="0 0 1264 1220"%3E%3C/svg%3E align="left")

Question

  1. blind signature(lack of information to make decision)

    1. Give more information

    2. Alert notification

  2. Device dependency: rely on apple sync

    1. how to remove backup from apple
  3. Evil apple or third party(save passkey and crack)

    1. Remove your passkey from keyring backup
  4. Lost device if remove back-up from Apple or third party

    1. multi passkey:Mac and mobile,can login with web page and mobile app

    2. if you have back-up passkey biding with your web3 account, you can produce a url to add passkey

  5. Onchain verification gas cost

  6. How to cross device?

    1. Use one finger with multi passkey(credential)

WebAuthn (Web Authentication) 是一个web标准, 该标准使用了公钥和私钥来给web应用的用户授权。 严格来讲,WebAuthn仅仅是浏览器API的名称, 而且它还是 FIDO2 的一部分。 FIDO2 是一系列标准的宏观总体,它包含了 WebAuthn 和 CTAP。 FIDO2 是历史遗留协议 FIDO Universal 2nd Factor(U2F)的替代者。

作为应用开发者,我们不处理 CTAP(Client-to-Authenticator Protocol), CTAP是一个协议,类似FIDO安全口令一样,浏览器用它来与认证器交互。

FIDO2使用公钥/私钥。用户有一个认证器,该认证器创建公钥/私钥的密钥对。 这些密钥对在每个网站都不一样。公钥被传送到服务器端并被存储到用户账号名下。 私钥存储在验证器方从不暴露。如果要登陆,服务器首先创建一个随机码(一个随机的二进制序列), 然后将它发送到验证器。验证器将随机码加入签名后用私钥加密, 然后将签名加密后的数据发送回服务器。 服务器用存储的公钥校验签名,如果签名合法,则进行授权。

按传统,该技术需要一个硬件安全口令,类似 Yubico key 或者 Feitian 生成的口令, 从而命名客户端和服务端两方。

FIDO2 依旧支持这些硬件口令,但是这个技术也支持其他的。如果您有一个安卓7以上版本的手机, 或者Windows10系统,那么您用WebAuthn时,便不再需要购买FIDO2安全口令。

2019年4月, 谷歌声明 所有安卓7版本以上的手机都可以充当一个FIDO2安全密钥。 在 2018年11月, 微软声明 您可以用 Windows Hello 作为一个FIDO2的安全密钥。 在 2020年6月 苹果声明 , 您可以通过兼容WebAuthn标准的方式, 将IOS系统的 FaceID 和 TouchID 用于web应用。

WebAuthn协议已经在 Edge、Firefox、Chrome、和 Safari 浏览器当中实现。 访问 https://caniuse.com 来查看当前已实现该标准的浏览器: https://caniuse.com/#search=webauthn

Web Authentication APICredential Management API 的扩展。

WebAuthn 从 Credential Management API 中继承了两个功能, 他们分别是 navigator.credentials.create()navigator.credentials.get() , 所以他们会就收一个 publickKey 参数。

webauthn基础时序图

WebAuthn的基本工作原理可以简单概括为以下几个步骤:

  1. 用户注册:用户在第一次登录时,选择并注册一种或多种生物特征或硬件密钥作为身份认证凭证。

  2. 身份认证请求:用户在登录时,浏览器向服务器发送身份认证请求,请求身份验证。

  3. 凭证创建:服务器生成一次性的随机挑战,并将其发送给浏览器。

  4. 身份认证:浏览器使用注册的生物特征或硬件密钥对随机挑战进行签名,然后将签名和挑战发送给服务器。

  5. 身份认证验证:服务器验证签名和挑战的有效性,如果验证通过,则允许用户登录。

主流的浏览器已经提供了对WebAuthn的原生支持,我们只需要调用相应的API即可。

在一个完整的 WebAuthn 认证流程中,通常有这么几个角色:

  • Relying Party 依赖方 (RP) :指服务提供方,即网站

  • User 用户:正准备登录的你

  • Authenticator 认证器:通常指 USB Key 或是设备内置的指纹扫描器、虹膜扫描器、面部识别装置等,正是它们在使用流程中代替了密码甚至是用户名

  • User Agent 用户代理:通常指浏览器或系统,负责与认证器交互

认证过程通常分为两种:

  • Registration Ceremony 注册仪式:用户向账户上添加认证器

  • Authentication Ceremony 验证仪式:用户通过已注册的认证器验证身份

同时,认证过程中还会产生这些内容:

  • Challenge 挑战:通常是一串随机字符串

  • Public Key Credential 公钥凭证:由认证器产生的凭证,在技术上代替了密码

  • Attestation 证明:注册时认证器产生的验证数据

  • Assertion 断言:验证时认证器产生的验证数据

WebAuthn 不仅在理论上是安全的,同时在整个过程中并没有隐私数据被传输——用户信息实际上只包含用户名和用户 ID。因此我们完全可以说 WebAuthn 是安全且私密的。

优势:

  • 更高的安全性:使用公私钥对挑战进行签名,避免了密码泄露的风险。

  • 更便捷的用户体验:用户可以使用指纹、面部识别等生物特征进行身份验证,无需记住复杂的密码。

  • 跨平台支持:WebAuthn可以在不同平台上使用,包括桌面浏览器、移动设备等。

Authentication

https://webauthn.guide/ 详细流程

const optionsFromServer = { "challenge": "random_string", // 需要转换为ArrayBuffer "rp": { // 我的网站信息 "name": "My Website", "id": "mywebsite.com" //一级或者二级域名 }, "user": { // 用户信息 "name": "anthony@email.com", "displayName": "Anthony", "id": "USER_ID_12345678910" // 需要转换为ArrayBuffer }, "pubKeyCredParams": [ //接受的公钥类型 { "type": "public-key", "alg": -7 // 接受的算法,-7是ES256 } ], "authenticatorSelection": { authenticatorAttachment: "platform", // 使用 platform 允许Windows Hello或TouchID的

// 身份验证器。使用 cross-platform 允许身份验证器,

// 如Yubikey ​ }, ​ "timeout": 60000 // 以毫秒为单位 };

另外一个更全的option,参考数据结构
const registerCredential = async () => {

  const credential = await navigator.credentials.create({

    publicKey: {

      challenge: new Uint8Array([/* 随机挑战 */]),
      rp

: {
    id: 'example.com', name: 'Example' },
      user: {
    id: new Uint8Array([/* 用户ID */]), name: 'user@example.com', displayName: 'User' },
      pubKeyCredParams: [
        {
    type: 'public-key', alg: -7 }, // ES256
        {
    type: 'public-key', alg: -257 }, // RS256
      ],
      authenticatorSelection: {
    authenticatorAttachment: 'platform', requireResidentKey: false, userVerification: 'preferred' },
      timeout: 60000,
      attestation: 'direct',
    },
  });
  // 将注册凭证发送给服务器
};


----
另外一个
publicKey: {
    challenge, // 自行设置
    rp, // 自行设置
    user, // 自行设置
    pubKeyCredParams: [
        {
            type: "public-key",
            alg: -7 // ES256
        },
        {
            type: "public-key",
            alg: -257 // RS256
        }
    ],
    authenticatorSelection: {
        userVerification: "discouraged",
        authenticatorAttachment: null // 除非用户指定,大部分情况下无需指定
    },
    excludeCredentials, // 自行设置
    timeout: 6

只是需要注意验证流程中获取到的 PublicKeyCredential 的结构和注册时的稍有不同。

客户端进行创建

// 请确保你已经将字符串转换为ArrayBuffer // 如上所述 const credential = await navigator.credentials.create({ publicKey: optionsFromServer });

客户端生成的credential

PublicKeyCredential { id: 'ABCDESKa23taowh09w0eJG...', rawId: ArrayBuffer(59), response: AuthenticatorAttestationResponse { clientDataJSON: ArrayBuffer(121), attestationObject: ArrayBuffer(306), }, type: 'public-key' }

另外一个凭证
const authenticateCredential = async () => {

  const credential = await navigator.credentials.get({

    publicKey: {

      challenge: new Uint8Array([/* 随机挑战 */]),
      allowCredentials: [
        {
    type: 'public-key', id: new Uint8Array([/* 凭证ID */]), transports: ['internal', 'usb', 'nfc', 'ble'] },
      ],
      userVerification: 'preferred',
      timeout: 60000,
    },
  });
  // 将认证凭证发送给服务器
};

登录申请后server发的challenge

const optionsFromServer = { "challenge": "somerandomstring", // Need to convert to ArrayBuffer "timeout": 60000, "rpId": "mywebsite.com", "allowCredentials": [ { "type": "public-key", "id": "AdPc7AjUmsefw37..." // Need to convert to ArrayBuffer } ] }

本地签名:

// make sure you've converted the strings to ArrayBuffer // as mentioned above const assertion = await navigator.credentials.get({ publicKey: optionsFromServer });

签名结构(断言)

PublicKeyCredential { id: 'ABCDESKa23taowh09w0eJG...', // WebAuthn凭证ID rawId: ArrayBuffer(59), response: AuthenticatorAssertionResponse { authenticatorData: ArrayBuffer(191), clientDataJSON: ArrayBuffer(118), signature: ArrayBuffer(70), // 我们需要验证的签名 userHandle: ArrayBuffer(10), }, type: 'public-key' }

https://fidoalliance.org/fido2/fido2-web-authentication-webauthn/

https://docs.authing.cn/v2/reference/sdk-for-ios/scenario/webauthn.html

使用 Authing 快速实现任何 Web、App 和企业软件的身份认证和用户管理, 为你的客户和员工提供完善的登录解决方案

获得2100万美元A轮,看来有价值啊,学习下产品线思路

AirAccount: Accounts for All!

Create Security Ethereum Account from Any Account for Anyone.

Feature:

Daily Control

No Seed Phrase

Deadman's Switch

Private key Recovery

Seamless Gas Payment

TEE Custody Private key

Second Biometrics Verification

Post Quantum Cryptography Support


Java 版本:https://vertx-china.github.io/docs/vertx-auth-webauthn/java/

较为全面版本:https://ayk.moe/articles/what-webauthn-is-and-develop-a-simple-demo/index.html

7-13

https://github.com/love-flutter/flutter-column

npm install -g flutter-pro-cli

  1. flutter create my_flutter

  2. cd my_flutter

  3. flutter-pro-cli i //产生项目结构

router协议:[schema]://[host]/[path]/?[query]

my_flutter://userPageIndex(page name)?userId=008

Uni_links外部链接调整(适合手机页面调用app),android,ios有不同解决方案(deep links,universal links)

先import page 页面

Flutter快学快用24讲

gzqhero· 2023-2-16

7-14

今日目标:

基于昨天的脚手架,做一个基础面板 ​​

一个button,一个text输入​​

一个area显示返回​​

API调用交互

  1. json 交互,dart.convert

  2. 建立api/reg/index.dart

brew install protobuf

flutter pub global activate protoc_plugin                    
Downloading packages... . (1.7s)
+ collection 1.19.0
+ fixnum 1.1.0
+ meta 1.15.0
+ path 1.9.0
+ protobuf 3.1.0
+ protoc_plugin 21.1.2
Building package executables... 
Built protoc_plugin:protoc_plugin.
Built protoc_plugin:protoc_plugin_bazel.
Installed executable protoc-gen-dart.
Warning: Pub installs executables into $HOME/.pub-cache/bin, which is not on your path.
You can fix that by adding this to your shell's config file (.zshrc, .bashrc, .bash_profile, etc.):

  export PATH="$PATH":"$HOME/.pub-cache/bin"

Activated protoc_plugin 21.1.2.

创建user_info.proto

![](data:image/svg+xml,%3Csvg xmlns="w3.org/2000/svg" viewBox="0 0 1772 842"%3E%3C/svg%3E align="left")


API list

  1. prepare

curl --location --request POST 'https://anotherairaccountcommunitynode.onrender.com/api/passkey/v1/reg/prepare' \ --header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \ --header 'Content-Type: application/json' \ --data-raw '{ "email": "jhfnetboy@gmail.com" }'

{"code":200,"message":"","data":null,"cost":"2562047h47m16.854775807s"}%

在reg那个接口填的邮箱,随便写,只要abc@aastar.org这种形式,我就不会保存下来,这样你可以反复使用这个邮箱注册

其他就保留注册

你在测试接口的时候,origin需要是一个url的形式: web端直接传浏览器上的那个url,app的话,要么就传https://aastar.org好了

curl --location --request POST 'https://anotherairaccountcommunitynode.onrender.com/api/passkey/v1/reg' \ --header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \ --header 'Content-Type: application/json' \ --data-raw '{ "captcha": "string", "email": "string", "network": "ethereum-mainnet", "origin": "https://aastar.org" }'


7-14

另外一条路陷入大量的bug和引入了很多额外概念,暂停

今日完成(重新开始)

  1. 初始化一个基础的scaffold for dev

    1.  npm install -g flutter-pro-cli  
      
       1. flutter create my_flutter2
      
       2. cd my_flutter2
      
    2.  github 网页建立一个repo,例如叫 my_flutter2
       git init
       git status
       git add -A
       git commit -m "inital from flutter create" 
       git remote add origin https://github.com/jhfnetboy/my_flutter2.git
       git push -u origin main
      
    3.  3. flutter-pro-cli i   //产生项目结构
       git status
        git add -A
       git commit -m "inite structure from flutter-pro-cli"
      
       sudo chmod 777 format_check.sh 
       ./format_check.sh
      
    4. 使用 dartfmt 命令可以根据 Dart 指南 将你程序中多余的空格替换掉。这与你使用支持 Dart 的 IDE 或者编辑器格式化代码的效果相同。

      dartfmt 命令接收一系列文件或目录作为参数。例如,下面是如何格式化当前目录下 binlibtest 目录下的所有 Dart 文件示例:

      当我们搜索 Flutter & Dart 静态代码分析(static code analysis)时,得到的结果有 flutter analyze, dartanalyzer 等命令行工具

      @TODO 待研究这个

  2. 建立API 分支,使用dio访问api

    1. git branch API

    2. git checkout API

    3. git push --set-upstream origin API

    4.  flutter pub add flutter_webview_plugin
       flutter pub outdated
       fix some bug with old framework
       git add -A
       git commit -m "fix some bug with old framework"
       git push
      
    5.  flutter pub add dio
       flutter pub outdated
       flutter pub upgrade --major-versions
      
    6.  import 'package:dio/dio.dart';
      
    7. dio第一次报错

    8.  报错:
       DioException [connection error]: The connection errored: Connection failed This indicates an error which most likely cannot be solved by the library. Error: SocketException: Connection failed (OS Error: Operation not permitted, errno = 1),
      
       macOS needs you to request a specific entitlement in order to access the network. To do that open macos/Runner/DebugProfile.entitlements and add the following key-value pair.
      
       <key>com.apple.security.network.client</key>
       <true/>
       Then do the same thing in macos/Runner/Release.entitlements.
      
       You need to stop and restart the app for the changes to take effect.
      
    9.  flutter clean
       flutter pub get
      
    10. 获得的返回
      Connecting to VM Service at ws://127.0.0.1:50987/It_p0x_XsaE=/ws
      flutter: {"code":200,"message":"","data":{"rp":{"name":"https://aastar.org","id":"aastar.org"},"user":{"name":"jhfnetboy@aastar.org","displayName":"jhfnetboy@aastar.org","id":"amhmbmV0Ym95QGFhc3Rhci5vcmc"},"challenge":"X48WS_9IM374Ucv05mJ5hUQP06uIWUvwcHpIN5DgTCc","pubKeyCredParams":[{"type":"public-key","alg":-7},{"type":"public-key","alg":-35},{"type":"public-key","alg":-36},{"type":"public-key","alg":-257},{"type":"public-key","alg":-258},{"type":"public-key","alg":-259},{"type":"public-key","alg":-37},{"type":"public-key","alg":-38},{"type":"public-key","alg":-39},{"type":"public-key","alg":-8}],"timeout":300000,"authenticatorSelection":{"authenticatorAttachment":"platform","requireResidentKey":false,"userVerification":"required"}},"cost":"2562047h47m16.854775807s"}
      
  1. 建立api2,美化和自动化一些api调用

Warrior的run QR分支:

报错:

Inspecting targets to integrate
      Using `ARCHS` setting to build architectures of target `Pods-Runner`: (``)
    [!] Unable to find a target named `RunnerTests` in project `Runner.xcodeproj`, did find `Runner`.

解决方法:切换设备(bottom line有快捷)

但是又遇到了:

Applying Flutters app_plugin_loader Gradle plugin imperatively using the apply script method which is deprecated, will be removed in a future release
猜测是gradle新老版本兼容问题
https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply

解决办法1:(备份了下面的打开camera配置)
rm -rf android
flutter create --platform android .

camera打开

android/app/src/main/AndroidManifest.xml

    </application>
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
</manifest>

response

"{"code":200,"message":"","data":{"rp":{"name":"https://aastar.org","id":"aastar.org"},"user":{"name":"jhfnetboy@aastar.org","displayName":"jhfnetboy@aastar.org","id":"amhmbmV0Ym95QGFhc3Rhci5vcmc"},"challenge":"X48WS_9IM374Ucv05mJ5hUQP06uIWUvwcHpIN5DgTCc","pubKeyCredParams":[{"type":"public-key","alg":-7},{"type":"public-key","alg":-35},{"type":"public-key","alg":-36},{"type":"public-key","alg":-257},{"type":"public-key","alg":-258},{"type":"public-key","alg":-259},{"type":"public-key","alg":-37},{"type":"public-key","alg":-38},{"type":"public-key","alg":-39},{"type":"public-key","alg":-8}],"timeout":300000,"authenticatorSelection":{"authenticatorAttachment":"platform","requireResidentKey":false,"userVerification":"required"}},"cost":"2562047h47m16.854775807s"}"

![](data:image/svg+xml,%3Csvg xmlns="w3.org/2000/svg" viewBox="0 0 1592 1256"%3E%3C/svg%3E align="left")

7-15

感觉在一起打黑客松,不过有点长期的意思,不仅仅idea呈现,更要打磨好。

今日目标

  1. 建立api2,美化和自动化一些api调用,完成所有API的测试

    1.  返回基本稳定:
       flutter: Response Data is: {code: 200, message: , data: {rp: {name: https://aastar.org, id: aastar.org}, user: {name: jhfnetboy@aastar.org, displayName: jhfnetboy@aastar.org, id: amhmbmV0Ym95QGFhc3Rhci5vcmc}, challenge: stRTZeYZrgWvt12EiDKyGB3I0JoueH1Z27qNtSrLswo, pubKeyCredParams: [{type: public-key, alg: -7}, {type: public-key, alg: -35}, {type: public-key, alg: -36}, {type: public-key, alg: -257}, {type: public-key, alg: -258}, {type: public-key, alg: -259}, {type: public-key, alg: -37}, {type: public-key, alg: -38}, {type: public-key, alg: -39}, {type: public-key, alg: -8}], timeout: 300000, authenticatorSelection: {authenticatorAttachment: platform, requireResidentKey: false, userVerification: required}}, cost: 2562047h47m16.854775807s}
      
       flutter: Response Headers is: connection: keep-alive
       flutter: x-render-origin-server: Render
       flutter: access-control-allow-origin: *
       flutter: date: Mon, 15 Jul 2024 04:25:07 GMT
       flutter: content-encoding: gzip
       flutter: vary: Accept-Encoding
       flutter: cf-cache-status: DYNAMIC
       flutter: content-type: application/json; charset=utf-8
       flutter: rndr-id: 48bb0b31-14f4-4ea7
       flutter: server: cloudflare
       flutter: access-control-allow-credentials: true
       flutter: access-control-allow-headers: Content-Type, Content-Length, Authorization, X-Requested-With, User-Agent, Origin, Host, Connection, Accept, Accept-Encoding, Accept-Language, DNT, Keep-Alive, Referer, Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site
       flutter: alt-svc: h3=":443"; ma=86400
       flutter: access-control-allow-methods: GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD
       flutter: content-length: 373
       flutter: cf-ray: 8a36fc9e7faea192-BKK
       flutter: Response realUri is: https://anotherairaccountcommunitynode.onrender.com/api/passkey/v1/reg
       flutter: Response requestOptions is: Instance of 'RequestOptions'
      
    2. 对于response要有一个处理函数,不同API,组合在不同处理函数中

      1. 接口有

        1. /api/passkey/v1/reg/prepare

          1. 入参: email address

            1.  {
                   "email": "jhfnetboy@aastar.org"
               }
              
  1. 出参:verify code to Email

    1. /api/passkey/v1/reg
  2. Email address, verify code

    1.  {
         "captcha": "111111",
         "email": "jhfnetboy@aastar.org",
         "origin": "https://AAStar.org"
       }
      
  3. Success with a reponse with Credential Options

    1.  {
           "attestation": "none",
           "authenticatorSelection": {
               "authenticatorAttachment": "platform",
               "requireResidentKey": true,
               "residentKey": "discouraged",
               "userVerification": "required"
           },
           "challenge": [
               0
           ],
           "excludeCredentials": [
               {
                   "id": [
                       0
                   ],
                   "transports": [
                       "usb"
                   ],
                   "type": "public-key"
               }
           ],
           "extensions": {
               "property1": "string",
               "property2": "string"
           },
           "pubKeyCredParams": [
               {
                   "alg": -7,
                   "type": "public-key"
               }
           ],
           "rp": {
               "icon": "string",
               "id": "string",
               "name": "string"
           },
           "timeout": 0,
           "user": {
               "displayName": "string",
               "icon": "string",
               "id": null,
               "name": "string"
           }
       }
      
       另一个结构:
       const optionsFromServer = {
           "challenge": "random_string", // 需要转换为ArrayBuffer
           "rp": {                        // 我的网站信息
             "name": "My Website",
             "id": "mywebsite.com"  //一级或者二级域名
           },
           "user": {                     // 用户信息
             "name": "anthony@email.com",                  
             "displayName": "Anthony",
             "id": "USER_ID_12345678910" // 需要转换为ArrayBuffer
           },
           "pubKeyCredParams": [  //接受的公钥类型
             {
               "type": "public-key",
               "alg": -7                  // 接受的算法,-7是ES256
             }
           ],
           "authenticatorSelection": {
               authenticatorAttachment: "platform",  // 使用 `platform` 允许Windows Hello或TouchID的
      
       ​                                        // 身份验证器。使用 `cross-platform` 允许身份验证器,
      
       ​                                        // 如Yubikey
       ​    },
       ​    "timeout": 60000              // 以毫秒为单位
       };
      
    1. /api/passkey/v1/reg/verify
  4.  要本地生成credential:​{    "authenticatorAttachment": "string",    "clientExtensionResults": {        "property1": "string",        "property2": "string"    },    "id": "string",    "rawId": [        0    ],    "response": {        "attestationObject": [            0        ],        "clientDataJSON": [            0        ],        "transports": [            "string"        ]    },    "transports": [        "string"    ],    "type": "string"}
    
  5.  ​response: 为啥返回了一个代码段?这个给谁用啊?{    "account_address": "string",    "account_init_code": "string"}
    
  1. /api/passkey/v1/sign

  2. /api/passkey/v1/sign/verify

  3. /api/passkey/v1/payment/sign

  4. /api/passkey/v1/payment/sign/verify

  1. 建立注册、登录界面

    1. input ,卡住了,等rory支援

    2. 明天先后台挨个按顺序调用一遍,用Davidxu的新数据

  2. 基于webauthn建立基础的流程

    1. 添加webauthn

    2.  await client.register("Arnaud", "1628a2b4-bb36-4a7b-9ccd-742b3238e3d3", {
         "authenticatorType": "auto",
         "userVerification": "required",
         "discoverable": "preferred",
         "timeout": 60000,
         "attestation": true
       })
      
  1. 登录后进入主界面

  2. 能够根据指定结构生成QR,基于交易数据(参考Clark提交给基础结构),扫码后能够解析QR

    1. 目前只扫码定价好的item付款
  3. 扫码解析后,提交给手机webautn后台做二次签名

  4. 组合成为全面的交易数据(Davidxu做确认)

  5. post给relay后台(API和dylan确认)获取gas sponsor签名

  6. 提交给node,调用node的DVT来做一次验证

  7. 完成二次签名验证,提交给bundler,正常4337流程

  8. validateOP需要钱包的链上合约需要有BLS验证(这个先跳过,后面补)

  9. 执行,扣gas,relay记账

  10. 目标行为交易,例如转账OK,获得异步的userop hash和tx hash(问clark)


一个学习资源:https://www.kindacode.com/cat/mobile/flutter/

awsome webauthn:https://github.com/yackermann/awesome-webauthn?tab=readme-ov-file

调试工具:https://inabajunmr.github.io/webauthn-viewer/

passkey:https://github.com/Dashlane/android-passkey-example

黑客松的用passkey,但不确定是否对交易进行了签名:https://ethglobal.com/showcase/passkeys-safe-7t2cf

基于Moblie 的社区多签:

  1. 安装App,Email注册账户,注册passkey生物公钥,获得AA Account

  2. 设置安全邮箱(最好和第一个邮箱是跨国分开,政府隔离)

  3. 加入社区,参与社区活动,完成社区任务,玩社区游戏,更多互动,获得社区积分

  4. 到达积分后等级升级,成为投票人;可以作为拨款项目多签人(独立项目资金库)和国库多签人。

  5. 在社区Dashboard发起项目,投票通过预算后获得独立拨款,发消息给国库多签人,每月发消息给项目金库多签人消息;

  6. 多签人基于NFT收到消息,对项目或者项目日常review后,确认,按手指/刷脸进行签名

  7. 交易通过


https://docs.bananahq.io/integration/passkey-signing 宝藏!

https://pub.dev/packages/web3auth_flutterweb3auth.io的文档和包,很不错

todo read:

https://github.com/peteruche21/Idw-poc/blob/main/packages/flutter/lib/src/signer/passkey.dart

https://docs.bananahq.io/integration/wip-zk-based-2fa/zkauth-module

https://hackmd.io/nJr0h2XFRvS8ReLYMXBWNg

https://biconomy.notion.site/ERC-4337-Decoding-EntryPoint-and-UserOperation-c9589d072041413486d2caef49260f9f

https://github.com/Web3Auth/single-factor-auth-flutter

https://github.com/peteruche21/Idw-poc/blob/main/packages/core/src/SimplePasskeyAccount.sol


7-18

忙成狗,为啥啊。。。搞不懂

USE_BIOMETRIC分支测试

flutter doctor -v

打包API到android调试:

  1.  keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA \        -keysize 2048 -validity 10000 -alias upload                [Storing /Users/jason/upload-keystore.jks]
    
  2. apk和华为有点问题,又坑。。。

https://docs.flutter.cn/deployment/android/#signing-the-app

Pdf

https://drive.google.com/file/d/15aGVKKYu8Az2hPnuPnQ___iwVsc3co97/view?usp=drive_link

Did you find this article valuable?

Support jLab by becoming a sponsor. Any amount is appreciated!