From 32255798cd886ed7ddfd81d48c85faf58aed2da2 Mon Sep 17 00:00:00 2001 From: Stone Pack Date: Fri, 29 Mar 2024 15:08:59 -0700 Subject: [PATCH 1/4] moved from old fork --- packages/web5/lib/src/crypto/crypto.dart | 11 + packages/web5/lib/src/crypto/ed25519.dart | 12 + packages/web5/lib/src/crypto/secp256k1.dart | 12 + packages/web5/lib/src/dids/bearer_did.dart | 2 + .../dids/did_core/did_document_metadata.dart | 7 +- .../did_core/did_verification_method.dart | 15 ++ .../web5/lib/src/dids/did_dht/did_dht.dart | 122 ++++++++++ .../src/dids/did_dht/dns_packet/answer.dart | 16 ++ .../src/dids/did_dht/dns_packet/header.dart | 51 +++- .../src/dids/did_dht/dns_packet/packet.dart | 221 ++++++++++++++++++ .../src/dids/did_dht/registered_did_type.dart | 51 ++++ packages/web5/lib/src/encoders/zbase.dart | 53 +++++ packages/web5/pubspec.yaml | 1 - 13 files changed, 561 insertions(+), 13 deletions(-) create mode 100644 packages/web5/lib/src/dids/did_dht/registered_did_type.dart create mode 100644 packages/web5/lib/src/encoders/zbase.dart diff --git a/packages/web5/lib/src/crypto/crypto.dart b/packages/web5/lib/src/crypto/crypto.dart index 101840b..8fd09b2 100644 --- a/packages/web5/lib/src/crypto/crypto.dart +++ b/packages/web5/lib/src/crypto/crypto.dart @@ -37,6 +37,17 @@ class Crypto { } } + static Uint8List publicKeyToBytes(Jwk publicKey) { + switch (publicKey.kty) { + case Ed25519.kty: + return Ed25519.publicKeyToBytes(publicKey: publicKey); + case Secp256k1.kty: + return Secp256k1.publicKeyToBytes(publicKey: publicKey); + default: + throw Exception('unsupported kty: ${publicKey.kty}'); + } + } + static Future sign(Jwk privateKeyJwk, Uint8List payload) { switch (privateKeyJwk.kty) { case Ecdsa.kty: diff --git a/packages/web5/lib/src/crypto/ed25519.dart b/packages/web5/lib/src/crypto/ed25519.dart index 641696f..2cc65c0 100644 --- a/packages/web5/lib/src/crypto/ed25519.dart +++ b/packages/web5/lib/src/crypto/ed25519.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:typed_data'; import 'package:cryptography/cryptography.dart' as crypto; @@ -51,6 +52,17 @@ class Ed25519 { return privateKeyJwk; } + static Uint8List publicKeyToBytes({required Jwk publicKey}) { + if (publicKey.x == null) { + throw Error(); + } + + final Uint8List encodedKey = utf8.encode(publicKey.x!); + final String base64Url = base64UrlEncode(encodedKey); + + return utf8.encode(base64Url); + } + static Future sign(Jwk privateKey, Uint8List payload) async { final privateKeyBytes = Base64Url.decode(privateKey.d!); diff --git a/packages/web5/lib/src/crypto/secp256k1.dart b/packages/web5/lib/src/crypto/secp256k1.dart index 1567b8a..b51d6b9 100644 --- a/packages/web5/lib/src/crypto/secp256k1.dart +++ b/packages/web5/lib/src/crypto/secp256k1.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; @@ -79,6 +80,17 @@ class Secp256k1 { return Future.value(privateKeyJwk); } + static Uint8List publicKeyToBytes({required Jwk publicKey}) { + if (publicKey.x == null) { + throw Error(); + } + + final Uint8List encodedKey = utf8.encode(publicKey.x!); + final String base64Url = base64UrlEncode(encodedKey); + + return utf8.encode(base64Url); + } + static Future sign(Jwk privateKeyJwk, Uint8List payload) { final sha256 = SHA256Digest(); diff --git a/packages/web5/lib/src/dids/bearer_did.dart b/packages/web5/lib/src/dids/bearer_did.dart index 78ab336..c871880 100644 --- a/packages/web5/lib/src/dids/bearer_did.dart +++ b/packages/web5/lib/src/dids/bearer_did.dart @@ -20,11 +20,13 @@ class BearerDid { String uri; KeyManager keyManager; DidDocument document; + DidDocumentMetadata metadata; BearerDid({ required this.uri, required this.keyManager, required this.document, + this.metadata = const DidDocumentMetadata(), }); Future export() async { diff --git a/packages/web5/lib/src/dids/did_core/did_document_metadata.dart b/packages/web5/lib/src/dids/did_core/did_document_metadata.dart index f866252..b8b1061 100644 --- a/packages/web5/lib/src/dids/did_core/did_document_metadata.dart +++ b/packages/web5/lib/src/dids/did_core/did_document_metadata.dart @@ -1,3 +1,5 @@ +import 'package:web5/src/dids/did_dht/registered_did_type.dart'; + /// contains metadata about the DID document contained in the didDocument /// property. This metadata typically does not change between invocations of /// the resolve and resolveRepresentation functions unless the DID document @@ -51,7 +53,9 @@ class DidDocumentMetadata { /// the scope of the containing DID document. final String? canonicalId; - DidDocumentMetadata({ + final List? types; + + const DidDocumentMetadata({ this.created, this.updated, this.deactivated, @@ -60,6 +64,7 @@ class DidDocumentMetadata { this.nextVersionId, this.equivalentId, this.canonicalId, + this.types, }); Map toJson() { diff --git a/packages/web5/lib/src/dids/did_core/did_verification_method.dart b/packages/web5/lib/src/dids/did_core/did_verification_method.dart index 5d5a2f5..5558269 100644 --- a/packages/web5/lib/src/dids/did_core/did_verification_method.dart +++ b/packages/web5/lib/src/dids/did_core/did_verification_method.dart @@ -1,5 +1,6 @@ import 'package:web5/src/crypto.dart'; import 'package:web5/src/dids/did_core/did_resource.dart'; +import 'package:web5/src/dids/did_core/did_verification_relationship.dart'; /// A DID document can express verification methods, such as cryptographic /// public keys, which can be used to authenticate or authorize interactions @@ -52,3 +53,17 @@ class DidVerificationMethod implements DidResource { ); } } + +class DidCreateVerificationMethod { + DidCreateVerificationMethod({ + required this.controller, + this.id, + required this.purposes, + required this.type, + }); + + final String controller; + final String? id; + final List purposes; + final String type; +} diff --git a/packages/web5/lib/src/dids/did_dht/did_dht.dart b/packages/web5/lib/src/dids/did_dht/did_dht.dart index 292c67d..80df594 100644 --- a/packages/web5/lib/src/dids/did_dht/did_dht.dart +++ b/packages/web5/lib/src/dids/did_dht/did_dht.dart @@ -1,11 +1,15 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:web5/src/crypto.dart'; +import 'package:web5/src/dids/bearer_did.dart'; import 'package:web5/src/dids/did.dart'; import 'package:web5/src/dids/did_core.dart'; import 'package:web5/src/dids/did_dht/dns_packet.dart'; +import 'package:web5/src/dids/did_dht/registered_did_type.dart'; import 'package:web5/src/dids/did_method_resolver.dart'; import 'package:web5/src/encoders.dart'; +import 'package:web5/src/encoders/zbase.dart'; final Set txtEntryNames = {'vm', 'auth', 'asm', 'agm', 'inv', 'del'}; @@ -14,6 +18,124 @@ class DidDht { static final resolver = DidMethodResolver(name: methodName, resolve: resolve); + static Future create({ + required AlgorithmId algorithm, + required KeyManager keyManager, + List? alsoKnownAs, + List? controllers, + String? gatewayUri, + bool? publish, + List? services, + List? types, + List? verificationMethods, + }) async { + // Generate random key material for the Identity Key. + final Jwk identityKeyUri = await Crypto.generatePrivateKey(algorithm); + final Jwk identityKey = await Crypto.computePublicKey(identityKeyUri); + + final String didUri = identityKeyToIdentifier(identityKey: identityKey); + final DidDocument doc = DidDocument( + id: didUri, + alsoKnownAs: alsoKnownAs, + controller: controllers ?? didUri, + ); + + // If the given verification methods do not contain an Identity Key, add one. + final List methodsToAdd = + verificationMethods ?? []; + + final Iterable identityMethods = + methodsToAdd.where( + (vm) => vm.id?.split('#').last == '0', + ); + + if (identityMethods.isEmpty) { + methodsToAdd.add( + DidCreateVerificationMethod( + id: '0', + type: 'JsonWebKey', + controller: didUri, + purposes: [ + VerificationPurpose.authentication, + VerificationPurpose.assertionMethod, + VerificationPurpose.capabilityDelegation, + VerificationPurpose.capabilityInvocation, + ], + ), + ); + } + + // Generate random key material for the Identity Key and any additional verification methods. + // Add verification methods to the DID document. + for (final DidCreateVerificationMethod vm in methodsToAdd) { + // Generate a random key for the verification method, or if its the Identity Key's + // verification method (`id` is 0) use the key previously generated. + late Jwk keyUri; + + if (vm.id?.split('#').last == '0') { + keyUri = identityKeyUri; + } else { + keyUri = await Crypto.generatePrivateKey(algorithm); + } + + final Jwk publicKey = await Crypto.computePublicKey(keyUri); + + // Use the given ID, the key's ID, or the key's thumbprint as the verification method ID. + String methodId = vm.id ?? publicKey.kid ?? publicKey.computeThumbprint(); + methodId = '$didUri#${methodId.split('#').last}'; + + doc.addVerificationMethod( + DidVerificationMethod( + id: methodId, + type: vm.type, + controller: vm.controller, + publicKeyJwk: publicKey, + ), + ); + + for (final VerificationPurpose purpose in vm.purposes) { + doc.addVerificationPurpose(purpose, methodId); + } + } + + for (final DidService service in (services ?? [])) { + doc.addService(service); + } + + final BearerDid did = BearerDid( + uri: didUri, + keyManager: keyManager, + document: doc, + metadata: DidDocumentMetadata( + types: types, + ), + ); + + if (publish ?? true) { + DidDht.publish(did: did); + } + + return did; + } + + static String identityKeyToIdentifier({ + required Jwk identityKey, + }) { + // Convert the key from JWK format to a byte array. + final Uint8List publicKeyBytes = Crypto.publicKeyToBytes(identityKey); + + final String identifier = ZBase32.encode(publicKeyBytes); + return 'did:${DidDht.methodName}:$identifier'; + } + + static Future publish({ + required BearerDid did, + String? gatewayUri, + }) async { + // TODO: Finish publish method + // final DnsPacket dnsPacket = DnsPacket.fromDid(did); + } + static Future resolve( Did did, { String relayUrl = 'https://diddht.tbddev.org', diff --git a/packages/web5/lib/src/dids/did_dht/dns_packet/answer.dart b/packages/web5/lib/src/dids/did_dht/dns_packet/answer.dart index 9b33ecc..63e0481 100644 --- a/packages/web5/lib/src/dids/did_dht/dns_packet/answer.dart +++ b/packages/web5/lib/src/dids/did_dht/dns_packet/answer.dart @@ -8,6 +8,22 @@ import 'package:web5/src/dids/did_dht/dns_packet/consts.dart'; import 'package:web5/src/dids/did_dht/dns_packet/opt_data.dart'; import 'package:web5/src/dids/did_dht/dns_packet/txt_data.dart'; +class BaseAnswer { + BaseAnswer({ + required this.type, + required this.name, + this.ttl, + this.classType, + required this.data, + }); + + DnsType type; + String name; + int? ttl; + String? classType; + D data; +} + /// Represents an answer section in a DNS packet. class DnsAnswer { /// The domain name to which this resource record pertains. diff --git a/packages/web5/lib/src/dids/did_dht/dns_packet/header.dart b/packages/web5/lib/src/dids/did_dht/dns_packet/header.dart index 04356b9..18e19cb 100644 --- a/packages/web5/lib/src/dids/did_dht/dns_packet/header.dart +++ b/packages/web5/lib/src/dids/did_dht/dns_packet/header.dart @@ -4,20 +4,49 @@ import 'package:web5/src/dids/did_dht/dns_packet/opcode.dart'; import 'package:web5/src/dids/did_dht/dns_packet/rcode.dart'; class DnsHeader { + /// Identifier assigned by the program that generates the query. int id; + + /// Whether this message is a query (0), or a response (1) bool qr; + + /// Specifies kind of query in this message. DnsOpCode opcode; - bool aa; + + /// Specifies that the responding name server is an authority for the domain name in question section. + bool? aa; + + /// Specifies whether the message was truncated. bool tc; + + /// Directs the name server to pursue the query recursively bool rd; - bool ra; + + /// Set or cleared in a response, and denotes whether recursive query support is available in the name server + bool? ra; + + /// Reserved for future use, always set to 0. bool z; - bool ad; - bool cd; - DnsRCode rcode; + + /// TODO: Find documentation for this field + bool? ad; + + /// TODO: Find documentation for this field + bool? cd; + + /// Response code + DnsRCode? rcode; + + /// Number of entries in the question section. int qdcount; + + /// Number of resource records in the answer section. int ancount; + + /// Number of name server resource records in the authority records section. int nscount; + + /// Number of resource records in the additional records section. int arcount; get numQuestions => qdcount; @@ -31,14 +60,14 @@ class DnsHeader { required this.id, required this.qr, required this.opcode, - required this.aa, + this.aa, required this.tc, required this.rd, - required this.ra, - required this.z, - required this.ad, - required this.cd, - required this.rcode, + this.ra, + this.z = false, + this.ad, + this.cd, + this.rcode, required this.qdcount, required this.ancount, required this.nscount, diff --git a/packages/web5/lib/src/dids/did_dht/dns_packet/packet.dart b/packages/web5/lib/src/dids/did_dht/dns_packet/packet.dart index a3275e4..be0c846 100644 --- a/packages/web5/lib/src/dids/did_dht/dns_packet/packet.dart +++ b/packages/web5/lib/src/dids/did_dht/dns_packet/packet.dart @@ -1,8 +1,21 @@ +// ignore_for_file: constant_identifier_names + +import 'dart:convert'; import 'dart:typed_data'; +import 'package:web5/src/crypto.dart'; +import 'package:web5/src/dids/bearer_did.dart'; +import 'package:web5/src/dids/did_core/did_service.dart'; +import 'package:web5/src/dids/did_core/did_verification_method.dart'; import 'package:web5/src/dids/did_dht/dns_packet/answer.dart'; import 'package:web5/src/dids/did_dht/dns_packet/header.dart'; +import 'package:web5/src/dids/did_dht/dns_packet/opcode.dart'; import 'package:web5/src/dids/did_dht/dns_packet/question.dart'; +import 'package:web5/src/dids/did_dht/dns_packet/type.dart'; +import 'package:web5/src/dids/did_dht/registered_did_type.dart'; + +const int DNS_RECORD_TTL = 7200; +const int DID_DHT_SPECIFICATION_VERSION = 0; class DnsPacket { DnsHeader header; @@ -55,4 +68,212 @@ class DnsPacket { return DnsPacket(header: header, questions: questions, answers: answers); } + + factory DnsPacket.fromDid(BearerDid did) { + final List> dnsAnswerRecords = []; + final Map idLookup = {}; + final List serviceIds = []; + final List verificationMethodIds = []; + + // Add DNS TXT records if the DID document contains an `alsoKnownAs` property. + if (did.document.alsoKnownAs != null) { + dnsAnswerRecords.add( + BaseAnswer( + type: DnsType.TXT, + name: '_aka.did.', + ttl: DNS_RECORD_TTL, + data: did.document.alsoKnownAs!.join(','), + ), + ); + } + + // Add DNS TXT records if the DID document contains a `controller` property. + if (did.document.controller != null) { + String controller; + + if (did.document.controller is List) { + controller = (did.document.controller as List).join(','); + } else { + controller = did.document.controller as String; + } + + dnsAnswerRecords.add( + BaseAnswer( + type: DnsType.TXT, + name: '_cnt.did.', + ttl: DNS_RECORD_TTL, + data: controller, + ), + ); + } + + // Add DNS TXT records for each verification method. + if (did.document.verificationMethod != null) { + for (var i = 0; i < did.document.verificationMethod!.length; i++) { + final DidVerificationMethod vm = did.document.verificationMethod![i]; + + final String dnsRecordId = 'k$i'; + verificationMethodIds.add(dnsRecordId); + + // Remove fragment prefix, if any. + final String methodId = vm.id.split('#').last; + idLookup[methodId] = dnsRecordId; + + final Jwk? publicKey = vm.publicKeyJwk; + + // Use the public key's `crv` property to get the DID DHT key type. + int keyType; + + // Convert the public key from JWK format to a byte array. + Uint8List publicKeyBytes; + + switch (publicKey?.crv) { + case 'Ed25519': + keyType = 0; + publicKeyBytes = Ed25519.publicKeyToBytes(publicKey: publicKey!); + break; + case 'secp256k1': + keyType = 1; + publicKeyBytes = Secp256k1.publicKeyToBytes(publicKey: publicKey!); + break; + default: + throw 'Verification method ${vm.id} contains an unsupported key type: ${publicKey?.crv}'; + } + + // Convert the public key from a byte array to Base64URL format. + final String publicKeyBase64Url = utf8.decode(publicKeyBytes); + // Define the data for the DNS TXT record. + final List txtData = [ + 'id=$methodId', + 't=$keyType', + 'k=$publicKeyBase64Url', + ]; + + // Add the controller property, if set to a value other than the Identity Key (DID Subject). + if (vm.controller != did.document.id) txtData.add('c=${vm.controller}'); + + // Add a TXT record for the verification method. + dnsAnswerRecords.add( + BaseAnswer( + type: DnsType.TXT, + name: '_$dnsRecordId._did.', + ttl: DNS_RECORD_TTL, + data: txtData.join(';'), + ), + ); + } + } + + // Add DNS TXT records for each service. + if (did.document.service != null) { + for (var i = 0; i < did.document.service!.length; i++) { + final DidService service = did.document.service![i]; + final String dnsRecordId = 's$i'; + + serviceIds.add(dnsRecordId); + final String id = service.id.split('#').last; + + // Define the data for the DNS TXT record. + final List txtData = [ + 'id=$id', + 'se=${service.serviceEndpoint}', + 't=${service.type}', + ]; + + // Add a TXT record for the service. + dnsAnswerRecords.add( + BaseAnswer( + type: DnsType.TXT, + name: '_$dnsRecordId._did.', + data: txtData.join(';'), + ), + ); + } + } + + // Initialize the root DNS TXT record with the DID DHT specification version. + final List rootRecord = ['v=$DID_DHT_SPECIFICATION_VERSION']; + + // Add verification methods to the root record. + if (verificationMethodIds.isNotEmpty) { + rootRecord.add('vm=${verificationMethodIds.join(',')}'); + } + + // Collect the verification method IDs for the given relationship and add to the rootRecord. + if (did.document.assertionMethod?.isNotEmpty ?? false) { + rootRecord.add( + 'assertionMethod=${did.document.assertionMethod!.join(',')}', + ); + } + if (did.document.authentication?.isNotEmpty ?? false) { + rootRecord.add( + 'authentication=${did.document.authentication!.join(',')}', + ); + } + if (did.document.capabilityDelegation?.isNotEmpty ?? false) { + rootRecord.add( + 'capabilityDelegation=${did.document.capabilityDelegation!.join(',')}', + ); + } + if (did.document.capabilityInvocation?.isNotEmpty ?? false) { + rootRecord.add( + 'capabilityInvocation=${did.document.capabilityInvocation!.join(',')}', + ); + } + if (did.document.keyAgreement?.isNotEmpty ?? false) { + rootRecord.add( + 'keyAgreement=${did.document.keyAgreement!.join(',')}', + ); + } + + if (serviceIds.isNotEmpty) { + rootRecord.add('svc=${serviceIds.join(',')}'); + } + + // If defined, add a DNS TXT record for each registered DID type. + if (did.metadata.types?.isNotEmpty ?? false) { + dnsAnswerRecords.add( + BaseAnswer( + type: DnsType.TXT, + name: '_typ._did.', + ttl: DNS_RECORD_TTL, + data: 'id=${did.metadata.types!.map((e) => e.value).join(',')}', + ), + ); + } + + // Add a DNS TXT record for the root record. + dnsAnswerRecords.add( + BaseAnswer( + type: DnsType.TXT, + name: '_did.', + ttl: DNS_RECORD_TTL, + data: rootRecord.join(';'), + ), + ); + + // Per the DID DHT specification, the method-specific identifier must be appended as the + // Origin of all records. + final String identifier = did.document.id.split(':').last; + for (final BaseAnswer record in dnsAnswerRecords) { + record.name += identifier; + } + + // Create a DNS response packet with the authoritative answer flag set. + return DnsPacket( + header: DnsHeader( + id: 0, + qr: false, + opcode: DnsOpCode.QUERY, + tc: false, + rd: true, + qdcount: 0, + ancount: dnsAnswerRecords.length, + nscount: 0, + arcount: 0, + ), + questions: [], + answers: [], + ); + } } diff --git a/packages/web5/lib/src/dids/did_dht/registered_did_type.dart b/packages/web5/lib/src/dids/did_dht/registered_did_type.dart new file mode 100644 index 0000000..769c6dc --- /dev/null +++ b/packages/web5/lib/src/dids/did_dht/registered_did_type.dart @@ -0,0 +1,51 @@ +enum DidDhtRegisteredDidType { + /// Type 0 is reserved for DIDs that do not wish to associate themselves + /// with a specific type but wish to make themselves discoverable. + discoverable, + + /// Organization: https://schema.org/Organization + organization, + + /// Government Organization: https://schema.org/GovernmentOrganization + government, + + /// Corporation: https://schema.org/Corporation + corporation, + + /// Local Business: https://schema.org/LocalBusiness + localBusiness, + + /// Software Package: https://schema.org/SoftwareSourceCode + softwarePackage, + + /// Web App: https://schema.org/WebApplication + webApp, + + /// Financial Institution: https://schema.org/FinancialService + financialInstitution, +} + +extension IntegerValue on DidDhtRegisteredDidType { + int get value { + switch (this) { + case DidDhtRegisteredDidType.discoverable: + return 0; + case DidDhtRegisteredDidType.organization: + return 1; + case DidDhtRegisteredDidType.government: + return 2; + case DidDhtRegisteredDidType.corporation: + return 3; + case DidDhtRegisteredDidType.localBusiness: + return 4; + case DidDhtRegisteredDidType.softwarePackage: + return 5; + case DidDhtRegisteredDidType.webApp: + return 6; + case DidDhtRegisteredDidType.financialInstitution: + return 7; + default: + throw 'Unable to get value for DidDhtRegisteredDidType $this'; + } + } +} diff --git a/packages/web5/lib/src/encoders/zbase.dart b/packages/web5/lib/src/encoders/zbase.dart new file mode 100644 index 0000000..f0d7a1b --- /dev/null +++ b/packages/web5/lib/src/encoders/zbase.dart @@ -0,0 +1,53 @@ +class ZBase32 { + static const String _base32Alphabet = 'ybndrfg8ejkmcpqxot1uwisza345h769'; + + static String encode(List data) { + final StringBuffer result = StringBuffer(); + int bits = 0; + int bitCount = 0; + + for (int byte in data) { + bits = (bits << 8) | byte; + bitCount += 8; + + while (bitCount >= 5) { + bitCount -= 5; + final int index = bits >> bitCount; + bits &= ((1 << bitCount) - 1); + result.write(_base32Alphabet[index]); + } + } + + if (bitCount > 0) { + bits <<= (5 - bitCount); + result.write(_base32Alphabet[bits]); + } + + return result.toString(); + } + + static List decode(String encoded) { + final List result = []; + + int bits = 0; + int bitCount = 0; + + for (int i = 0; i < encoded.length; i++) { + final int value = _base32Alphabet.indexOf(encoded[i]); + if (value == -1) { + throw ArgumentError('Invalid character in encoded string'); + } + + bits = (bits << 5) | value; + bitCount += 5; + + if (bitCount >= 8) { + bitCount -= 8; + result.add(bits >> bitCount); + bits &= ((1 << bitCount) - 1); + } + } + + return result; + } +} diff --git a/packages/web5/pubspec.yaml b/packages/web5/pubspec.yaml index fe246f1..9c77071 100644 --- a/packages/web5/pubspec.yaml +++ b/packages/web5/pubspec.yaml @@ -7,7 +7,6 @@ environment: sdk: ^3.1.4 dependencies: - base32: ^2.1.3 collection: ^1.18.0 convert: ^3.1.1 cryptography: ^2.7.0 From da62ad50b2b25543b4059c8932ecd49832378d60 Mon Sep 17 00:00:00 2001 From: Stone Pack Date: Mon, 1 Apr 2024 08:19:20 -0700 Subject: [PATCH 2/4] updated create function with algorithm handling --- .../lib/src/crypto/in_memory_key_manager.dart | 4 ++-- packages/web5/lib/src/crypto/key_manager.dart | 2 +- .../dids/did_core/did_verification_method.dart | 2 ++ packages/web5/lib/src/dids/did_dht/did_dht.dart | 15 +++++++++------ 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/web5/lib/src/crypto/in_memory_key_manager.dart b/packages/web5/lib/src/crypto/in_memory_key_manager.dart index ad4ff07..5aadb72 100644 --- a/packages/web5/lib/src/crypto/in_memory_key_manager.dart +++ b/packages/web5/lib/src/crypto/in_memory_key_manager.dart @@ -24,13 +24,13 @@ class InMemoryKeyManager implements KeyManager, KeyImporter, KeyExporter { final Map _keyStore = {}; @override - Future generatePrivateKey(AlgorithmId algId) async { + Future generatePrivateKey(AlgorithmId algId) async { final privateKeyJwk = await Crypto.generatePrivateKey(algId); final alias = privateKeyJwk.computeThumbprint(); _keyStore[alias] = privateKeyJwk; - return alias; + return privateKeyJwk; } @override diff --git a/packages/web5/lib/src/crypto/key_manager.dart b/packages/web5/lib/src/crypto/key_manager.dart index b90c52d..dbe4ce1 100644 --- a/packages/web5/lib/src/crypto/key_manager.dart +++ b/packages/web5/lib/src/crypto/key_manager.dart @@ -24,7 +24,7 @@ abstract interface class KeyManager { /// Generates and securely stores a private key based on the provided /// algorithm. Returns a unique alias that can be utilized to reference the /// generated key for future operations. - Future generatePrivateKey(AlgorithmId algId); + Future generatePrivateKey(AlgorithmId algId); /// Retrieves the public key associated with a previously stored private key, /// identified by the provided alias. diff --git a/packages/web5/lib/src/dids/did_core/did_verification_method.dart b/packages/web5/lib/src/dids/did_core/did_verification_method.dart index 5558269..f747d1f 100644 --- a/packages/web5/lib/src/dids/did_core/did_verification_method.dart +++ b/packages/web5/lib/src/dids/did_core/did_verification_method.dart @@ -56,12 +56,14 @@ class DidVerificationMethod implements DidResource { class DidCreateVerificationMethod { DidCreateVerificationMethod({ + required this.algorithm, required this.controller, this.id, required this.purposes, required this.type, }); + final AlgorithmId algorithm; final String controller; final String? id; final List purposes; diff --git a/packages/web5/lib/src/dids/did_dht/did_dht.dart b/packages/web5/lib/src/dids/did_dht/did_dht.dart index 80df594..787d8f1 100644 --- a/packages/web5/lib/src/dids/did_dht/did_dht.dart +++ b/packages/web5/lib/src/dids/did_dht/did_dht.dart @@ -19,8 +19,7 @@ class DidDht { static final resolver = DidMethodResolver(name: methodName, resolve: resolve); static Future create({ - required AlgorithmId algorithm, - required KeyManager keyManager, + KeyManager? keyManager, List? alsoKnownAs, List? controllers, String? gatewayUri, @@ -29,9 +28,12 @@ class DidDht { List? types, List? verificationMethods, }) async { + final AlgorithmId idAlgorithm = AlgorithmId.ed25519; + keyManager ??= InMemoryKeyManager(); + // Generate random key material for the Identity Key. - final Jwk identityKeyUri = await Crypto.generatePrivateKey(algorithm); - final Jwk identityKey = await Crypto.computePublicKey(identityKeyUri); + final Jwk idKeyUri = await keyManager.generatePrivateKey(idAlgorithm); + final Jwk identityKey = await Crypto.computePublicKey(idKeyUri); final String didUri = identityKeyToIdentifier(identityKey: identityKey); final DidDocument doc = DidDocument( @@ -52,6 +54,7 @@ class DidDht { if (identityMethods.isEmpty) { methodsToAdd.add( DidCreateVerificationMethod( + algorithm: AlgorithmId.ed25519, id: '0', type: 'JsonWebKey', controller: didUri, @@ -73,9 +76,9 @@ class DidDht { late Jwk keyUri; if (vm.id?.split('#').last == '0') { - keyUri = identityKeyUri; + keyUri = idKeyUri; } else { - keyUri = await Crypto.generatePrivateKey(algorithm); + keyUri = await keyManager.generatePrivateKey(vm.algorithm); } final Jwk publicKey = await Crypto.computePublicKey(keyUri); From d699e8dc39e03312c91827aec961cb44a832643f Mon Sep 17 00:00:00 2001 From: Stone Pack Date: Tue, 2 Apr 2024 12:07:44 -0700 Subject: [PATCH 3/4] updated key generation and storage to match web did --- .../lib/src/crypto/in_memory_key_manager.dart | 6 ++--- packages/web5/lib/src/crypto/key_manager.dart | 22 ++++++++----------- packages/web5/lib/src/dids/bearer_did.dart | 7 +----- .../web5/lib/src/dids/did_dht/did_dht.dart | 7 ++++-- 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/packages/web5/lib/src/crypto/in_memory_key_manager.dart b/packages/web5/lib/src/crypto/in_memory_key_manager.dart index 5aadb72..36fd921 100644 --- a/packages/web5/lib/src/crypto/in_memory_key_manager.dart +++ b/packages/web5/lib/src/crypto/in_memory_key_manager.dart @@ -20,17 +20,17 @@ import 'package:web5/src/crypto/crypto.dart'; /// final signatureBytes = await keyManager.sign(keyAlias, Uint8List.fromList([20, 32])); /// ``` /// -class InMemoryKeyManager implements KeyManager, KeyImporter, KeyExporter { +class InMemoryKeyManager implements KeyManager { final Map _keyStore = {}; @override - Future generatePrivateKey(AlgorithmId algId) async { + Future generatePrivateKey(AlgorithmId algId) async { final privateKeyJwk = await Crypto.generatePrivateKey(algId); final alias = privateKeyJwk.computeThumbprint(); _keyStore[alias] = privateKeyJwk; - return privateKeyJwk; + return alias; } @override diff --git a/packages/web5/lib/src/crypto/key_manager.dart b/packages/web5/lib/src/crypto/key_manager.dart index dbe4ce1..268aebe 100644 --- a/packages/web5/lib/src/crypto/key_manager.dart +++ b/packages/web5/lib/src/crypto/key_manager.dart @@ -3,33 +3,29 @@ import 'dart:typed_data'; import 'package:web5/src/crypto/algorithm_id.dart'; import 'package:web5/src/crypto/jwk.dart'; -abstract interface class KeyImporter { - /// Imports a private key. Returns - /// a unique id that can be utilized to reference the imported key for - /// future operations. - Future import(Jwk jwk); -} - -abstract interface class KeyExporter { - /// Exports the private key with the provided id. - Future export(String keyId); -} - /// A key management interface that provides functionality for generating, /// storing, and utilizing private keys and their associated public keys. /// Implementations of this interface should handle the secure generation and /// storage of keys, providing mechanisms for utilizing them in cryptographic /// operations like signing. abstract interface class KeyManager { + /// Exports the private key with the provided id. + Future export(String keyId); + /// Generates and securely stores a private key based on the provided /// algorithm. Returns a unique alias that can be utilized to reference the /// generated key for future operations. - Future generatePrivateKey(AlgorithmId algId); + Future generatePrivateKey(AlgorithmId algId); /// Retrieves the public key associated with a previously stored private key, /// identified by the provided alias. Future getPublicKey(String keyId); + /// Imports a private key. Returns + /// a unique id that can be utilized to reference the imported key for + /// future operations. + Future import(Jwk jwk); + /// Signs the provided payload using the private key identified by the /// provided alias. Future sign(String keyId, Uint8List payload); diff --git a/packages/web5/lib/src/dids/bearer_did.dart b/packages/web5/lib/src/dids/bearer_did.dart index c871880..69326a3 100644 --- a/packages/web5/lib/src/dids/bearer_did.dart +++ b/packages/web5/lib/src/dids/bearer_did.dart @@ -35,15 +35,10 @@ class BearerDid { document: document, ); - if (keyManager is! KeyExporter) { - return Future.value(portableDid); - } - - final keyExporter = keyManager as KeyExporter; for (final vm in document.verificationMethod!) { final publicKeyJwk = vm.publicKeyJwk!; final keyId = publicKeyJwk.computeThumbprint(); - final jwk = await keyExporter.export(keyId); + final jwk = await keyManager.export(keyId); portableDid.privateKeys.add(jwk); } diff --git a/packages/web5/lib/src/dids/did_dht/did_dht.dart b/packages/web5/lib/src/dids/did_dht/did_dht.dart index 787d8f1..4542f12 100644 --- a/packages/web5/lib/src/dids/did_dht/did_dht.dart +++ b/packages/web5/lib/src/dids/did_dht/did_dht.dart @@ -32,9 +32,11 @@ class DidDht { keyManager ??= InMemoryKeyManager(); // Generate random key material for the Identity Key. - final Jwk idKeyUri = await keyManager.generatePrivateKey(idAlgorithm); + final Jwk idKeyUri = await Crypto.generatePrivateKey(idAlgorithm); final Jwk identityKey = await Crypto.computePublicKey(idKeyUri); + await keyManager.import(idKeyUri); + final String didUri = identityKeyToIdentifier(identityKey: identityKey); final DidDocument doc = DidDocument( id: didUri, @@ -78,7 +80,8 @@ class DidDht { if (vm.id?.split('#').last == '0') { keyUri = idKeyUri; } else { - keyUri = await keyManager.generatePrivateKey(vm.algorithm); + keyUri = await Crypto.generatePrivateKey(vm.algorithm); + keyManager.import(keyUri); } final Jwk publicKey = await Crypto.computePublicKey(keyUri); From c1d4e9e438de614070111da406937a59e286078a Mon Sep 17 00:00:00 2001 From: Stone Pack Date: Wed, 3 Apr 2024 07:17:45 -0700 Subject: [PATCH 4/4] revert key manager changes --- .../lib/src/crypto/in_memory_key_manager.dart | 2 +- packages/web5/lib/src/crypto/key_manager.dart | 20 +++++++++++-------- packages/web5/lib/src/dids/bearer_did.dart | 7 ++++++- .../web5/lib/src/dids/did_dht/did_dht.dart | 16 ++++++--------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/packages/web5/lib/src/crypto/in_memory_key_manager.dart b/packages/web5/lib/src/crypto/in_memory_key_manager.dart index 36fd921..ad4ff07 100644 --- a/packages/web5/lib/src/crypto/in_memory_key_manager.dart +++ b/packages/web5/lib/src/crypto/in_memory_key_manager.dart @@ -20,7 +20,7 @@ import 'package:web5/src/crypto/crypto.dart'; /// final signatureBytes = await keyManager.sign(keyAlias, Uint8List.fromList([20, 32])); /// ``` /// -class InMemoryKeyManager implements KeyManager { +class InMemoryKeyManager implements KeyManager, KeyImporter, KeyExporter { final Map _keyStore = {}; @override diff --git a/packages/web5/lib/src/crypto/key_manager.dart b/packages/web5/lib/src/crypto/key_manager.dart index 268aebe..b90c52d 100644 --- a/packages/web5/lib/src/crypto/key_manager.dart +++ b/packages/web5/lib/src/crypto/key_manager.dart @@ -3,15 +3,24 @@ import 'dart:typed_data'; import 'package:web5/src/crypto/algorithm_id.dart'; import 'package:web5/src/crypto/jwk.dart'; +abstract interface class KeyImporter { + /// Imports a private key. Returns + /// a unique id that can be utilized to reference the imported key for + /// future operations. + Future import(Jwk jwk); +} + +abstract interface class KeyExporter { + /// Exports the private key with the provided id. + Future export(String keyId); +} + /// A key management interface that provides functionality for generating, /// storing, and utilizing private keys and their associated public keys. /// Implementations of this interface should handle the secure generation and /// storage of keys, providing mechanisms for utilizing them in cryptographic /// operations like signing. abstract interface class KeyManager { - /// Exports the private key with the provided id. - Future export(String keyId); - /// Generates and securely stores a private key based on the provided /// algorithm. Returns a unique alias that can be utilized to reference the /// generated key for future operations. @@ -21,11 +30,6 @@ abstract interface class KeyManager { /// identified by the provided alias. Future getPublicKey(String keyId); - /// Imports a private key. Returns - /// a unique id that can be utilized to reference the imported key for - /// future operations. - Future import(Jwk jwk); - /// Signs the provided payload using the private key identified by the /// provided alias. Future sign(String keyId, Uint8List payload); diff --git a/packages/web5/lib/src/dids/bearer_did.dart b/packages/web5/lib/src/dids/bearer_did.dart index 69326a3..c871880 100644 --- a/packages/web5/lib/src/dids/bearer_did.dart +++ b/packages/web5/lib/src/dids/bearer_did.dart @@ -35,10 +35,15 @@ class BearerDid { document: document, ); + if (keyManager is! KeyExporter) { + return Future.value(portableDid); + } + + final keyExporter = keyManager as KeyExporter; for (final vm in document.verificationMethod!) { final publicKeyJwk = vm.publicKeyJwk!; final keyId = publicKeyJwk.computeThumbprint(); - final jwk = await keyManager.export(keyId); + final jwk = await keyExporter.export(keyId); portableDid.privateKeys.add(jwk); } diff --git a/packages/web5/lib/src/dids/did_dht/did_dht.dart b/packages/web5/lib/src/dids/did_dht/did_dht.dart index 4542f12..ed9a70b 100644 --- a/packages/web5/lib/src/dids/did_dht/did_dht.dart +++ b/packages/web5/lib/src/dids/did_dht/did_dht.dart @@ -32,10 +32,8 @@ class DidDht { keyManager ??= InMemoryKeyManager(); // Generate random key material for the Identity Key. - final Jwk idKeyUri = await Crypto.generatePrivateKey(idAlgorithm); - final Jwk identityKey = await Crypto.computePublicKey(idKeyUri); - - await keyManager.import(idKeyUri); + final String keyAlias = await keyManager.generatePrivateKey(idAlgorithm); + final Jwk identityKey = await keyManager.getPublicKey(keyAlias); final String didUri = identityKeyToIdentifier(identityKey: identityKey); final DidDocument doc = DidDocument( @@ -75,17 +73,15 @@ class DidDht { for (final DidCreateVerificationMethod vm in methodsToAdd) { // Generate a random key for the verification method, or if its the Identity Key's // verification method (`id` is 0) use the key previously generated. - late Jwk keyUri; + late Jwk publicKey; if (vm.id?.split('#').last == '0') { - keyUri = idKeyUri; + publicKey = identityKey; } else { - keyUri = await Crypto.generatePrivateKey(vm.algorithm); - keyManager.import(keyUri); + String alias = await keyManager.generatePrivateKey(vm.algorithm); + publicKey = await keyManager.getPublicKey(alias); } - final Jwk publicKey = await Crypto.computePublicKey(keyUri); - // Use the given ID, the key's ID, or the key's thumbprint as the verification method ID. String methodId = vm.id ?? publicKey.kid ?? publicKey.computeThumbprint(); methodId = '$didUri#${methodId.split('#').last}';