//SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import "../utils/BytesUtils.sol"; import "@ensdomains/buffer/contracts/Buffer.sol"; /// @dev RRUtils is a library that provides utilities for parsing DNS resource records. library RRUtils { using BytesUtils for *; using Buffer for *; /// @dev Returns the number of bytes in the DNS name at 'offset' in 'self'. /// @param self The byte array to read a name from. /// @param offset The offset to start reading at. /// @return The length of the DNS name at 'offset', in bytes. function nameLength( bytes memory self, uint256 offset ) internal pure returns (uint256) { uint256 idx = offset; while (true) { assert(idx < self.length); uint256 labelLen = self.readUint8(idx); idx += labelLen + 1; if (labelLen == 0) { break; } } return idx - offset; } /// @dev Returns a DNS format name at the specified offset of self. /// @param self The byte array to read a name from. /// @param offset The offset to start reading at. /// @return ret The name. function readName( bytes memory self, uint256 offset ) internal pure returns (bytes memory ret) { uint256 len = nameLength(self, offset); return self.substring(offset, len); } /// @dev Returns the number of labels in the DNS name at 'offset' in 'self'. /// @param self The byte array to read a name from. /// @param offset The offset to start reading at. /// @return The number of labels in the DNS name at 'offset', in bytes. function labelCount( bytes memory self, uint256 offset ) internal pure returns (uint256) { uint256 count = 0; while (true) { assert(offset < self.length); uint256 labelLen = self.readUint8(offset); offset += labelLen + 1; if (labelLen == 0) { break; } count += 1; } return count; } uint256 constant RRSIG_TYPE = 0; uint256 constant RRSIG_ALGORITHM = 2; uint256 constant RRSIG_LABELS = 3; uint256 constant RRSIG_TTL = 4; uint256 constant RRSIG_EXPIRATION = 8; uint256 constant RRSIG_INCEPTION = 12; uint256 constant RRSIG_KEY_TAG = 16; uint256 constant RRSIG_SIGNER_NAME = 18; struct SignedSet { uint16 typeCovered; uint8 algorithm; uint8 labels; uint32 ttl; uint32 expiration; uint32 inception; uint16 keytag; bytes signerName; bytes data; bytes name; } function readSignedSet( bytes memory data ) internal pure returns (SignedSet memory self) { self.typeCovered = data.readUint16(RRSIG_TYPE); self.algorithm = data.readUint8(RRSIG_ALGORITHM); self.labels = data.readUint8(RRSIG_LABELS); self.ttl = data.readUint32(RRSIG_TTL); self.expiration = data.readUint32(RRSIG_EXPIRATION); self.inception = data.readUint32(RRSIG_INCEPTION); self.keytag = data.readUint16(RRSIG_KEY_TAG); self.signerName = readName(data, RRSIG_SIGNER_NAME); self.data = data.substring( RRSIG_SIGNER_NAME + self.signerName.length, data.length - RRSIG_SIGNER_NAME - self.signerName.length ); } function rrs( SignedSet memory rrset ) internal pure returns (RRIterator memory) { return iterateRRs(rrset.data, 0); } /// @dev An iterator over resource records. struct RRIterator { bytes data; uint256 offset; uint16 dnstype; uint16 class; uint32 ttl; uint256 rdataOffset; uint256 nextOffset; } /// @dev Begins iterating over resource records. /// @param self The byte string to read from. /// @param offset The offset to start reading at. /// @return ret An iterator object. function iterateRRs( bytes memory self, uint256 offset ) internal pure returns (RRIterator memory ret) { ret.data = self; ret.nextOffset = offset; next(ret); } /// @dev Returns true iff there are more RRs to iterate. /// @param iter The iterator to check. /// @return True iff the iterator has finished. function done(RRIterator memory iter) internal pure returns (bool) { return iter.offset >= iter.data.length; } /// @dev Moves the iterator to the next resource record. /// @param iter The iterator to advance. function next(RRIterator memory iter) internal pure { iter.offset = iter.nextOffset; if (iter.offset >= iter.data.length) { return; } // Skip the name uint256 off = iter.offset + nameLength(iter.data, iter.offset); // Read type, class, and ttl iter.dnstype = iter.data.readUint16(off); off += 2; iter.class = iter.data.readUint16(off); off += 2; iter.ttl = iter.data.readUint32(off); off += 4; // Read the rdata uint256 rdataLength = iter.data.readUint16(off); off += 2; iter.rdataOffset = off; iter.nextOffset = off + rdataLength; } /// @dev Returns the name of the current record. /// @param iter The iterator. /// @return A new bytes object containing the owner name from the RR. function name(RRIterator memory iter) internal pure returns (bytes memory) { return iter.data.substring( iter.offset, nameLength(iter.data, iter.offset) ); } /// @dev Returns the rdata portion of the current record. /// @param iter The iterator. /// @return A new bytes object containing the RR's RDATA. function rdata( RRIterator memory iter ) internal pure returns (bytes memory) { return iter.data.substring( iter.rdataOffset, iter.nextOffset - iter.rdataOffset ); } uint256 constant DNSKEY_FLAGS = 0; uint256 constant DNSKEY_PROTOCOL = 2; uint256 constant DNSKEY_ALGORITHM = 3; uint256 constant DNSKEY_PUBKEY = 4; struct DNSKEY { uint16 flags; uint8 protocol; uint8 algorithm; bytes publicKey; } function readDNSKEY( bytes memory data, uint256 offset, uint256 length ) internal pure returns (DNSKEY memory self) { self.flags = data.readUint16(offset + DNSKEY_FLAGS); self.protocol = data.readUint8(offset + DNSKEY_PROTOCOL); self.algorithm = data.readUint8(offset + DNSKEY_ALGORITHM); self.publicKey = data.substring( offset + DNSKEY_PUBKEY, length - DNSKEY_PUBKEY ); } uint256 constant DS_KEY_TAG = 0; uint256 constant DS_ALGORITHM = 2; uint256 constant DS_DIGEST_TYPE = 3; uint256 constant DS_DIGEST = 4; struct DS { uint16 keytag; uint8 algorithm; uint8 digestType; bytes digest; } function readDS( bytes memory data, uint256 offset, uint256 length ) internal pure returns (DS memory self) { self.keytag = data.readUint16(offset + DS_KEY_TAG); self.algorithm = data.readUint8(offset + DS_ALGORITHM); self.digestType = data.readUint8(offset + DS_DIGEST_TYPE); self.digest = data.substring(offset + DS_DIGEST, length - DS_DIGEST); } function isSubdomainOf( bytes memory self, bytes memory other ) internal pure returns (bool) { uint256 off = 0; uint256 counts = labelCount(self, 0); uint256 othercounts = labelCount(other, 0); while (counts > othercounts) { off = progress(self, off); counts--; } return self.equals(off, other, 0); } function compareNames( bytes memory self, bytes memory other ) internal pure returns (int256) { if (self.equals(other)) { return 0; } uint256 off; uint256 otheroff; uint256 prevoff; uint256 otherprevoff; uint256 counts = labelCount(self, 0); uint256 othercounts = labelCount(other, 0); // Keep removing labels from the front of the name until both names are equal length while (counts > othercounts) { prevoff = off; off = progress(self, off); counts--; } while (othercounts > counts) { otherprevoff = otheroff; otheroff = progress(other, otheroff); othercounts--; } // Compare the last nonequal labels to each other while (counts > 0 && !self.equals(off, other, otheroff)) { prevoff = off; off = progress(self, off); otherprevoff = otheroff; otheroff = progress(other, otheroff); counts -= 1; } if (off == 0) { return -1; } if (otheroff == 0) { return 1; } return self.compare( prevoff + 1, self.readUint8(prevoff), other, otherprevoff + 1, other.readUint8(otherprevoff) ); } /// @dev Compares two serial numbers using RFC1982 serial number math. function serialNumberGte( uint32 i1, uint32 i2 ) internal pure returns (bool) { unchecked { return int32(i1) - int32(i2) >= 0; } } function progress( bytes memory body, uint256 off ) internal pure returns (uint256) { return off + 1 + body.readUint8(off); } /// @dev Computes the keytag for a chunk of data. /// @param data The data to compute a keytag for. /// @return The computed key tag. function computeKeytag(bytes memory data) internal pure returns (uint16) { /* This function probably deserves some explanation. * The DNSSEC keytag function is a checksum that relies on summing up individual bytes * from the input string, with some mild bitshifting. Here's a Naive solidity implementation: * * function computeKeytag(bytes memory data) internal pure returns (uint16) { * uint ac; * for (uint i = 0; i < data.length; i++) { * ac += i & 1 == 0 ? uint16(data.readUint8(i)) << 8 : data.readUint8(i); * } * return uint16(ac + (ac >> 16)); * } * * The EVM, with its 256 bit words, is exceedingly inefficient at doing byte-by-byte operations; * the code above, on reasonable length inputs, consumes over 100k gas. But we can make the EVM's * large words work in our favour. * * The code below works by treating the input as a series of 256 bit words. It first masks out * even and odd bytes from each input word, adding them to two separate accumulators `ac1` and `ac2`. * The bytes are separated by empty bytes, so as long as no individual sum exceeds 2^16-1, we're * effectively summing 16 different numbers with each EVM ADD opcode. * * Once it's added up all the inputs, it has to add all the 16 bit values in `ac1` and `ac2` together. * It does this using the same trick - mask out every other value, shift to align them, add them together. * After the first addition on both accumulators, there's enough room to add the two accumulators together, * and the remaining sums can be done just on ac1. */ unchecked { require(data.length <= 8192, "Long keys not permitted"); uint256 ac1; uint256 ac2; for (uint256 i = 0; i < data.length + 31; i += 32) { uint256 word; assembly { word := mload(add(add(data, 32), i)) } if (i + 32 > data.length) { uint256 unused = 256 - (data.length - i) * 8; word = (word >> unused) << unused; } ac1 += (word & 0xFF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00) >> 8; ac2 += (word & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF); } ac1 = (ac1 & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) + ((ac1 & 0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000) >> 16); ac2 = (ac2 & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) + ((ac2 & 0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000) >> 16); ac1 = (ac1 << 8) + ac2; ac1 = (ac1 & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) + ((ac1 & 0xFFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000) >> 32); ac1 = (ac1 & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) + ((ac1 & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF0000000000000000) >> 64); ac1 = (ac1 & 0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + (ac1 >> 128); ac1 += (ac1 >> 16) & 0xFFFF; return uint16(ac1); } } }