Skip to content

Commit

Permalink
feature/dht-create (#55)
Browse files Browse the repository at this point in the history
* moved from old fork

* updated create function with algorithm handling

* updated key generation and storage to match web did

* revert key manager changes
  • Loading branch information
fingersonfire authored Apr 10, 2024
1 parent c2d7046 commit fe87e51
Show file tree
Hide file tree
Showing 11 changed files with 547 additions and 12 deletions.
11 changes: 11 additions & 0 deletions packages/web5/lib/src/crypto/crypto.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Uint8List> sign(Jwk privateKeyJwk, Uint8List payload) {
switch (privateKeyJwk.kty) {
case Ecdsa.kty:
Expand Down
12 changes: 12 additions & 0 deletions packages/web5/lib/src/crypto/ed25519.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:cryptography/cryptography.dart' as crypto;
Expand Down Expand Up @@ -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<Uint8List> sign(Jwk privateKey, Uint8List payload) async {
final privateKeyBytes = Base64Url.decode(privateKey.d!);

Expand Down
12 changes: 12 additions & 0 deletions packages/web5/lib/src/crypto/secp256k1.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';

Expand Down Expand Up @@ -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<Uint8List> sign(Jwk privateKeyJwk, Uint8List payload) {
final sha256 = SHA256Digest();

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -51,6 +53,8 @@ class DidDocumentMetadata {
/// the scope of the containing DID document.
final String? canonicalId;

final List<DidDhtRegisteredDidType>? types;

const DidDocumentMetadata({
this.created,
this.updated,
Expand All @@ -60,6 +64,7 @@ class DidDocumentMetadata {
this.nextVersionId,
this.equivalentId,
this.canonicalId,
this.types,
});

Map<String, dynamic> toJson() {
Expand All @@ -72,6 +77,7 @@ class DidDocumentMetadata {
'nextVersionId': nextVersionId,
'equivalentId': equivalentId,
'canonicalId': canonicalId,
'types': types?.map((e) => e.value) ?? [],
};

json.removeWhere((key, value) => value == null);
Expand Down Expand Up @@ -105,6 +111,7 @@ class DidDocumentMetadata {
nextVersionId,
equivalentId,
canonicalId,
types,
);
}
}
124 changes: 124 additions & 0 deletions packages/web5/lib/src/dids/did_dht/did_dht.dart
Original file line number Diff line number Diff line change
@@ -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<String> txtEntryNames = {'vm', 'auth', 'asm', 'agm', 'inv', 'del'};

Expand All @@ -14,6 +18,126 @@ class DidDht {

static final resolver = DidMethodResolver(name: methodName, resolve: resolve);

static Future<BearerDid> create({
KeyManager? keyManager,
List<String>? alsoKnownAs,
List<String>? controllers,
String? gatewayUri,
bool? publish,
List<DidService>? services,
List<DidDhtRegisteredDidType>? types,
List<DidCreateVerificationMethod>? verificationMethods,
}) async {
final AlgorithmId idAlgorithm = AlgorithmId.ed25519;
keyManager ??= InMemoryKeyManager();

// Generate random key material for the Identity Key.
final String keyAlias = await keyManager.generatePrivateKey(idAlgorithm);
final Jwk identityKey = await keyManager.getPublicKey(keyAlias);

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<DidCreateVerificationMethod> methodsToAdd =
verificationMethods ?? [];

final Iterable<DidCreateVerificationMethod> identityMethods =
methodsToAdd.where(
(vm) => vm.id?.split('#').last == '0',
);

if (identityMethods.isEmpty) {
methodsToAdd.add(
DidCreateVerificationMethod(
algorithm: AlgorithmId.ed25519,
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 publicKey;

if (vm.id?.split('#').last == '0') {
publicKey = identityKey;
} else {
String alias = await keyManager.generatePrivateKey(vm.algorithm);
publicKey = await keyManager.getPublicKey(alias);
}

// 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<void> publish({
required BearerDid did,
String? gatewayUri,
}) async {
// TODO: Finish publish method
// final DnsPacket dnsPacket = DnsPacket.fromDid(did);
}

static Future<DidResolutionResult> resolve(
Did did, {
String relayUrl = 'https://diddht.tbddev.org',
Expand Down
16 changes: 16 additions & 0 deletions packages/web5/lib/src/dids/did_dht/dns_packet/answer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<D> {
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.
Expand Down
51 changes: 40 additions & 11 deletions packages/web5/lib/src/dids/did_dht/dns_packet/header.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down
Loading

0 comments on commit fe87e51

Please sign in to comment.