Skip to content

Commit

Permalink
feat: ✨ Add new utils methods for keychain
Browse files Browse the repository at this point in the history
  • Loading branch information
redDwarf03 committed Feb 18, 2025
1 parent eaff7f2 commit f749dcd
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,32 @@ It supports the Archethic Cryptography rules which are:
<br/>
</details>

## Utils

### Keychain Utilities

The `KeychainUtil` mixin provides utility methods for handling keychain transactions on the Archethic network.

#### Methods

- `Future<bool> 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<List<String>> 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<String?> _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<Transaction?> _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
Expand Down
1 change: 1 addition & 0 deletions lib/archethic_lib_dart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
31 changes: 31 additions & 0 deletions lib/src/model/keychain.dart
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,34 @@ Jwk keyToJWK(Uint8List publicKey, String keyId) {
throw Exception('Curve not supported');
}
}

Uint8List jwkToKey(Map<String, dynamic> 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');
}
}
132 changes: 132 additions & 0 deletions lib/src/utils/keychain_util.dart
Original file line number Diff line number Diff line change
@@ -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<bool> 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<List<String>> 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 = <String>[];
final json = jsonDecode(transaction.data!.content!);

if (json.containsKey('verificationMethod')) {
for (final method in json['verificationMethod']) {
if (method is Map<String, dynamic> &&
method.containsKey('publicKeyJwk')) {
final publicKeyJwk = method['publicKeyJwk'];
if (publicKeyJwk is Map<String, dynamic> &&
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 = <String>[];
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<String?> _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<Transaction?> _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];
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit f749dcd

Please sign in to comment.