Skip to content

Commit

Permalink
feat(flutter/catalyst_key_derivation): Key derivation integration (#1149
Browse files Browse the repository at this point in the history
)

* feat(cat-voice-package): add rust key derivation implementation

Signed-off-by: bkioshn <[email protected]>

* refactor: move uikit_example into utilities dir

* fix: exclude example packages from melos bootstrap

* refactor: move poc_local_storage into catalyst_voices/utilities

* fix: key derivation implementation

Signed-off-by: bkioshn <[email protected]>

* fix: auto gen file

Signed-off-by: bkioshn <[email protected]>

* refactor: move catalyst_voices_remote_widgets package to catalyst_voices/utilities dir

* refactor: move scripts into catalyst_voices dir

* refactor: move catalyst_voices/packages into catalyst_voices/packages/internal

* refactor: move catalyst_voices_packages into catalyst_voices/packages/external

* refactor: move melos.yaml into catalyst_voices

* fix: add word to dict

Signed-off-by: bkioshn <[email protected]>

* fix: key derivation implementation

Signed-off-by: bkioshn <[email protected]>

* fix: remove simple rs

Signed-off-by: bkioshn <[email protected]>

* fix: flutter format

Signed-off-by: bkioshn <[email protected]>

* refactor: move app into apps/voices dir

* refactor: rename packages/external to packages/libs

* docs: update README

* style: dart lint issues

* fix: builder target

* fix: update paths in Earthfile

* fix: uikit_example pubspec + libs examples deps override

* chore: test

* fix: update cspell and check markdown rules

* fix: README formatting

* fix: add description to module

Signed-off-by: bkioshn <[email protected]>

* fix: add cspell dict

Signed-off-by: bkioshn <[email protected]>

* feat: add sign data, verify sig, get pubkey

Signed-off-by: bkioshn <[email protected]>

* fix: auto gen file

Signed-off-by: bkioshn <[email protected]>

* feat: add rust project setup files

Signed-off-by: bkioshn <[email protected]>

* fix: flutter example

Signed-off-by: bkioshn <[email protected]>

* fix: build-web target path

* fix: remove build-web target suffix separator

* fix: wallet-automation tests target path

* fix: wallet-automation builder target

* fix: voices automation tests target

* fix: code-generator target path

* fix: typo and comment

Signed-off-by: bkioshn <[email protected]>

* fix: vscode launch.recommended paths

* feat: catalyst_voices justfile

* refactor: rename catalyst_voices_remote_widgets to remote_widgets

* fix: remote_widget spelling and licence checks

* fix: remote_widget analyzer

* refactor: move repo-catalyst-voices from root Earthfile to catalyst_voices/Earthfile

* fix: restructure rust key derivation and solve dispose value

Signed-off-by: bkioshn <[email protected]>

* fix: auto gen file

Signed-off-by: bkioshn <[email protected]>

* fix: ignore auto gen file

Signed-off-by: bkioshn <[email protected]>

* fix: remove auto gen file

Signed-off-by: bkioshn <[email protected]>

* fix: add justfile, fix readme, and remove run.sh

Signed-off-by: bkioshn <[email protected]>

* fix: markdown

Signed-off-by: bkioshn <[email protected]>

* fix: update git ignore

Signed-off-by: bkioshn <[email protected]>

* fix: ignore spelling check for dart auto gen file

Signed-off-by: bkioshn <[email protected]>

* chore: add flutter_rust_bridge headers to launch.recommended.json for web

* feat: add loader to transaction panel

* chore: move ed25519 code to catalyst_key_derivation package

* fix: add ci check and build

Signed-off-by: bkioshn <[email protected]>

* fix: add earthfile and fix justfile

Signed-off-by: bkioshn <[email protected]>

* fix: deprecated api

* fix: file drop

* fix: force downgraded version of flutter_dropzone

* chore: update repository links + codeowners paths

* fix: deploy uikit workflow

* fix: uikit_example earthly build target

* fix: uikit deploy workflow path

* chore: integration tests docs readme paths

* chore: remote widget readme path

* chore: markdown ignore macos pods path fix

* chore: integration test scripts

* chore: build_runner assets

* chore: document flutter_dropzone issue

* chore: revert dropzone fixes

* chore: regenerate code

* feat: key derivation

* fix: remove frb generated rust file

Signed-off-by: bkioshn <[email protected]>

* fix: rename

Signed-off-by: bkioshn <[email protected]>

* fix: add word to dict

Signed-off-by: bkioshn <[email protected]>

* fix: cspell ignore auto gen flutter file

Signed-off-by: bkioshn <[email protected]>

* fix: auto gen file

Signed-off-by: bkioshn <[email protected]>

* fix: file path cspell

Signed-off-by: bkioshn <[email protected]>

* test: check target

Signed-off-by: bkioshn <[email protected]>

* fix: rust earthfile

Signed-off-by: bkioshn <[email protected]>

* chore: add blueprint.cue along with new Earthfile

Signed-off-by: bkioshn <[email protected]>

* chore: fix blueprint.cue

Signed-off-by: bkioshn <[email protected]>

* fix: linter and format

Signed-off-by: bkioshn <[email protected]>

* fix: remove simple.rs

Signed-off-by: bkioshn <[email protected]>

* fix: ci check create dummy

Signed-off-by: bkioshn <[email protected]>

* chore: adjust breaking changes

* fix: ci check create dummy

Signed-off-by: bkioshn <[email protected]>

* fix: rust ci

Signed-off-by: bkioshn <[email protected]>

* fix: rust format

Signed-off-by: bkioshn <[email protected]>

* feat: add to hex functions

Signed-off-by: bkioshn <[email protected]>

* fix: auto gen file

Signed-off-by: bkioshn <[email protected]>

* fix: add toHex example to dart

Signed-off-by: bkioshn <[email protected]>

* fix: tests

* docs: update example

* feat: update code generation to include web/pkg, it's needed for package distrubution

* chore: code generation

* fix: initializer

* chore: cleanup

* fix: code-generator

Signed-off-by: bkioshn <[email protected]>

* docs: document how to contribute changes to the package

* feat: add earthlyignore

* chore: regenerate files

* fix: bump ci version to 3.2.24

Signed-off-by: bkioshn <[email protected]>

* fix: update rustfmt.toml

Signed-off-by: bkioshn <[email protected]>

* fix: point flutter_rust_bridge ci to 3.2.23

Signed-off-by: bkioshn <[email protected]>

* fix: ci bump version and deny.toml

Signed-off-by: bkioshn <[email protected]>

* fix: typo

Signed-off-by: bkioshn <[email protected]>

* chore: add integration tests for catalyst_key_derivation

* docs: document that a private key should be dropped

* docs: add flutter_rust_bridge issue link

* style: typo

* style: reformat

* docs: update readme

* docs: update readme

* chore: disable cache

* chore: workaround integration tests

* chore: revert workaround

* chore: configure nginx headers for flutter_rust_bridge

* chore: drop enable-threads.js to test if it fixes ci

* chore: make global nginx headers

* chore: add again the workaround

* chore: don't compile the app for integration tests, they do it themselves

* chore: run tests on firefox only

* chore: run only on chrome without workaround

* chore: script load order

* chore: reenable firefox tests

* docs: clarify todo

* feat: skip catalyst key derivation tests if init didn't work

* chore: update flutter ci

---------

Signed-off-by: bkioshn <[email protected]>
Co-authored-by: bkioshn <[email protected]>
Co-authored-by: Damian Molinski <[email protected]>
Co-authored-by: bkioshn <[email protected]>
Co-authored-by: Steven Johnson <[email protected]>
  • Loading branch information
5 people authored Nov 13, 2024
1 parent 0712347 commit 618f769
Show file tree
Hide file tree
Showing 76 changed files with 2,932 additions and 169 deletions.
1 change: 1 addition & 0 deletions .earthlyignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
**/*.iml
**/coverage/
**/test_reports/
**/*.log

# node related

Expand Down
7 changes: 6 additions & 1 deletion .vscode/launch.recommended.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
"program": "lib/configs/main_web.dart",
"args": [
"--dart-define",
"SENTRY_DSN=REPLACE_WITH_SENTRY_DSN_URL"
"SENTRY_DSN=REPLACE_WITH_SENTRY_DSN_URL",
// flutter_rust_bridge: https://cjycode.com/flutter_rust_bridge/manual/miscellaneous/web-cross-origin#when-flutter-run
"--web-header",
"Cross-Origin-Opener-Policy=same-origin",
"--web-header",
"Cross-Origin-Embedder-Policy=require-corp"
]
},
{
Expand Down
1 change: 1 addition & 0 deletions catalyst_voices/.earthlyignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
**/*.iml
**/coverage/
**/test_reports/
**/*.log

# node related

Expand Down
6 changes: 5 additions & 1 deletion catalyst_voices/apps/voices/integration_test/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ VERSION 0.8
IMPORT ../../.. AS catalyst-voices

integration-test-web:
FROM catalyst-voices+build-web
FROM catalyst-voices+builder
ARG TARGETARCH
ARG browser
LET driver_port = 4444
Expand All @@ -21,6 +21,9 @@ integration-test-web:
# IF [ $browser = "edge" && $TARGETARCH = "amd64" ]]
# LET driver = "msedgedriver"
# END

WORKDIR /frontend/apps/voices

RUN ($driver --port=$driver_port > $driver.log &) && \
sleep 5 && \
flutter drive --driver=test_driver/integration_tests.dart \
Expand All @@ -35,6 +38,7 @@ integration-test-web:
WAIT
SAVE ARTIFACT $driver.log AS LOCAL $driver.log
END

IF [ -f fail ]
RUN --no-cache echo ""$browser" integration test failed" && \
echo "Printing "$driver" logs..." && \
Expand Down
4 changes: 4 additions & 0 deletions catalyst_voices/apps/voices/lib/configs/bootstrap.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';

import 'package:catalyst_key_derivation/catalyst_key_derivation.dart';
import 'package:catalyst_voices/app/app.dart';
import 'package:catalyst_voices/configs/app_bloc_observer.dart';
import 'package:catalyst_voices/configs/sentry_service.dart';
Expand Down Expand Up @@ -90,6 +91,9 @@ Future<BootstrapArgs> bootstrap() async {

await Dependencies.instance.init();

// Key derivation needs to be initialized before it can be used
await CatalystKeyDerivation.init();

final router = AppRouter.init(
guards: const [
MilestoneGuard(),
Expand Down
4 changes: 3 additions & 1 deletion catalyst_voices/apps/voices/lib/dependency/dependencies.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';

import 'package:catalyst_cardano/catalyst_cardano.dart';
import 'package:catalyst_key_derivation/catalyst_key_derivation.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_repositories/catalyst_voices_repositories.dart';
import 'package:catalyst_voices_services/catalyst_voices_services.dart';
Expand Down Expand Up @@ -66,7 +67,8 @@ final class Dependencies extends DependencyProvider {

void _registerServices() {
registerLazySingleton<Storage>(() => const SecureStorage());
registerLazySingleton<KeyDerivation>(KeyDerivation.new);
registerLazySingleton<CatalystKeyDerivation>(CatalystKeyDerivation.new);
registerLazySingleton<KeyDerivation>(() => KeyDerivation(get()));
registerLazySingleton<KeychainProvider>(VaultKeychainProvider.new);
registerLazySingleton<DummyAuthStorage>(SecureDummyAuthStorage.new);
registerLazySingleton<Downloader>(Downloader.new);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class _BlocSummary extends StatelessWidget {
},
builder: (context, state) {
if (state == null) {
return const Offstage();
return const _SummaryPlaceholder();
}

return _Summary(
Expand All @@ -131,6 +131,20 @@ class _BlocSummary extends StatelessWidget {
}
}

class _SummaryPlaceholder extends StatelessWidget {
const _SummaryPlaceholder();

@override
Widget build(BuildContext context) {
return const Center(
child: Padding(
padding: EdgeInsets.all(32),
child: CircularProgressIndicator(),
),
);
}
}

class _Summary extends StatelessWidget {
final Set<AccountRole> roles;
final WalletInfo walletInfo;
Expand Down
1 change: 1 addition & 0 deletions catalyst_voices/apps/voices/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies:
catalyst_cardano: ^0.3.0
catalyst_cardano_serialization: ^0.4.0
catalyst_cardano_web: ^0.3.0
catalyst_key_derivation: ^0.1.0
catalyst_voices_assets:
path: ../../packages/internal/catalyst_voices_assets
catalyst_voices_blocs:
Expand Down
80 changes: 80 additions & 0 deletions catalyst_voices/apps/voices/web/enable-threads.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// TODO(dtscalac): remove workaround when flutter_rust_bridge supports crossOriginIsolated for flutter drive:
// https://github.com/fzyzcjy/flutter_rust_bridge/issues/2407

// https://github.com/orgs/community/discussions/13309#discussioncomment-3844940
// NOTE: This file creates a service worker that cross-origin-isolates the page (read more here: https://web.dev/coop-coep/) which allows us to use wasm threads.
// Normally you would set the COOP and COEP headers on the server to do this, but Github Pages doesn't allow this, so this is a hack to do that.

/* Edited version of: coi-serviceworker v0.1.6 - Guido Zuidhof, licensed under MIT */
// From here: https://github.com/gzuidhof/coi-serviceworker
if (typeof window === 'undefined') {
self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", e => e.waitUntil(self.clients.claim()));

async function handleFetch(request) {
if (request.cache === "only-if-cached" && request.mode !== "same-origin") {
return;
}

if (request.mode === "no-cors") { // We need to set `credentials` to "omit" for no-cors requests, per this comment: https://bugs.chromium.org/p/chromium/issues/detail?id=1309901#c7
request = new Request(request.url, {
cache: request.cache,
credentials: "omit",
headers: request.headers,
integrity: request.integrity,
destination: request.destination,
keepalive: request.keepalive,
method: request.method,
mode: request.mode,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
signal: request.signal,
});
}

let r = await fetch(request).catch(e => console.error(e));

if (r.status === 0) {
return r;
}

const headers = new Headers(r.headers);
// NOTE https://github.com/fzyzcjy/flutter_rust_bridge/issues/1618 changes to require-corp
headers.set("Cross-Origin-Embedder-Policy", "require-corp"); // credentialless or require-corp
headers.set("Cross-Origin-Opener-Policy", "same-origin");

return new Response(r.body, { status: r.status, statusText: r.statusText, headers });
}

self.addEventListener("fetch", function (e) {
e.respondWith(handleFetch(e.request)); // respondWith must be executed synchonously (but can be passed a Promise)
});

} else {
(async function () {
if (window.crossOriginIsolated !== false) return;

let registration = await navigator.serviceWorker.register(window.document.currentScript.src).catch(e => console.error("COOP/COEP Service Worker failed to register:", e));
if (registration) {
console.log("COOP/COEP Service Worker registered", registration.scope);

registration.addEventListener("updatefound", () => {
console.log("Reloading page to make use of updated COOP/COEP Service Worker.");
window.location.reload();
});

// If the registration is active, but it's not controlling the page
if (registration.active && !navigator.serviceWorker.controller) {
console.log("Reloading page to make use of COOP/COEP Service Worker.");
window.location.reload();
}
}
})();
}

// Code to deregister:
// let registrations = await navigator.serviceWorker.getRegistrations();
// for(let registration of registrations) {
// await registration.unregister();
// }
4 changes: 3 additions & 1 deletion catalyst_voices/apps/voices/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
</head>

<body>
<script src="flutter_bootstrap.js" async></script>
<!-- TODO(dtscalac): make flutter_bootstrap.js just async when enable-threads.js is no longer needed -->
<script src="flutter_bootstrap.js" async="false"></script>
<script src="enable-threads.js" async="false"></script>
</body>

</html>
5 changes: 5 additions & 0 deletions catalyst_voices/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ http {
server {
listen 80;
server_name localhost;

# https://cjycode.com/flutter_rust_bridge/manual/miscellaneous/web-cross-origin#background
add_header Cross-Origin-Opener-Policy "same-origin";
add_header Cross-Origin-Embedder-Policy "require-corp";

location / {
root /app;
index index.html;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';

import 'package:catalyst_cardano_serialization/catalyst_cardano_serialization.dart';
import 'package:catalyst_key_derivation/catalyst_key_derivation.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_blocs/src/registration/cubits/keychain_creation_cubit.dart';
import 'package:catalyst_voices_blocs/src/registration/cubits/recover_cubit.dart';
Expand All @@ -27,7 +28,7 @@ final class RegistrationCubit extends Cubit<RegistrationState>
final RegistrationService _registrationService;
final RegistrationProgressNotifier _progressNotifier;

Ed25519KeyPair? _keyPair;
Bip32Ed25519XPrivateKey? _masterKey;
Transaction? _transaction;

/// Returns [RegistrationCubit] if found in widget tree. Does not add
Expand Down Expand Up @@ -168,20 +169,18 @@ final class RegistrationCubit extends Cubit<RegistrationState>
final wallet = _walletLinkCubit.selectedWallet!;
final roles = _walletLinkCubit.roles;

final keyPair = await _registrationService.deriveAccountRoleKeyPair(
seedPhrase: seedPhrase,
roles: roles,
);
final masterKey =
await _registrationService.deriveMasterKey(seedPhrase: seedPhrase);

final transaction = await _registrationService.prepareRegistration(
wallet: wallet,
// TODO(dtscalac): inject the networkId
networkId: NetworkId.testnet,
keyPair: keyPair,
masterKey: masterKey,
roles: roles,
);

_keyPair = keyPair;
_masterKey = masterKey;
_transaction = transaction;

final fee = transaction.body.fee;
Expand All @@ -197,7 +196,8 @@ final class RegistrationCubit extends Cubit<RegistrationState>
} on RegistrationException catch (error, stackTrace) {
_logger.severe('Prepare registration', error, stackTrace);

_keyPair = null;
_masterKey?.drop();
_masterKey = null;
_transaction = null;

final exception = LocalizedRegistrationException.from(error);
Expand All @@ -220,7 +220,7 @@ final class RegistrationCubit extends Cubit<RegistrationState>
),
);

final keyPair = _keyPair!;
final masterKey = _masterKey!;
final transaction = _transaction!;

final password = _keychainCreationCubit.password;
Expand All @@ -234,8 +234,7 @@ final class RegistrationCubit extends Cubit<RegistrationState>
unsignedTx: transaction,
roles: roles,
lockFactor: lockFactor,
// TODO(dtscalac): Update key value when derivation is final.
keyPair: keyPair,
masterKey: masterKey,
);

await _userService.useAccount(account);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies:
catalyst_cardano: ^0.3.0
catalyst_cardano_serialization: ^0.4.0
catalyst_cardano_web: ^0.3.0
catalyst_key_derivation: ^0.1.0
catalyst_voices_brands:
path: ../catalyst_voices_brands
catalyst_voices_models:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,43 @@
import 'package:catalyst_cardano_serialization/catalyst_cardano_serialization.dart';
import 'package:catalyst_key_derivation/catalyst_key_derivation.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:ed25519_hd_key/ed25519_hd_key.dart';

/// Derives key pairs from a seed phrase.
final class KeyDerivation {
const KeyDerivation();
final CatalystKeyDerivation _keyDerivation;

/// Derives an [Ed25519KeyPair] from a [seedPhrase] and [path].
const KeyDerivation(this._keyDerivation);

Future<Bip32Ed25519XPrivateKey> deriveMasterKey({
required SeedPhrase seedPhrase,
}) {
return _keyDerivation.deriveMasterKey(
mnemonic: seedPhrase.mnemonic,
);
}

/// Derives an [Ed25519KeyPair] from a [masterKey] and [path].
///
/// Example [path]: m/0'/2147483647'
///
// TODO(dtscalac): this takes around 2.5s to execute, optimize it
// or move to a JS web worker.
Future<Ed25519KeyPair> deriveKeyPair({
required SeedPhrase seedPhrase,
Future<Bip32Ed25519XKeyPair> deriveKeyPair({
required Bip32Ed25519XPrivateKey masterKey,
required String path,
}) async {
final masterKey = await ED25519_HD_KEY.derivePath(
path,
seedPhrase.uint8ListSeed,
);
final privateKey = await masterKey.derivePrivateKey(path: path);
final publicKey = await privateKey.derivePublicKey();

final privateKey = masterKey.key;
final publicKey = await ED25519_HD_KEY.getPublicKey(privateKey, false);

return Ed25519KeyPair(
publicKey: Ed25519PublicKey.fromBytes(publicKey),
privateKey: Ed25519PrivateKey.fromBytes(privateKey),
return Bip32Ed25519XKeyPair(
publicKey: publicKey,
privateKey: privateKey,
);
}

/// Derives the [Ed25519KeyPair] for the [role] from a [seedPhrase].
Future<Ed25519KeyPair> deriveAccountRoleKeyPair({
required SeedPhrase seedPhrase,
/// Derives the [Ed25519KeyPair] for the [role] from a [masterKey].
Future<Bip32Ed25519XKeyPair> deriveAccountRoleKeyPair({
required Bip32Ed25519XPrivateKey masterKey,
required AccountRole role,
}) async {
return deriveKeyPair(
seedPhrase: seedPhrase,
masterKey: masterKey,
path: _roleKeyDerivationPath(role),
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:catalyst_cardano_serialization/catalyst_cardano_serialization.dart';
import 'package:catalyst_key_derivation/catalyst_key_derivation.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:catalyst_voices_services/src/lockable.dart';

Expand All @@ -9,9 +9,9 @@ abstract interface class Keychain implements Lockable {

Future<KeychainMetadata> get metadata;

Future<Ed25519PrivateKey?> getMasterKey();
Future<Bip32Ed25519XPrivateKey?> getMasterKey();

Future<void> setMasterKey(Ed25519PrivateKey key);
Future<void> setMasterKey(Bip32Ed25519XPrivateKey key);

Future<void> clear();
}
Loading

0 comments on commit 618f769

Please sign in to comment.