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"