From 82176c77fa3d0affb68a31785abd11fefa722027 Mon Sep 17 00:00:00 2001 From: Moe Jangda Date: Wed, 24 Apr 2024 20:15:27 -0500 Subject: [PATCH] Refactor `did:dht` (#68) Co-authored-by: Ethan Lee Co-authored-by: Jiyoon Koo <118510103+jiyoontbd@users.noreply.github.com> Co-authored-by: Ethan Lee <125412902+ethan-tbd@users.noreply.github.com> --- packages/web5/lib/src/crypto/secp256k1.dart | 12 +- .../lib/src/dids/did_core/did_document.dart | 12 +- .../dids/did_core/did_document_metadata.dart | 2 +- .../web5/lib/src/dids/did_dht/bencoder.dart | 43 ++ packages/web5/lib/src/dids/did_dht/bep44.dart | 101 +++++ .../web5/lib/src/dids/did_dht/converters.dart | 3 + .../converters/did_document_converter.dart | 168 ++++++++ .../did_dht/converters/service_converter.dart | 73 ++++ .../dids/did_dht/converters/vm_converter.dart | 74 ++++ .../web5/lib/src/dids/did_dht/did_dht.dart | 322 +++++---------- .../web5/lib/src/dids/did_dht/dns/answer.dart | 180 --------- .../web5/lib/src/dids/did_dht/dns/consts.dart | 8 - .../web5/lib/src/dids/did_dht/dns/header.dart | 150 ------- .../web5/lib/src/dids/did_dht/dns/name.dart | 107 ----- .../web5/lib/src/dids/did_dht/dns/opcode.dart | 39 -- .../lib/src/dids/did_dht/dns/opt_data.dart | 141 ------- .../web5/lib/src/dids/did_dht/dns/packet.dart | 133 ------- .../lib/src/dids/did_dht/dns/question.dart | 95 ----- .../web5/lib/src/dids/did_dht/dns/rcode.dart | 39 -- .../web5/lib/src/dids/did_dht/dns/rdata.dart | 3 - .../lib/src/dids/did_dht/dns/txt_data.dart | 80 ---- .../web5/lib/src/dids/did_dht/dns_packet.dart | 11 +- .../src/dids/did_dht/dns_packet/answer.dart | 186 ++++----- .../src/dids/did_dht/dns_packet/class.dart | 30 -- .../did_dht/{dns => dns_packet}/codec.dart | 0 .../src/dids/did_dht/dns_packet/header.dart | 106 ++--- .../lib/src/dids/did_dht/dns_packet/name.dart | 87 ++-- .../src/dids/did_dht/dns_packet/opcode.dart | 16 +- .../src/dids/did_dht/dns_packet/opt_data.dart | 61 ++- .../dids/did_dht/dns_packet/option_code.dart | 41 -- .../src/dids/did_dht/dns_packet/packet.dart | 372 +++++------------- .../src/dids/did_dht/dns_packet/question.dart | 107 +++-- .../src/dids/did_dht/dns_packet/rcode.dart | 16 +- .../src/dids/did_dht/dns_packet/rdata.dart | 6 +- .../{dns => dns_packet}/rdata_codecs.dart | 8 +- .../{dns => dns_packet}/record_class.dart | 0 .../{dns => dns_packet}/record_type.dart | 0 .../src/dids/did_dht/dns_packet/txt_data.dart | 73 ++-- .../lib/src/dids/did_dht/dns_packet/type.dart | 159 -------- .../src/dids/did_dht/registered_did_type.dart | 51 --- .../src/dids/did_dht/registered_types.dart | 31 ++ .../lib/src/dids/did_dht/root_record.dart | 133 +++++++ .../lib/src/dids/did_dht/service_record.dart | 18 - .../web5/lib/src/dids/did_dht/vm_record.dart | 18 - .../web5/lib/src/dids/did_jwk/did_jwk.dart | 1 + .../web5/lib/src/dids/did_web/did_web.dart | 2 +- .../test/dids/did_core/did_document_test.dart | 5 +- .../web5/test/dids/did_dht/bencoder_test.dart | 53 +++ .../web5/test/dids/did_dht/bep44_test.dart | 7 + .../did_document_converter_test.dart | 155 ++++++++ .../converters/service_converter_test.dart | 73 ++++ .../did_dht/converters/vm_converter_test.dart | 30 ++ .../web5/test/dids/did_dht/did_dht_test.dart | 49 +-- .../{dns => dns_packet}/answer_test.dart | 10 +- .../{dns => dns_packet}/header_test.dart | 2 +- .../{dns => dns_packet}/name_test.dart | 2 +- .../{dns => dns_packet}/packet_test.dart | 6 +- .../{dns => dns_packet}/txt_data_test.dart | 2 +- .../did_dht/{dns => dns_packet}/vector.dart | 0 59 files changed, 1558 insertions(+), 2154 deletions(-) create mode 100644 packages/web5/lib/src/dids/did_dht/bencoder.dart create mode 100644 packages/web5/lib/src/dids/did_dht/bep44.dart create mode 100644 packages/web5/lib/src/dids/did_dht/converters.dart create mode 100644 packages/web5/lib/src/dids/did_dht/converters/did_document_converter.dart create mode 100644 packages/web5/lib/src/dids/did_dht/converters/service_converter.dart create mode 100644 packages/web5/lib/src/dids/did_dht/converters/vm_converter.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/answer.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/consts.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/header.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/name.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/opcode.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/opt_data.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/packet.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/question.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/rcode.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/rdata.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns/txt_data.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/dns_packet/class.dart rename packages/web5/lib/src/dids/did_dht/{dns => dns_packet}/codec.dart (100%) delete mode 100644 packages/web5/lib/src/dids/did_dht/dns_packet/option_code.dart rename packages/web5/lib/src/dids/did_dht/{dns => dns_packet}/rdata_codecs.dart (72%) rename packages/web5/lib/src/dids/did_dht/{dns => dns_packet}/record_class.dart (100%) rename packages/web5/lib/src/dids/did_dht/{dns => dns_packet}/record_type.dart (100%) delete mode 100644 packages/web5/lib/src/dids/did_dht/dns_packet/type.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/registered_did_type.dart create mode 100644 packages/web5/lib/src/dids/did_dht/registered_types.dart create mode 100644 packages/web5/lib/src/dids/did_dht/root_record.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/service_record.dart delete mode 100644 packages/web5/lib/src/dids/did_dht/vm_record.dart create mode 100644 packages/web5/test/dids/did_dht/bencoder_test.dart create mode 100644 packages/web5/test/dids/did_dht/bep44_test.dart create mode 100644 packages/web5/test/dids/did_dht/converters/did_document_converter_test.dart create mode 100644 packages/web5/test/dids/did_dht/converters/service_converter_test.dart create mode 100644 packages/web5/test/dids/did_dht/converters/vm_converter_test.dart rename packages/web5/test/dids/did_dht/{dns => dns_packet}/answer_test.dart (82%) rename packages/web5/test/dids/did_dht/{dns => dns_packet}/header_test.dart (86%) rename packages/web5/test/dids/did_dht/{dns => dns_packet}/name_test.dart (93%) rename packages/web5/test/dids/did_dht/{dns => dns_packet}/packet_test.dart (91%) rename packages/web5/test/dids/did_dht/{dns => dns_packet}/txt_data_test.dart (92%) rename packages/web5/test/dids/did_dht/{dns => dns_packet}/vector.dart (100%) diff --git a/packages/web5/lib/src/crypto/secp256k1.dart b/packages/web5/lib/src/crypto/secp256k1.dart index b51d6b9..639932b 100644 --- a/packages/web5/lib/src/crypto/secp256k1.dart +++ b/packages/web5/lib/src/crypto/secp256k1.dart @@ -172,7 +172,15 @@ class Secp256k1 { } static Jwk bytesToPublicKey(Uint8List input) { - // TODO: implement bytesToPublicKey - throw UnimplementedError(); + final xBytes = input.sublist(1, 33); + final yBytes = input.sublist(33, 65); + + return Jwk( + kty: kty, + alg: alg, + crv: crv, + x: Base64Url.encode(xBytes), + y: Base64Url.encode(yBytes), + ); } } diff --git a/packages/web5/lib/src/dids/did_core/did_document.dart b/packages/web5/lib/src/dids/did_core/did_document.dart index c72f32e..d9d7ff5 100644 --- a/packages/web5/lib/src/dids/did_core/did_document.dart +++ b/packages/web5/lib/src/dids/did_core/did_document.dart @@ -30,7 +30,7 @@ class DidDocument implements DidResource { /// A DID controller is an entity that is authorized to make changes to a /// DID document. The process of authorizing a DID controller is defined /// by the DID method. - final dynamic controller; // String or List + final List? controller; /// cryptographic public keys, which can be used to authenticate or authorize /// interactions with the DID subject or associated parties. @@ -83,8 +83,8 @@ class DidDocument implements DidResource { List? capabilityInvocation; DidDocument({ - this.context, required this.id, + this.context, this.alsoKnownAs, this.controller, this.verificationMethod, @@ -98,13 +98,13 @@ class DidDocument implements DidResource { void addVerificationMethod( DidVerificationMethod vm, { - VerificationPurpose? purpose, + List purpose = const [], }) { verificationMethod ??= []; verificationMethod!.add(vm); - if (purpose != null) { - addVerificationPurpose(purpose, vm.id); + for (final p in purpose) { + addVerificationPurpose(p, vm.id); } } @@ -234,7 +234,7 @@ class DidDocument implements DidResource { context: json['context'], id: json['id'], alsoKnownAs: json['alsoKnownAs']?.cast(), - controller: json['controller'], + controller: json['controller']?.cast>(), verificationMethod: (json['verificationMethod'] as List?) ?.map((item) => DidVerificationMethod.fromJson(item)) .toList(), 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 b4b39eb..7f8fa66 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,4 +1,4 @@ -import 'package:web5/src/dids/did_dht/registered_did_type.dart'; +import 'package:web5/src/dids/did_dht/registered_types.dart'; /// contains metadata about the DID document contained in the didDocument /// property. This metadata typically does not change between invocations of diff --git a/packages/web5/lib/src/dids/did_dht/bencoder.dart b/packages/web5/lib/src/dids/did_dht/bencoder.dart new file mode 100644 index 0000000..a7d0bb1 --- /dev/null +++ b/packages/web5/lib/src/dids/did_dht/bencoder.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +enum Token { + dict('d', 100), + integer('i', 105), + list('l', 108), + end('e', 101); + + final String value; + final int byte; + + const Token(this.value, this.byte); +} + +/// More information about Bencode can be found +/// [here](https://wiki.theory.org/BitTorrentSpecification#Bencoding) +class Bencoder { + // Encodes various Dart types into Bencoded format + static String bencode(dynamic input) { + if (input is String) { + return '${input.length}:$input'; + } else if (input is int) { + return '${Token.integer.value}$input${Token.end.value}'; + } else if (input is Uint8List) { + final str = utf8.decode(input); + return bencode(str); + } else { + throw FormatException('Unsupported type: ${input.runtimeType}'); + } + } + + static Uint8List encode(dynamic input) { + if (input is String || input is int) { + return utf8.encode(bencode(input)); + } else if (input is Uint8List) { + final prefix = utf8.encode('${input.length}:'); + return Uint8List.fromList([...prefix, ...input]); + } else { + throw FormatException('Unsupported type: ${input.runtimeType}'); + } + } +} diff --git a/packages/web5/lib/src/dids/did_dht/bep44.dart b/packages/web5/lib/src/dids/did_dht/bep44.dart new file mode 100644 index 0000000..a5d09f5 --- /dev/null +++ b/packages/web5/lib/src/dids/did_dht/bep44.dart @@ -0,0 +1,101 @@ +import 'dart:typed_data'; +import 'package:web5/src/crypto.dart'; +import 'package:web5/src/dids/did_dht/bencoder.dart'; + +typedef Signer = Future Function(Uint8List payload); + +/// Represents a BEP44 message, which is used for storing and retrieving data +/// in the Mainline DHT network. +/// +/// A BEP44 message is used primarily in the context of the DID DHT method +/// for publishing and resolving DID documents in the DHT network. This type +/// encapsulates the data structure required for such operations in accordance +/// with BEP44. +/// +/// See [BEP44 Specification](https://www.bittorrent.org/beps/bep_0044.html) +class Bep44Message { + static Future create( + Uint8List message, + int seq, + Signer sign, + ) async { + final toSign = BytesBuilder(copy: false); + toSign.add(Bencoder.encode('seq')); + toSign.add(Bencoder.encode(seq)); + toSign.add(Bencoder.encode('v')); + toSign.add(Bencoder.encode(message)); + + final sig = await sign(toSign.toBytes()); + + // The sequence number needs to be converted to a big-endian byte array. + final seqBytes = ByteData(8)..setInt64(0, seq, Endian.big); + final encoded = BytesBuilder(copy: false); + + encoded.add(sig); + encoded.add(seqBytes.buffer.asUint8List()); + encoded.add(message); + + return encoded.toBytes(); + } + + static DecodedBep44Message decode(Uint8List bytes) { + if (bytes.length < 72) { + throw FormatException( + 'Response must be at least 72 bytes but got: ${bytes.length}', + ); + } + + if (bytes.length > 1072) { + throw FormatException( + 'Response is larger than 1072 bytes, got: ${bytes.length}', + ); + } + + final sig = bytes.sublist(0, 64); + final seqBytes = ByteData.sublistView(bytes, 64, 72); + final int seq = seqBytes.getUint64(0, Endian.big); + final v = bytes.sublist(72); + + // The public key 'k' is not provided in the data and needs to be handled accordingly. + return DecodedBep44Message(seq: seq, sig: sig, v: v); + } + + static DecodedBep44Message verify(Uint8List input, Uint8List publicKey) { + final message = decode(input); + message.verify(publicKey); + + return message; + } +} + +class DecodedBep44Message { + /// The sequence number of the message, used to ensure the latest version of + /// the data is retrieved and updated. It's a monotonically increasing number. + int seq; + + /// The signature of the message, ensuring the authenticity and integrity + /// of the data. It's computed over the bencoded sequence number and value. + Uint8List sig; + + /// The actual data being stored or retrieved from the DHT network, typically + /// encoded in a format suitable for DNS packet representation of a DID Document. + Uint8List v; + + DecodedBep44Message({ + required this.seq, + required this.sig, + required this.v, + }); + + void verify(Uint8List publicKey) async { + final toSign = BytesBuilder(copy: false); + toSign.add(Bencoder.encode('seq')); + toSign.add(Bencoder.encode(seq)); + toSign.add(Bencoder.encode('v')); + toSign.add(Bencoder.encode(v)); + + final jwk = Crypto.bytesToPublicKey(AlgorithmId.ed25519, publicKey); + + await Ed25519.verify(jwk, toSign.toBytes(), sig); + } +} diff --git a/packages/web5/lib/src/dids/did_dht/converters.dart b/packages/web5/lib/src/dids/did_dht/converters.dart new file mode 100644 index 0000000..f9a4399 --- /dev/null +++ b/packages/web5/lib/src/dids/did_dht/converters.dart @@ -0,0 +1,3 @@ +export './converters/did_document_converter.dart'; +export './converters/vm_converter.dart'; +export './converters/service_converter.dart'; diff --git a/packages/web5/lib/src/dids/did_dht/converters/did_document_converter.dart b/packages/web5/lib/src/dids/did_dht/converters/did_document_converter.dart new file mode 100644 index 0000000..93a5f73 --- /dev/null +++ b/packages/web5/lib/src/dids/did_dht/converters/did_document_converter.dart @@ -0,0 +1,168 @@ +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/root_record.dart'; +import 'package:web5/src/dids/did_dht/converters/vm_converter.dart'; +import 'package:web5/src/dids/did_dht/converters/service_converter.dart'; + +/// Class that houses methods to convert a [DidDocument] to a [DnsPacket] +/// and vice versa. +class DidDocumentConverter { + /// Converts a [DidDocument] to a [DnsPacket]. + static DnsPacket convertDidDocument(DidDocument document) { + final rootRecord = RootRecord(); + final List> answers = []; + + final vmRecordMap = {}; + + final verificationMethods = document.verificationMethod ?? []; + for (var i = 0; i < verificationMethods.length; i++) { + final vm = verificationMethods[i]; + final txtRecord = + VerificationMethodConverter.convertVerificationMethod(i, vm); + + answers.add(txtRecord); + rootRecord.addVmRecordName(i); + + vmRecordMap[vm.id] = i; + } + + final assertionMethods = document.assertionMethod ?? []; + for (final am in assertionMethods) { + final vmRecordName = vmRecordMap[am]; + if (vmRecordName != null) { + rootRecord.addAsmRecordName(vmRecordName); + } + } + + final authMethods = document.authentication ?? []; + for (final am in authMethods) { + final vmRecordName = vmRecordMap[am]; + if (vmRecordName != null) { + rootRecord.addAuthRecordName(vmRecordName); + } + } + + final capabilityDelegations = document.capabilityDelegation ?? []; + for (final cd in capabilityDelegations) { + final vmRecordName = vmRecordMap[cd]; + if (vmRecordName != null) { + rootRecord.addDelRecordName(vmRecordName); + } + } + + final capabilityInvocations = document.capabilityInvocation ?? []; + for (final ci in capabilityInvocations) { + final vmRecordName = vmRecordMap[ci]; + if (vmRecordName != null) { + rootRecord.addInvRecordName(vmRecordName); + } + } + + final agmMethods = document.keyAgreement ?? []; + for (final agm in agmMethods) { + final vmRecordName = vmRecordMap[agm]; + if (vmRecordName != null) { + rootRecord.addAgmRecordName(vmRecordName); + } + } + + final serviceRecords = document.service ?? []; + for (var i = 0; i < serviceRecords.length; i++) { + final service = serviceRecords[i]; + final txtRecord = ServiceRecordConverter.convertService(i, service); + + answers.add(txtRecord); + rootRecord.addSvcRecordName(i); + } + + answers.insert(0, rootRecord.toTxtRecord(document.id)); + + return DnsPacket.create(answers); + } + + static DidDocument convertDnsPacket(Did did, DnsPacket dnsPacket) { + final didDocument = DidDocument(id: did.uri); + + final purposesMap = {}; + RootRecord? rootRecord; + + for (final answer in dnsPacket.answers) { + if (answer.type != RecordType.TXT) { + continue; + } + + // lame but necessary. can't use as Answer because in Dart, + // even though TxtData is a subtype of RData, Answer + // is not considered a subtype of Answer because generic types are + //invariant. This means that even if B is a subtype of A, Generic + // is not considered a subtype of Generic + final txtData = answer.data as TxtData; + final txtRecord = Answer( + name: answer.name, + type: answer.type, + klass: answer.klass, + ttl: answer.ttl, + data: txtData, + ); + + if (answer.name.value == '_did.${did.id}') { + rootRecord = RootRecord.fromTxtRecord(txtRecord); + } else if (txtRecord.name.value.startsWith('_k')) { + final vm = + VerificationMethodConverter.convertTxtRecord(did.uri, txtRecord); + didDocument.addVerificationMethod(vm); + + final delim = txtRecord.name.value.indexOf('.', 3); + final recordName = txtRecord.name.value.substring(1, delim); + purposesMap[recordName] = vm.id; + } else if (txtRecord.name.value.startsWith('_s')) { + final service = + ServiceRecordConverter.convertTxtRecord(did.uri, txtRecord); + didDocument.addService(service); + } + } + + for (final recordName in rootRecord!.asm) { + final vmId = purposesMap[recordName]; + didDocument.addVerificationPurpose( + VerificationPurpose.assertionMethod, + vmId, + ); + } + + for (final recordName in rootRecord.auth) { + final vmId = purposesMap[recordName]; + didDocument.addVerificationPurpose( + VerificationPurpose.authentication, + vmId, + ); + } + + for (final recordName in rootRecord.del) { + final vmId = purposesMap[recordName]; + didDocument.addVerificationPurpose( + VerificationPurpose.capabilityDelegation, + vmId, + ); + } + + for (final recordName in rootRecord.inv) { + final vmId = purposesMap[recordName]; + didDocument.addVerificationPurpose( + VerificationPurpose.capabilityInvocation, + vmId, + ); + } + + for (final recordName in rootRecord.agm) { + final vmId = purposesMap[recordName]; + didDocument.addVerificationPurpose( + VerificationPurpose.keyAgreement, + vmId, + ); + } + + return didDocument; + } +} diff --git a/packages/web5/lib/src/dids/did_dht/converters/service_converter.dart b/packages/web5/lib/src/dids/did_dht/converters/service_converter.dart new file mode 100644 index 0000000..6408ad3 --- /dev/null +++ b/packages/web5/lib/src/dids/did_dht/converters/service_converter.dart @@ -0,0 +1,73 @@ +import 'package:web5/src/dids/did_core.dart'; +import 'package:web5/src/dids/did_dht/dns_packet.dart'; + +/// Class that houses methods to convert a [DidService] to a [Answer] +/// and vice versa. +class ServiceRecordConverter { + static Set txtEntryNames = {'id', 't', 'se'}; + + /// Converts a [DidService] to a [Answer]. + static Answer convertService(int idx, DidService service) { + final data = [ + 'id=${service.id.split('#').last}', + 't=${service.type}', + 'se=${service.serviceEndpoint.join(',')}', + ].join(';'); + + return Answer( + name: RecordName('_s$idx._did'), + type: RecordType.TXT, + klass: RecordClass.IN, + data: TxtData([data]), + ttl: 7200, + ); + } + + /// Converts a [Answer] to a [DidService]. + static DidService convertTxtRecord(String did, Answer record) { + final Map map = {}; + + final fields = record.data.value.first.split(';'); + for (final field in fields) { + final parts = field.split('='); + if (parts.length != 2) { + throw Exception('Invalid verification method format'); + } + + final [key, value] = parts; + map[key] = value; + } + + for (final entry in txtEntryNames) { + if (!map.containsKey(entry)) { + throw Exception('service record Missing entry: $entry'); + } + } + + return DidService( + id: '$did#${map['id']!}', + type: map['t']!, + serviceEndpoint: map['se']!.split(','), + ); + } + + // factory ServiceRecord.fromService(DidService service) { + // return ServiceRecord( + // id: service.id, + // type: service.type, + // rData: TxtData([ + // 'id=${service.id};t=${service.type};se=${service.serviceEndpoint};', + // ]), + // ttl: 7200, + // serviceEndpoint: service.serviceEndpoint, + // ); + // } + + // DidService toService() { + // return DidService( + // id: id, + // type: type, + // serviceEndpoint: serviceEndpoint ?? '', + // ); + // } +} diff --git a/packages/web5/lib/src/dids/did_dht/converters/vm_converter.dart b/packages/web5/lib/src/dids/did_dht/converters/vm_converter.dart new file mode 100644 index 0000000..fd41b3c --- /dev/null +++ b/packages/web5/lib/src/dids/did_dht/converters/vm_converter.dart @@ -0,0 +1,74 @@ +import 'package:web5/src/crypto.dart'; +import 'package:web5/src/dids/did_core.dart'; +import 'package:web5/src/dids/did_dht/dns_packet.dart'; +import 'package:web5/src/encoders.dart'; + +/// Relevant [spec text](https://did-dht.com/#verification-method-record) +class VerificationMethodConverter { + /// relevant [spec text](https://did-dht.com/registry/index.html#key-type-index) + static final Map _keyTypeToIndex = { + Ed25519.crv: '0', + Secp256k1.crv: '1', + }; + + /// relevant [spec text](https://did-dht.com/registry/index.html#key-type-index) + static final Map _indexToKeyType = { + '0': AlgorithmId.ed25519, + '1': AlgorithmId.secp256k1, + }; + + static Set txtEntryNames = {'id', 't', 'k', 'a', 'c'}; + + static Answer convertVerificationMethod( + int idx, + DidVerificationMethod method, + ) { + final pubKey = Crypto.publicKeyToBytes(method.publicKeyJwk!); + final keyTypeIndex = _keyTypeToIndex[method.publicKeyJwk!.crv!]; + final data = [ + 'id=${method.id.split('#').last}', + 't=$keyTypeIndex', + 'k=${Base64Url.encode(pubKey)}', + ].join(';'); + + return Answer( + name: RecordName('_k$idx._did'), + type: RecordType.TXT, + klass: RecordClass.IN, + data: TxtData([data]), + ttl: 7200, + ); + } + + static DidVerificationMethod convertTxtRecord( + String did, + Answer record, + ) { + final map = {}; + + final fields = record.data.value.first.split(';'); + for (final field in fields) { + final parts = field.split('='); + if (parts.length != 2) { + throw Exception('Invalid verification method format'); + } + + final [key, value] = parts; + map[key] = value; + } + + final keyType = _indexToKeyType[map['t']]; + if (keyType == null) { + throw Exception('Unsupported key type: ${map['t']}'); + } + + final pubKey = Crypto.bytesToPublicKey(keyType, Base64Url.decode(map['k'])); + final vmIdFragment = map['id'] ?? pubKey.computeThumbprint(); + return DidVerificationMethod( + id: '$did#$vmIdFragment', + type: 'JsonWebKey', + controller: map['c'] ?? did, + publicKeyJwk: pubKey, + ); + } +} 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 a5a7f5c..e1436a7 100644 --- a/packages/web5/lib/src/dids/did_dht/did_dht.dart +++ b/packages/web5/lib/src/dids/did_dht/did_dht.dart @@ -1,150 +1,130 @@ +import 'dart:convert'; 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.dart'; +import 'package:web5/src/dids/did_dht/bep44.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/dids/did_dht/registered_types.dart'; +import 'package:web5/src/dids/did_dht/converters/did_document_converter.dart'; import 'package:web5/src/encoders/zbase.dart'; -final Set txtEntryNames = {'vm', 'auth', 'asm', 'agm', 'inv', 'del'}; - class DidDht { static const String methodName = 'dht'; + static const _defaultRelay = 'https://diddht.tbddev.org'; static Future create({ KeyManager? keyManager, - List? alsoKnownAs, - List? controllers, - String? gatewayUri, - bool? publish, - List? services, + bool publish = false, + String gatewayUri = _defaultRelay, + List alsoKnownAs = const [], + List controllers = const [], + List services = const [], List? types, - List? verificationMethods, + List verificationMethods = const [], }) 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 keyAlias = await keyManager.generatePrivateKey(AlgorithmId.ed25519); + final identityKey = await keyManager.getPublicKey(keyAlias); - final String didUri = identityKeyToIdentifier(identityKey: identityKey); - final DidDocument doc = DidDocument( - id: didUri, - alsoKnownAs: alsoKnownAs, - controller: controllers ?? didUri, + final id = _computeIdentifier(identityKey: identityKey); + final did = 'did:$methodName:$id'; + + final identityVm = DidVerificationMethod( + id: '0', + type: 'JsonWebKey', + controller: did, ); - // If the given verification methods do not contain an Identity Key, add one. - final List methodsToAdd = - verificationMethods ?? []; + final didDoc = DidDocument( + id: did, + alsoKnownAs: alsoKnownAs, + controller: controllers.isEmpty ? [did] : controllers, + ); - final Iterable identityMethods = - methodsToAdd.where( - (vm) => vm.id?.split('#').last == '0', + didDoc.addVerificationMethod( + identityVm, + purpose: [ + VerificationPurpose.authentication, + VerificationPurpose.assertionMethod, + VerificationPurpose.capabilityDelegation, + VerificationPurpose.capabilityInvocation, + ], ); - if (identityMethods.isEmpty) { - methodsToAdd.add( - DidCreateVerificationMethod( - algorithm: AlgorithmId.ed25519, - id: '0', - type: 'JsonWebKey', - controller: didUri, - purposes: [ - VerificationPurpose.authentication, - VerificationPurpose.assertionMethod, - VerificationPurpose.capabilityDelegation, - VerificationPurpose.capabilityInvocation, - ], - ), + for (final vmOpts in verificationMethods) { + final alias = await keyManager.generatePrivateKey(vmOpts.algorithm); + final 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 = + vmOpts.id ?? publicKey.kid ?? publicKey.computeThumbprint(); + methodId = '$did#${methodId.split('#').last}'; + + final vm = DidVerificationMethod( + id: methodId, + type: vmOpts.type, + controller: vmOpts.controller, + publicKeyJwk: publicKey, ); + didDoc.addVerificationMethod(vm, purpose: vmOpts.purposes); + } + + for (final service in services) { + didDoc.addService(service); } - // 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 (publish == true) { + final dnsPacket = DidDocumentConverter.convertDidDocument(didDoc); - if (vm.id?.split('#').last == '0') { - publicKey = identityKey; - } else { - String alias = await keyManager.generatePrivateKey(vm.algorithm); - publicKey = await keyManager.getPublicKey(alias); + sign(Uint8List data) async { + return await keyManager!.sign(keyAlias, data); } - // 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}'; + final seq = DateTime.now().microsecondsSinceEpoch; + final message = await Bep44Message.create(dnsPacket.encode(), seq, sign); - doc.addVerificationMethod( - DidVerificationMethod( - id: methodId, - type: vm.type, - controller: vm.controller, - publicKeyJwk: publicKey, - ), - ); + final pkarrUrl = Uri.parse('$gatewayUri/$id'); + final request = await HttpClient().putUrl(pkarrUrl); - for (final VerificationPurpose purpose in vm.purposes) { - doc.addVerificationPurpose(purpose, methodId); - } - } + request.headers.contentType = ContentType.binary; + request.add(message); - for (final DidService service in (services ?? [])) { - doc.addService(service); + final response = await request.close(); + if (response.statusCode != HttpStatus.ok) { + final body = await response.transform(utf8.decoder).join(); + throw Exception( + 'Failed to publish DID document. got: ${response.statusCode} $body', + ); + } } - final BearerDid did = BearerDid( - uri: didUri, + return BearerDid( + uri: did, keyManager: keyManager, - document: doc, - metadata: DidDocumentMetadata( - types: types, - ), + document: didDoc, + // 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', + String relayUrl = _defaultRelay, HttpClient? client, }) async { if (did.method != methodName) { return DidResolutionResult.withError(DidResolutionError.invalidDid); } + final List identityKey; + try { + identityKey = ZBase32.decode(did.id); + } catch (e) { + return DidResolutionResult.withError(DidResolutionError.invalidDid); + } + final parsedRelayUrl = Uri.parse(relayUrl); final resolutionUrl = parsedRelayUrl.replace(path: did.id); @@ -153,139 +133,33 @@ class DidDht { final response = await request.close(); final List bytes = []; - // Listening for response data await for (var byteList in response) { bytes.addAll(byteList); } httpClient.close(force: false); - // TODO: verify signature - // final signatureBytes = bytes.sublist(0, 64); - // final seq = bytes.sublist(64, 72); - - if (bytes.length < 72) { - return DidResolutionResult.withError(DidResolutionError.invalidDid); - } - - final v = bytes.sublist(72); - - final dnsPacket = DnsPacket.decode(v); - final Map> txtMap = {}; - List? rootRecord; - - for (final answer in dnsPacket.answers) { - if (answer.type != DnsType.TXT) { - continue; - } - - final txtData = answer.data as DnsTxtData; - - if (answer.name.value.startsWith('_did')) { - rootRecord = txtData.value; - continue; - } - - txtMap[answer.name.value] = txtData.value; - } + final bep44Message = Bep44Message.verify( + Uint8List.fromList(bytes), + Uint8List.fromList(identityKey), + ); - if (rootRecord == null) { - // TODO: figure out more appopriate resolution error to use. + try { + final dnsPacket = DnsPacket.decode(bep44Message.v); + final document = DidDocumentConverter.convertDnsPacket(did, dnsPacket); + return DidResolutionResult(didDocument: document); + } catch (e) { return DidResolutionResult.withError(DidResolutionError.invalidDid); } + } - final Map> relationshipsMap = {}; - for (final entry in rootRecord[0].split(';')) { - final splitEntry = entry.split('='); - - if (splitEntry.length != 2) { - // TODO: figure out more appopriate resolution error to use. - return DidResolutionResult.withError(DidResolutionError.invalidDid); - } - - final [property, values] = splitEntry; - final splitValues = values.split(','); - - if (!txtEntryNames.contains(property)) { - continue; - } - - for (final value in splitValues) { - relationshipsMap[value] ??= []; - relationshipsMap[value]!.add(property); - } - } - - final didDocument = DidDocument(id: did.uri); - for (final property in txtMap.entries) { - final values = property.value[0].split(','); - final valueMap = {}; - - for (var value in values) { - final [k, v] = value.split('='); - valueMap[k] = v; - } - - if (property.key.startsWith('_k')) { - AlgorithmId algId; - switch (valueMap['t']) { - case '0': - algId = AlgorithmId.ed25519; - break; - case '1': - algId = AlgorithmId.secp256k1; - break; - default: - throw Exception('unsupported algorithm type: ${valueMap['t']}'); - } - - final publicKeyBytes = Base64Url.decode(valueMap['k']); - final publicKeyJwk = Crypto.bytesToPublicKey(algId, publicKeyBytes); - final verificationMethod = DidVerificationMethod( - controller: did.uri, - id: valueMap['id'], - type: 'JsonWebKey2020', - publicKeyJwk: publicKeyJwk, - ); - - didDocument.addVerificationMethod(verificationMethod); - final entryId = property.key.substring(1).split('.')[0]; - final relationships = relationshipsMap[entryId]; - - if (relationships == null) { - continue; - } - - for (final relationship in relationships) { - VerificationPurpose? vr; - if (relationship == 'auth') { - vr = VerificationPurpose.authentication; - } else if (relationship == 'asm') { - vr = VerificationPurpose.assertionMethod; - } else if (relationship == 'agm') { - vr = VerificationPurpose.keyAgreement; - } else if (relationship == 'inv') { - vr = VerificationPurpose.capabilityInvocation; - } else if (relationship == 'del') { - vr = VerificationPurpose.capabilityDelegation; - } - - if (vr != null) { - didDocument.addVerificationPurpose(vr, verificationMethod.id); - } - } - } else if (property.key.startsWith('_s')) { - final service = DidService( - id: valueMap['id'], - type: valueMap['t'], - serviceEndpoint: valueMap['uri'], - ); - - didDocument.addService(service); - } - } + static String _computeIdentifier({ + required Jwk identityKey, + }) { + // Convert the key from JWK format to a byte array. + final Uint8List publicKeyBytes = Crypto.publicKeyToBytes(identityKey); - return DidResolutionResult(didDocument: didDocument); + return ZBase32.encode(publicKeyBytes); } } diff --git a/packages/web5/lib/src/dids/did_dht/dns/answer.dart b/packages/web5/lib/src/dids/did_dht/dns/answer.dart deleted file mode 100644 index c99b530..0000000 --- a/packages/web5/lib/src/dids/did_dht/dns/answer.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'dart:typed_data'; - -import 'package:web5/src/dids/did_dht/dns/codec.dart'; -import 'package:web5/src/dids/did_dht/dns/rdata_codecs.dart'; -import 'package:web5/src/dids/did_dht/dns/consts.dart'; -import 'package:web5/src/dids/did_dht/dns/name.dart'; -import 'package:web5/src/dids/did_dht/dns/opt_data.dart'; -import 'package:web5/src/dids/did_dht/dns/rdata.dart'; -import 'package:web5/src/dids/did_dht/dns/record_class.dart'; -import 'package:web5/src/dids/did_dht/dns/record_type.dart'; - -/// Represents an answer section in a DNS packet. -class Answer { - /// The domain name to which this resource record pertains. - late RecordName name; - - /// The type of the resource record. Specifies the meaning of the data in the RData field. - late RecordType type; - - /// The class of the data in the RData field. - late RecordClass klass; - - /// The specific data for this resource record, according to its type. - late T data; - - /// The Time-To-Live of the resource record. This value is the number of seconds - /// that the resource record may be cached before it should be discarded. - late int ttl; - - /// A flag indicating whether the cache flush bit is set for this record. - late bool flush; - - /// For OPT records, this field specifies the maximum UDP payload size. - late int? udpPayloadSize; - - /// For OPT records, this field specifies the extended RCODE. - late int? extendedRcode; - - /// For OPT records, this field specifies the EDNS version. - late int? ednsVersion; - - /// For OPT records, this field specifies the EDNS flags. - late int? flags; - - /// For OPT records, this field indicates whether the DNSSEC OK bit is set. - late bool? flagDo; - - /// Options for OPT records, dynamically determined based on the specific type of option. - late dynamic options; - - Answer({ - required this.name, - required this.type, - required this.klass, - required this.data, - required this.ttl, - this.flush = false, - this.udpPayloadSize, - this.extendedRcode, - this.ednsVersion, - this.flags, - this.flagDo, - this.options, - }); - - Answer._(); - - static final codec = _AnswerCodec(); - - /// Decodes a [Answer] from a byte buffer [buf] starting at the given [offset]. - /// - /// Throws [FormatException] if the buffer data cannot be decoded into a valid DNS answer. - factory Answer.decode(Uint8List buf, int offset) => - codec.decode(buf, offset: offset).value as Answer; - - Uint8List encode({Uint8List? buf, int offset = 0}) => - codec.encode(this, input: buf, offset: offset).value; - - int encodingLength() { - return name.encodingLength() + 8 + data.encodingLength(); - } -} - -class _AnswerCodec implements Codec { - @override - EncodeResult encode( - Answer answer, { - Uint8List? input, - int offset = 0, - }) { - final buf = input ?? Uint8List(answer.encodingLength()); - final oldOffset = offset; - - final n = RecordName.codec.encode(answer.name, input: buf, offset: offset); - offset += n.offset; - - ByteData.view(buf.buffer).setUint16(offset, answer.type.value, Endian.big); - - if (answer.type == RecordType.OPT) { - if (answer.name.value != '.') { - throw Exception('OPT name must be root.'); - } - ByteData.view(buf.buffer) - .setUint16(offset, answer.udpPayloadSize!, Endian.big); - - buf[offset + 4] = answer.extendedRcode!; - buf[offset + 5] = answer.ednsVersion!; - - ByteData.view(buf.buffer) - .setUint16(offset + 6, answer.flags ?? 0, Endian.big); - - offset += 8; - // TODO: need OptDataCodec here - offset += answer.options.encode(buf, offset) as int; - } else { - final klassValue = answer.flush ? FLUSH_MASK : answer.klass.value; - ByteData.view(buf.buffer).setUint16(offset + 2, klassValue, Endian.big); - - ByteData.view(buf.buffer).setUint32(offset + 4, answer.ttl, Endian.big); - - offset += 8; - - final result = RDataCodecs.encode( - answer.type, - answer.data, - input: buf, - offset: offset, - ); - offset += result.offset; - } - - return EncodeResult(buf, offset - oldOffset); - } - - @override - DecodeResult decode(Uint8List buf, {int offset = 0}) { - final originalOffset = offset; - - final nameResult = RecordName.codec.decode(buf, offset: offset); - offset += nameResult.offset; - - final byteData = ByteData.sublistView(buf); - - final rawType = byteData.getUint16(offset, Endian.big); - final type = RecordType.fromValue(rawType); - offset += 2; - - final answer = Answer._(); - answer.name = nameResult.value; - answer.type = type; - - if (type == RecordType.OPT) { - answer.udpPayloadSize = byteData.getUint16(offset + 2, Endian.big); - answer.extendedRcode = byteData.getUint8(offset + 4); - answer.ednsVersion = byteData.getUint8(offset + 5); - - answer.flags = byteData.getUint16(offset + 6, Endian.big); - answer.flagDo = (answer.flags! >> 15) & 0x1 == 1; - - answer.options = OptionData.decode(buf, offset + 8); - - offset += (8 + answer.options.numBytes).toInt(); - } else { - final rawDnsClass = byteData.getUint16(offset, Endian.big); - answer.klass = RecordClass.fromValue(rawDnsClass & NOT_FLUSH_MASK); - - answer.flush = (rawDnsClass & FLUSH_MASK) != 0; - offset += 2; - - answer.ttl = byteData.getUint32(offset, Endian.big); - offset += 4; - - final result = RDataCodecs.decode(type, buf, offset: offset); - answer.data = result.value; - offset += result.offset; - } - - return DecodeResult(answer, offset - originalOffset); - } -} diff --git a/packages/web5/lib/src/dids/did_dht/dns/consts.dart b/packages/web5/lib/src/dids/did_dht/dns/consts.dart deleted file mode 100644 index 56c5979..0000000 --- a/packages/web5/lib/src/dids/did_dht/dns/consts.dart +++ /dev/null @@ -1,8 +0,0 @@ -// ignore_for_file: constant_identifier_names - -const int QUERY_FLAG = 0; -const int RESPONSE_FLAG = 1 << 15; -const int FLUSH_MASK = 1 << 15; -const int NOT_FLUSH_MASK = ~FLUSH_MASK; -const int QU_MASK = 1 << 15; -const int NOT_QU_MASK = ~QU_MASK; diff --git a/packages/web5/lib/src/dids/did_dht/dns/header.dart b/packages/web5/lib/src/dids/did_dht/dns/header.dart deleted file mode 100644 index 2d17732..0000000 --- a/packages/web5/lib/src/dids/did_dht/dns/header.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'dart:typed_data'; - -import 'package:web5/src/dids/did_dht/dns/codec.dart'; -import 'package:web5/src/dids/did_dht/dns/opcode.dart'; -import 'package:web5/src/dids/did_dht/dns/rcode.dart'; - -class Header { - /// 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. - OpCode opcode; - - /// 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; - - /// 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; - - /// TODO: Find documentation for this field - bool? ad; - - /// TODO: Find documentation for this field - bool? cd; - - /// Response code - RCode? 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; - get numAnswers => ancount; - get numAuthorities => nscount; - get numAdditionals => arcount; - - final numBytes = 12; - - Header({ - required this.id, - required this.qr, - required this.opcode, - this.aa, - required this.tc, - required this.rd, - this.ra, - this.z = false, - this.ad, - this.cd, - this.rcode, - required this.qdcount, - required this.ancount, - required this.nscount, - required this.arcount, - }); - - static final codec = _HeaderCodec(); - - factory Header.decode(Uint8List buf, {int offset = 0}) => - codec.decode(buf, offset: offset).value; - - Uint8List encode({Uint8List? buf, int offset = 0}) => - codec.encode(this).value; - - int encodingLength() => numBytes; -} - -class _HeaderCodec implements Codec
{ - @override - EncodeResult encode(Header header, {Uint8List? input, int offset = 0}) { - final buf = input ?? Uint8List(12); - final byteData = ByteData.sublistView(buf); - - byteData.setUint16(offset, header.id, Endian.big); - offset += 2; - - final flags = (header.qr ? 1 : 0) << 15 | - (header.opcode.value & 0xF) << 11 | - (header.aa ?? false ? 1 : 0) << 10 | - (header.tc ? 1 : 0) << 9 | - (header.rd ? 1 : 0) << 8 | - (header.ra ?? false ? 1 : 0) << 7 | - (header.z ? 1 : 0) << 6 | - (header.ad ?? false ? 1 : 0) << 5 | - (header.cd ?? false ? 1 : 0) << 4 | - (header.rcode?.value ?? 0) & 0xF; - - byteData.setUint16(offset, flags, Endian.big); - offset += 2; - - byteData.setUint16(offset, header.qdcount, Endian.big); - offset += 2; - byteData.setUint16(offset, header.ancount, Endian.big); - offset += 2; - byteData.setUint16(offset, header.nscount, Endian.big); - offset += 2; - byteData.setUint16(offset, header.arcount, Endian.big); - - return EncodeResult(buf, 12); - } - - @override - DecodeResult
decode(Uint8List buf, {int offset = 0}) { - if (buf.length < 12) throw Exception('Header must be 12 bytes'); - - final byteData = ByteData.sublistView(buf); - final flags = byteData.getUint16(offset + 2, Endian.big); - - final header = Header( - id: byteData.getUint16(offset, Endian.big), - qr: (flags >> 15) & 0x1 == 1, - opcode: OpCode.fromValue((flags >> 11) & 0xf), - aa: (flags >> 10) & 0x1 == 1, - tc: (flags >> 9) & 0x1 == 1, - rd: (flags >> 8) & 0x1 == 1, - ra: (flags >> 7) & 0x1 == 1, - z: (flags >> 6) & 0x1 == 1, - ad: (flags >> 5) & 0x1 == 1, - cd: (flags >> 4) & 0x1 == 1, - rcode: RCode.fromValue(flags & 0xf), - qdcount: byteData.getUint16(offset + 4, Endian.big), - ancount: byteData.getUint16(offset + 6, Endian.big), - nscount: byteData.getUint16(offset + 8, Endian.big), - arcount: byteData.getUint16(offset + 10, Endian.big), - ); - - return DecodeResult(header, 12); - } -} diff --git a/packages/web5/lib/src/dids/did_dht/dns/name.dart b/packages/web5/lib/src/dids/did_dht/dns/name.dart deleted file mode 100644 index 80c9283..0000000 --- a/packages/web5/lib/src/dids/did_dht/dns/name.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:web5/src/dids/did_dht/dns/codec.dart'; - -class RecordName { - final String value; - - RecordName(this.value); - - static final codec = _NameCodec(); - - factory RecordName.decode(Uint8List buf, {int offset = 0}) => - codec.decode(buf).value; - - Uint8List encode() => codec.encode(this).value; - - int encodingLength() { - if (value == '.' || value == '..') return 1; - return utf8.encode(value.replaceAll(RegExp(r'^\.|\.$'), '')).length + 2; - } -} - -class _NameCodec implements Codec { - @override - EncodeResult encode( - RecordName data, { - Uint8List? input, - int offset = 0, - }) { - final buf = input ?? Uint8List(data.encodingLength()); - final oldOffset = offset; - // Strip leading and trailing dots - final n = data.value.replaceAll(RegExp(r'^\.|\.$'), ''); - if (n.isNotEmpty) { - final list = n.split('.'); - for (var label in list) { - final encodedLabel = utf8.encode(label); - buf[offset] = encodedLabel.length; // Length byte - offset++; - buf.setRange(offset, offset + encodedLabel.length, encodedLabel); - offset += encodedLabel.length; - } - } - - buf[offset++] = 0; // Null terminator for DNS record - - return EncodeResult(buf, offset - oldOffset); - } - - @override - DecodeResult decode(Uint8List buf, {int offset = 0}) { - int oldOffset = offset; - int totalLength = 0; - int consumedBytes = 0; - bool jumped = false; - final List list = []; - - while (true) { - if (offset >= buf.length) { - throw Exception('Cannot decode name (buffer overflow)'); - } - - final len = buf[offset++]; - consumedBytes += jumped ? 0 : 1; - - if (len == 0) { - break; - } else if ((len & 0xc0) == 0) { - if (offset + len > buf.length) { - throw Exception('Cannot decode name (buffer overflow)'); - } - - totalLength += len + 1; - if (totalLength > 254) { - throw Exception('Cannot decode name (name too long)'); - } - - final label = utf8.decode(buf.sublist(offset, offset + len)); - - list.add(label); - offset += len; - consumedBytes += jumped ? 0 : len; - } else if ((len & 0xc0) == 0xc0) { - if (offset >= buf.length) { - throw Exception('Cannot decode name (buffer overflow)'); - } - - final jumpOffset = ((len & 0x3f) << 8) | buf[offset++]; - if (jumpOffset >= oldOffset) { - throw Exception('Cannot decode name (bad pointer)'); - } - - offset = jumpOffset; - oldOffset = jumpOffset; - consumedBytes += jumped ? 0 : 1; - jumped = true; - } else { - throw Exception('Cannot decode name (bad label)'); - } - } - - final decodedName = list.isEmpty ? '.' : list.join('.'); - - return DecodeResult(RecordName(decodedName), consumedBytes); - } -} diff --git a/packages/web5/lib/src/dids/did_dht/dns/opcode.dart b/packages/web5/lib/src/dids/did_dht/dns/opcode.dart deleted file mode 100644 index dcc8f92..0000000 --- a/packages/web5/lib/src/dids/did_dht/dns/opcode.dart +++ /dev/null @@ -1,39 +0,0 @@ -// ignore_for_file: constant_identifier_names - -enum OpCode { - QUERY(0, 'QUERY'), - IQUERY(1, 'IQUERY'), - STATUS(2, 'STATUS'), - OPCODE_3(3, 'OPCODE_3'), - NOTIFY(4, 'NOTIFY'), - UPDATE(5, 'UPDATE'), - OPCODE_6(6, 'OPCODE_6'), - OPCODE_7(7, 'OPCODE_7'), - OPCODE_8(8, 'OPCODE_8'), - OPCODE_9(9, 'OPCODE_9'), - OPCODE_10(10, 'OPCODE_10'), - OPCODE_11(11, 'OPCODE_11'), - OPCODE_12(12, 'OPCODE_12'), - OPCODE_13(13, 'OPCODE_13'), - OPCODE_14(14, 'OPCODE_14'), - OPCODE_15(15, 'OPCODE_15'); - - final int value; - final String name; - - const OpCode(this.value, this.name); - - static OpCode fromValue(int value) { - return OpCode.values.firstWhere( - (op) => op.value == value, - orElse: () => OpCode.QUERY, // Default or a suitable fallback - ); - } - - static OpCode fromName(String name) { - return OpCode.values.firstWhere( - (op) => op.name.toUpperCase() == name.toUpperCase(), - orElse: () => OpCode.QUERY, // Default or a suitable fallback - ); - } -} diff --git a/packages/web5/lib/src/dids/did_dht/dns/opt_data.dart b/packages/web5/lib/src/dids/did_dht/dns/opt_data.dart deleted file mode 100644 index cb6a975..0000000 --- a/packages/web5/lib/src/dids/did_dht/dns/opt_data.dart +++ /dev/null @@ -1,141 +0,0 @@ -import 'dart:typed_data'; - -class OptionData { - final List