From f749dcd9b0f190b01ac46a38f8c1cc59a80fe5e5 Mon Sep 17 00:00:00 2001 From: redDwarf03 Date: Tue, 18 Feb 2025 11:11:07 +0100 Subject: [PATCH] feat: :sparkles: Add new utils methods for keychain --- CHANGELOG.md | 4 + README.md | 26 ++++++ lib/archethic_lib_dart.dart | 1 + lib/src/model/keychain.dart | 31 ++++++++ lib/src/utils/keychain_util.dart | 132 +++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 6 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 lib/src/utils/keychain_util.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 925681c1..7a0bb6bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 7.4.0 +- Add a method to verify the existence of a keychain using a public key. +- Add a method to retrieve the genesis address of services in a keychain using a public key. + # 7.3.0 - Add a method to verify archethic keys diff --git a/README.md b/README.md index a039de07..a626cb2b 100644 --- a/README.md +++ b/README.md @@ -567,6 +567,32 @@ It supports the Archethic Cryptography rules which are:
+## Utils + +### Keychain Utilities + +The `KeychainUtil` mixin provides utility methods for handling keychain transactions on the Archethic network. + +#### Methods + +- `Future checkKeychain(String endpoint, Uint8List pubkey)` + - Checks if a keychain exists for the given public key on the specified network endpoint. + - Returns `true` if a keychain is found, `false` otherwise. + +- `Future> keychainAddresses(String endpoint, Uint8List pubkey)` + + - Retrieves the list of keychain addresses associated with the given public key. + - Returns a list of strings representing the addresses. + +- `Future _findKeychainGenesisAddress(String endpoint, Uint8List pubkey) (private method)` + - Computes the genesis address of the keychain for the given public key. + - Returns the genesis address if found, null otherwise. + +- `Future _searchKeychain(String endpoint, String genesisAddress) (private method)` + - Searches for the keychain transaction associated with the genesis address. + - Returns the transaction if found, null otherwise. + + ## Running the tests ```bash diff --git a/lib/archethic_lib_dart.dart b/lib/archethic_lib_dart.dart index ed63e94b..4216d34e 100644 --- a/lib/archethic_lib_dart.dart +++ b/lib/archethic_lib_dart.dart @@ -58,6 +58,7 @@ export 'src/utils/confirmations/transaction_event.dart'; export 'src/utils/confirmations/transaction_remote.dart'; export 'src/utils/confirmations/transaction_sender.dart'; export 'src/utils/crypto.dart'; +export 'src/utils/keychain_util.dart'; export 'src/utils/notification_util.dart'; export 'src/utils/oracle/archethic_oracle.dart'; export 'src/utils/transaction_error_util.dart'; diff --git a/lib/src/model/keychain.dart b/lib/src/model/keychain.dart index 61db10af..9167069c 100644 --- a/lib/src/model/keychain.dart +++ b/lib/src/model/keychain.dart @@ -394,3 +394,34 @@ Jwk keyToJWK(Uint8List publicKey, String keyId) { throw Exception('Curve not supported'); } } + +Uint8List jwkToKey(Map jwk) { + String normalizeBase64(String base64) { + final buffer = StringBuffer(base64); + while (buffer.length % 4 != 0) { + buffer.write('='); + } + return buffer.toString(); + } + + final String kty = jwk['kty']; + final String crv = jwk['crv']; + final String xBase64 = jwk['x']; + final x = base64Url.decode(normalizeBase64(xBase64)); + + if (kty == 'OKP' && crv == 'Ed25519') { + // Ed25519 format (Curve ID = 0) + return Uint8List.fromList([0, 0] + x); + } else if (kty == 'EC' && (crv == 'P-256' || crv == 'secp256k1')) { + // Extract x and y coordinates for EC keys + final String yBase64 = jwk['y']; + final y = base64Url.decode(normalizeBase64(yBase64)); + + final curveID = (crv == 'P-256') ? 1 : 2; + + // Format: [CurveID, 0] + X + Y + return Uint8List.fromList([curveID, 0] + x + y); + } else { + throw Exception('Unsupported JWK format'); + } +} diff --git a/lib/src/utils/keychain_util.dart b/lib/src/utils/keychain_util.dart new file mode 100644 index 00000000..fcb6255f --- /dev/null +++ b/lib/src/utils/keychain_util.dart @@ -0,0 +1,132 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:archethic_lib_dart/src/model/keychain.dart'; +import 'package:archethic_lib_dart/src/model/transaction.dart'; +import 'package:archethic_lib_dart/src/services/api_service.dart'; +import 'package:archethic_lib_dart/src/utils/crypto.dart'; +import 'package:archethic_lib_dart/src/utils/utils.dart'; +import 'package:logging/logging.dart'; + +/// A mixin providing utility methods to interact with the Archethic blockchain keychain. +/// +/// This mixin includes functions to verify the existence of a keychain, retrieve keychain addresses, +/// and perform related cryptographic operations. +mixin KeychainUtil { + final _logger = Logger('KeychainUtil'); + + /// Checks if a keychain exists for the given public key on the specified endpoint. + /// + /// - [endpoint]: The API endpoint to query. + /// - [pubkey]: The public key as a `Uint8List`. + /// + /// Returns `true` if the keychain exists, `false` otherwise. + Future checkKeychain(String endpoint, Uint8List pubkey) async { + final genesisAddress = await _findKeychainGenesisAddress(endpoint, pubkey); + if (genesisAddress == null || genesisAddress.isEmpty) { + return false; + } + final transaction = await _searchKeychain(endpoint, genesisAddress); + if (transaction == null) { + return false; + } + + return true; + } + + /// Retrieves the list of keychain addresses associated with the given public key. + /// + /// - [endpoint]: The API endpoint to query. + /// - [pubkey]: The public key as a `Uint8List`. + /// + /// Returns a list of keychain addresses as hexadecimal strings. + Future> keychainAddresses( + String endpoint, + Uint8List pubkey, + ) async { + // We hardcode the curve ID (0 = ed25519 by default) + final genesisAddress = await _findKeychainGenesisAddress(endpoint, pubkey); + if (genesisAddress == null || genesisAddress.isEmpty) { + return []; + } + + final transaction = await _searchKeychain(endpoint, genesisAddress); + if (transaction == null || + transaction.data == null || + transaction.data!.content == null) { + return []; + } + + final publicKeys = []; + final json = jsonDecode(transaction.data!.content!); + + if (json.containsKey('verificationMethod')) { + for (final method in json['verificationMethod']) { + if (method is Map && + method.containsKey('publicKeyJwk')) { + final publicKeyJwk = method['publicKeyJwk']; + if (publicKeyJwk is Map && + publicKeyJwk.containsKey('x')) { + try { + final publicKey = jwkToKey(publicKeyJwk); + publicKeys.add(uint8ListToHex(publicKey)); + } catch (e) { + _logger.severe( + 'Error converting JWK to public key: $e', + ); + } + } + } + } + } + + final addressList = []; + for (final publicKey in publicKeys) { + addressList.add( + '00${uint8ListToHex(hash(hexToUint8List(publicKey)))}', + ); + } + + return addressList; + } + + /// Finds the genesis address for a keychain associated with the given public key. + /// + /// - [endpoint]: The API endpoint to query. + /// - [pubkey]: The public key as a `Uint8List`. + /// + /// Returns the genesis address as a `String`, or `null` if not found. + Future _findKeychainGenesisAddress( + String endpoint, + Uint8List pubkey, + ) async { + // We hardcode the curve ID (0 = ed25519 by default) + final address = '00${uint8ListToHex(hash(pubkey))}'; + final genesisAddress = await ApiService(endpoint).getGenesisAddress( + address, + ); + return genesisAddress.address; + } + + /// Searches for the keychain transaction using its genesis address. + /// + /// - [endpoint]: The API endpoint to query. + /// - [genesisAddress]: The genesis address of the keychain. + /// + /// Returns a `Transaction` object if found, or `null` otherwise. + Future _searchKeychain( + String endpoint, + String genesisAddress, + ) async { + final transactionMap = await ApiService(endpoint).getLastTransaction( + [genesisAddress], + request: 'address data { content } type', + ); + + if (transactionMap[genesisAddress] == null || + transactionMap[genesisAddress]!.type != 'keychain') { + return null; + } + return transactionMap[genesisAddress]; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 8eaf20f8..4b292530 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ name: archethic_lib_dart description: Archethic dart library for Flutter for Node and Browser. This library aims to provide a easy way to create Archethic transaction and to send them over the network homepage: https://github.com/archethic-foundation/libdart -version: 7.3.0 +version: 7.4.0 environment: sdk: ">=3.5.3 <4.0.0"