Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/dht-create #55

Merged
merged 5 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 2 additions & 2 deletions packages/web5/lib/src/crypto/in_memory_key_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ class InMemoryKeyManager implements KeyManager, KeyImporter, KeyExporter {
final Map<String, Jwk> _keyStore = {};

@override
Future<String> generatePrivateKey(AlgorithmId algId) async {
Future<Jwk> generatePrivateKey(AlgorithmId algId) async {
final privateKeyJwk = await Crypto.generatePrivateKey(algId);
final alias = privateKeyJwk.computeThumbprint();

_keyStore[alias] = privateKeyJwk;

return alias;
return privateKeyJwk;
}

@override
Expand Down
2 changes: 1 addition & 1 deletion packages/web5/lib/src/crypto/key_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> generatePrivateKey(AlgorithmId algId);
Future<Jwk> generatePrivateKey(AlgorithmId algId);
fingersonfire marked this conversation as resolved.
Show resolved Hide resolved

/// Retrieves the public key associated with a previously stored private key,
/// identified by the provided alias.
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
2 changes: 2 additions & 0 deletions packages/web5/lib/src/dids/bearer_did.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<PortableDid> export() async {
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,7 +53,9 @@ class DidDocumentMetadata {
/// the scope of the containing DID document.
final String? canonicalId;

DidDocumentMetadata({
final List<DidDhtRegisteredDidType>? types;

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

Map<String, dynamic> toJson() {
Expand Down
17 changes: 17 additions & 0 deletions packages/web5/lib/src/dids/did_core/did_verification_method.dart
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -52,3 +53,19 @@ 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<VerificationPurpose> purposes;
final String type;
}
125 changes: 125 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,127 @@ 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 Jwk idKeyUri = await keyManager.generatePrivateKey(idAlgorithm);
final Jwk identityKey = await Crypto.computePublicKey(idKeyUri);

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 keyUri;

if (vm.id?.split('#').last == '0') {
keyUri = idKeyUri;
} else {
keyUri = await keyManager.generatePrivateKey(vm.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<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
Loading