diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c
index a28f2ad5ef485dc5f6ebf8f79936366b6ab635b4..a24427d9cf6d225587c97a0da53fd97c71a541cd 100644
--- a/src/knot/ctl/remote.c
+++ b/src/knot/ctl/remote.c
@@ -100,7 +100,7 @@ static int remote_rdata_apply(server_t *s, remote_cmdargs_t* a, remote_zonef_t *
 
 		uint16_t rr_count = knot_rrset_rr_count(rr);
 		for (uint16_t i = 0; i < rr_count; i++) {
-			const knot_dname_t *dn = knot_rdata_ns_name(rr, i);
+			const knot_dname_t *dn = knot_rrs_ns_name(&rr->rrs, i);
 			rcu_read_lock();
 			zone = knot_zonedb_find(s->zone_db, dn);
 			if (cb(s, zone) != KNOT_EOK) {
@@ -235,13 +235,13 @@ static int remote_c_zonestatus(server_t *s, remote_cmdargs_t* a)
 		const zone_t *zone = knot_zonedb_iter_val(&it);
 
 		/* Fetch latest serial. */
-		const knot_rrset_t *soa_rrs = 0;
+		const knot_rrs_t *soa_rrs = NULL;
 		uint32_t serial = 0;
 		if (zone->contents) {
-			soa_rrs = knot_node_rrset(zone->contents->apex,
-			                          KNOT_RRTYPE_SOA);
+			soa_rrs = knot_node_rrs(zone->contents->apex,
+			                        KNOT_RRTYPE_SOA);
 			assert(soa_rrs != NULL);
-			serial = knot_rdata_soa_serial(soa_rrs);
+			serial = knot_rrs_soa_serial(soa_rrs);
 		}
 
 		/* Evalute zone type. */
@@ -538,7 +538,7 @@ static void log_command(const char *cmd, const remote_cmdargs_t* args)
 
 		uint16_t rr_count = knot_rrset_rr_count(rr);
 		for (uint16_t j = 0; j < rr_count; j++) {
-			const knot_dname_t *dn = knot_rdata_ns_name(rr, j);
+			const knot_dname_t *dn = knot_rrs_ns_name(&rr->rrs, j);
 			char *name = knot_dname_to_str(dn);
 
 			int ret = snprintf(params, rest, " %s", name);
diff --git a/src/knot/dnssec/nsec3-chain.c b/src/knot/dnssec/nsec3-chain.c
index 06bbfdadd94971554403584bb5d68a943f177783..39d444d9f6a6328b4c7960b3d7ce3ab74e4d62be 100644
--- a/src/knot/dnssec/nsec3-chain.c
+++ b/src/knot/dnssec/nsec3-chain.c
@@ -372,16 +372,16 @@ static int connect_nsec3_nodes(knot_node_t *a, knot_node_t *b,
 
 	assert(a->rrset_count == 1);
 
-	knot_rrset_t *a_rrset = knot_node_get_rrset(a, KNOT_RRTYPE_NSEC3);
-	assert(a_rrset);
-	uint8_t algorithm = knot_rdata_nsec3_algorithm(a_rrset, 0);
+	knot_rrs_t *a_rrs = knot_node_get_rrs(a, KNOT_RRTYPE_NSEC3);
+	assert(a_rrs);
+	uint8_t algorithm = knot_rrs_nsec3_algorithm(a_rrs, 0);
 	if (algorithm == 0) {
 		return KNOT_EINVAL;
 	}
 
 	uint8_t *raw_hash = NULL;
 	uint8_t raw_length = 0;
-	knot_rdata_nsec3_next_hashed(a_rrset, 0, &raw_hash, &raw_length);
+	knot_rrs_nsec3_next_hashed(a_rrs, 0, &raw_hash, &raw_length);
 	if (raw_hash == NULL) {
 		return KNOT_EINVAL;
 	}
diff --git a/src/knot/dnssec/zone-nsec.c b/src/knot/dnssec/zone-nsec.c
index 7ceada1cb2c64bd2b559eb6efa0d71b277162fc5..27ca82cdfe6793eb85fffc77e1e0ea661efb53dd 100644
--- a/src/knot/dnssec/zone-nsec.c
+++ b/src/knot/dnssec/zone-nsec.c
@@ -94,12 +94,12 @@ static bool get_zone_soa_min_ttl(const knot_zone_contents_t *zone,
 	assert(ttl);
 
 	knot_node_t *apex = zone->apex;
-	knot_rrset_t *soa = knot_node_get_rrset(apex, KNOT_RRTYPE_SOA);
+	const knot_rrs_t *soa = knot_node_rrs(apex, KNOT_RRTYPE_SOA);
 	if (!soa) {
 		return false;
 	}
 
-	uint32_t result =  knot_rdata_soa_minimum(soa);
+	uint32_t result =  knot_rrs_soa_minimum(soa);
 	if (result == 0) {
 		return false;
 	}
diff --git a/src/knot/dnssec/zone-sign.c b/src/knot/dnssec/zone-sign.c
index 3cbc1e16784220aa6e16f0fbe5d40acd98ac61e3..a9df6e1950843f390407b387b54fd51bb59af5ac 100644
--- a/src/knot/dnssec/zone-sign.c
+++ b/src/knot/dnssec/zone-sign.c
@@ -79,8 +79,8 @@ static bool valid_signature_exists(const knot_rrset_t *covered,
 
 	uint16_t rrsigs_rdata_count = knot_rrset_rr_count(rrsigs);
 	for (uint16_t i = 0; i < rrsigs_rdata_count; i++) {
-		uint16_t keytag = knot_rdata_rrsig_key_tag(rrsigs, i);
-		uint16_t type_covered = knot_rdata_rrsig_type_covered(rrsigs, i);
+		uint16_t keytag = knot_rrs_rrsig_key_tag(&rrsigs->rrs, i);
+		uint16_t type_covered = knot_rrs_rrsig_type_covered(&rrsigs->rrs, i);
 		if (keytag != key->keytag || type_covered != covered->type) {
 			continue;
 		}
@@ -163,7 +163,7 @@ static const knot_zone_key_t *get_matching_zone_key(const knot_rrset_t *rrsigs,
 	assert(rrsigs && rrsigs->type == KNOT_RRTYPE_RRSIG);
 	assert(keys);
 
-	uint16_t keytag = knot_rdata_rrsig_key_tag(rrsigs, pos);
+	uint16_t keytag = knot_rrs_rrsig_key_tag(&rrsigs->rrs, pos);
 
 	return knot_get_zone_key(keys, keytag);
 }
@@ -181,7 +181,7 @@ static void note_earliest_expiration(const knot_rrset_t *rrsigs, size_t pos,
 	assert(rrsigs);
 	assert(expires_at);
 
-	const uint32_t current = knot_rdata_rrsig_sig_expiration(rrsigs, pos);
+	const uint32_t current = knot_rrs_rrsig_sig_expiration(&rrsigs->rrs, pos);
 	if (current < *expires_at) {
 		*expires_at = current;
 	}
@@ -449,7 +449,7 @@ static int remove_standalone_rrsigs(const knot_node_t *node,
 
 	uint16_t rrsigs_rdata_count = knot_rrset_rr_count(rrsigs);
 	for (uint16_t i = 0; i < rrsigs_rdata_count; ++i) {
-		uint16_t type_covered = knot_rdata_rrsig_type_covered(rrsigs, i);
+		uint16_t type_covered = knot_rrs_rrsig_type_covered(&rrsigs->rrs, i);
 		if (!knot_node_rrset(node, type_covered)) {
 			knot_rrset_t *to_remove = knot_rrset_new_from(rrsigs, NULL);
 			if (to_remove == NULL) {
@@ -1329,7 +1329,7 @@ int knot_zone_sign_update_soa(const knot_rrset_t *soa,
 
 	dbg_dnssec_verb("Updating SOA...\n");
 
-	uint32_t serial = knot_rdata_soa_serial(soa);
+	uint32_t serial = knot_rrs_soa_serial(&soa->rrs);
 	if (serial == UINT32_MAX && policy->soa_up == KNOT_SOA_SERIAL_UPDATE) {
 		// TODO: this is wrong, the value should be 'rewound' to 0 in this case
 		return KNOT_EINVAL;
@@ -1370,7 +1370,7 @@ int knot_zone_sign_update_soa(const knot_rrset_t *soa,
 		return result;
 	}
 
-	knot_rdata_soa_serial_set(soa_to, new_serial);
+	knot_rrs_soa_serial_set(&soa_to->rrs, new_serial);
 
 	// add signatures for new SOA
 
diff --git a/src/knot/nameserver/internet.c b/src/knot/nameserver/internet.c
index dde203de8e7bdb23a74782b1718b797263b2b3dc..ba8653f7431c9b7ebf5bb9d92588680cd13916fb 100644
--- a/src/knot/nameserver/internet.c
+++ b/src/knot/nameserver/internet.c
@@ -71,7 +71,7 @@ static knot_rrset_t *dname_cname_synth(const knot_rrset_t *dname_rr,
 
 	/* Replace last labels of qname with DNAME. */
 	const knot_dname_t *dname_wire = knot_rrset_owner(dname_rr);
-	const knot_dname_t *dname_tgt = knot_rdata_dname_target(dname_rr);
+	const knot_dname_t *dname_tgt = knot_rrs_dname_target(&dname_rr->rrs);
 	int labels = knot_dname_labels(dname_wire, NULL);
 	knot_dname_t *cname = knot_dname_replace_suffix(qname, labels, dname_tgt);
 	if (cname == NULL) {
@@ -103,7 +103,7 @@ static bool dname_cname_cannot_synth(const knot_rrset_t *rrset, const knot_dname
 {
 	if (knot_dname_labels(qname, NULL)
 		- knot_dname_labels(knot_rrset_owner(rrset), NULL)
-		+ knot_dname_labels(knot_rdata_dname_target(rrset), NULL)
+		+ knot_dname_labels(knot_rrs_dname_target(&rrset->rrs), NULL)
 		> KNOT_DNAME_MAXLABELS) {
 		return true;
 	} else {
@@ -153,7 +153,7 @@ static int put_rrsig(const knot_dname_t *sig_owner, uint16_t type,
  */
 static int put_answer(knot_pkt_t *pkt, uint16_t type, struct query_data *qdata)
 {
-	const knot_rrset_t *rrset = NULL;
+	knot_rrset_t *rrset = NULL;
 
 	/* Wildcard expansion or exact match, either way RRSet owner is
 	 * is QNAME. We can fake name synthesis by setting compression hint to
@@ -190,7 +190,7 @@ static int put_answer(knot_pkt_t *pkt, uint16_t type, struct query_data *qdata)
 	default: /* Single RRSet of given type. */
 		rrset = knot_node_get_rrset(qdata->node, type);
 		if (rrset) {
-			const knot_rrset_t *rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
+			knot_rrset_t *rrsigs = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
 			ret = ns_put_rr(pkt, rrset, rrsigs, compr_hint, 0, qdata);
 		}
 		break;
@@ -217,9 +217,9 @@ static int put_authority_ns(knot_pkt_t *pkt, struct query_data *qdata)
 		return KNOT_EOK;
 	}
 
-	const knot_rrset_t *ns_rrset = knot_node_rrset(zone->apex, KNOT_RRTYPE_NS);
+	knot_rrset_t *ns_rrset = knot_node_get_rrset(zone->apex, KNOT_RRTYPE_NS);
 	if (ns_rrset) {
-		const knot_rrset_t *rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
+		knot_rrset_t *rrsigs = knot_node_get_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
 		return ns_put_rr(pkt, ns_rrset, rrsigs, COMPR_HINT_NONE,
 		                 KNOT_PF_NOTRUNC|KNOT_PF_CHECKDUP, qdata);
 	} else {
@@ -233,20 +233,20 @@ static int put_authority_soa(knot_pkt_t *pkt, struct query_data *qdata,
                              const knot_zone_contents_t *zone)
 {
 	dbg_ns("%s(%p, %p)\n", __func__, pkt, zone);
+	assert(knot_node_rrtype_exists(zone->apex, KNOT_RRTYPE_SOA));
 	knot_rrset_t *soa_rrset = knot_node_get_rrset(zone->apex, KNOT_RRTYPE_SOA);
-	assert(soa_rrset);
-	const knot_rrset_t *rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
+	knot_rrset_t *rrsigs = knot_node_get_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
 
 	// if SOA's TTL is larger than MINIMUM, copy the RRSet and set
 	// MINIMUM as TTL
 	int ret = KNOT_EOK;
 	uint32_t flags = KNOT_PF_NOTRUNC;
-	uint32_t min = knot_rdata_soa_minimum(soa_rrset);
+	uint32_t min = knot_rrs_soa_minimum(&soa_rrset->rrs);
 	if (min < knot_rrset_rr_ttl(soa_rrset, 0)) {
-		ret = knot_rrset_copy(soa_rrset, &soa_rrset, &pkt->mm);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
+//		ret = knot_rrset_copy(soa_rrset, &soa_rrset, &pkt->mm);
+//		if (ret != KNOT_EOK) {
+//			return ret;
+//		}
 
 		knot_rrset_rr_set_ttl(soa_rrset, 0, min);
 		flags |= KNOT_PF_FREE;
@@ -269,8 +269,8 @@ static int put_delegation(knot_pkt_t *pkt, struct query_data *qdata)
 	}
 
 	/* Insert NS record. */
-	const knot_rrset_t *rrset = knot_node_rrset(qdata->node, KNOT_RRTYPE_NS);
-	const knot_rrset_t *rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
+	knot_rrset_t *rrset = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_NS);
+	knot_rrset_t *rrsigs = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
 	return ns_put_rr(pkt, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata);
 }
 
@@ -287,7 +287,7 @@ static int put_additional(knot_pkt_t *pkt, const knot_rrset_t *rr,
 	uint32_t flags = KNOT_PF_NOTRUNC|KNOT_PF_CHECKDUP;
 	uint16_t hint = COMPR_HINT_NONE;
 	const knot_node_t *node = NULL;
-	const knot_rrset_t *additional = NULL;
+	knot_rrset_t *additional = NULL;
 
 	/* All RRs should have additional node cached or NULL. */
 	uint16_t rr_rdata_count = knot_rrset_rr_count(rr);
@@ -300,9 +300,9 @@ static int put_additional(knot_pkt_t *pkt, const knot_rrset_t *rr,
 			continue;
 		}
 		
-		const knot_rrset_t *rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
+		knot_rrset_t *rrsigs = knot_node_get_rrset(node, KNOT_RRTYPE_RRSIG);
 		for (int k = 0; k < ar_type_count; ++k) {
-			additional = knot_node_rrset(node, ar_type_list[k]);
+			additional = knot_node_get_rrset(node, ar_type_list[k]);
 			if (additional == NULL) {
 				continue;
 			}
@@ -323,7 +323,7 @@ static int follow_cname(knot_pkt_t *pkt, uint16_t rrtype, struct query_data *qda
 
 	const knot_node_t *cname_node = qdata->node;
 	knot_rrset_t *cname_rr = knot_node_get_rrset(qdata->node, rrtype);
-	const knot_rrset_t *rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
+	knot_rrset_t *rrsigs = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
 	int ret = KNOT_EOK;
 
 	assert(cname_rr != NULL);
@@ -383,7 +383,7 @@ static int follow_cname(knot_pkt_t *pkt, uint16_t rrtype, struct query_data *qda
 	}
 
 	/* Now follow the next CNAME TARGET. */
-	qdata->name = knot_rdata_cname_name(cname_rr);
+	qdata->name = knot_rrs_cname_name(&cname_rr->rrs);
 
 #ifdef KNOT_NS_DEBUG
 	char *cname_str = knot_dname_to_str(cname_node->owner);
@@ -658,8 +658,8 @@ static int solve_additional_dnssec(int state, knot_pkt_t *pkt, struct query_data
 	}
 }
 
-int ns_put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr,
-              const knot_rrset_t *rrsigs, uint16_t compr_hint,
+int ns_put_rr(knot_pkt_t *pkt, knot_rrset_t *rr,
+              knot_rrset_t *rrsigs, uint16_t compr_hint,
               uint32_t flags, struct query_data *qdata)
 {
 	/* RFC3123 s.6 - empty APL is valid, ignore other empty RRs. */
@@ -670,10 +670,12 @@ int ns_put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr,
 	}
 
 	// Copy all RRs for now TODO
+	knot_rrset_t *to_free = rr;
 	int ret = knot_rrset_copy(rr, (knot_rrset_t **)&rr, &pkt->mm);
 	if (ret != KNOT_EOK) {
 		return KNOT_ENOMEM;
 	}
+	knot_rrset_free(&to_free, NULL);
 	flags |= KNOT_PF_FREE;
 
 	/* Wildcard expansion applies only for answers. */
@@ -713,6 +715,7 @@ int ns_put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr,
 		knot_rrinfo_t *rrinfo = &pkt->rr_info[pkt->rrset_count - 1];
 		ret = put_rrsig(rr->owner, rr->type, rrsigs, rrinfo, qdata);
 	}
+	knot_rrset_free(&rrsigs, NULL);
 
 	return ret;
 }
diff --git a/src/knot/nameserver/internet.h b/src/knot/nameserver/internet.h
index d58f312b099e6101e3c27c0f1c8073669cd22bba..af362e523defeaf27ffbe244da76a6887ffbe01e 100644
--- a/src/knot/nameserver/internet.h
+++ b/src/knot/nameserver/internet.h
@@ -54,8 +54,8 @@ int internet_answer(knot_pkt_t *resp, struct query_data *qdata);
  *
  * \return KNOT_E*
  */
-int ns_put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr,
-              const knot_rrset_t *rrsigs, uint16_t compr_hint,
+int ns_put_rr(knot_pkt_t *pkt, knot_rrset_t *rr,
+              knot_rrset_t *rrsigs, uint16_t compr_hint,
               uint32_t flags, struct query_data *qdata);
 
 /*! \brief Require given QUERY TYPE or return error code. */
diff --git a/src/knot/nameserver/ixfr.c b/src/knot/nameserver/ixfr.c
index 83ffa1d9db42b53b55b26e33afc156d2da8ebc34..711459039b8c062ae52e86297ceb6538e05c5c46 100644
--- a/src/knot/nameserver/ixfr.c
+++ b/src/knot/nameserver/ixfr.c
@@ -130,10 +130,8 @@ static int ixfr_load_chsets(knot_changesets_t **chgsets, const zone_t *zone,
 	assert(zone);
 
 	/* Compare serials. */
-	const knot_node_t *apex = zone->contents->apex;
-	const knot_rrset_t *our_soa = knot_node_rrset(apex, KNOT_RRTYPE_SOA);
-	uint32_t serial_to = knot_rdata_soa_serial(our_soa);
-	uint32_t serial_from = knot_rdata_soa_serial(their_soa);
+	uint32_t serial_to = knot_zone_serial(zone->contents);
+	uint32_t serial_from = knot_rrs_soa_serial(&their_soa->rrs);
 	int ret = knot_serial_compare(serial_to, serial_from);
 	if (ret <= 0) { /* We have older/same age zone. */
 		return KNOT_EUPTODATE;
@@ -297,8 +295,8 @@ int ixfr_answer(knot_pkt_t *pkt, struct query_data *qdata)
 		case KNOT_EOK:      /* OK */
 			ixfr = (struct ixfr_proc*)qdata->ext;
 			IXFR_LOG(LOG_INFO, "Started (serial %u -> %u).",
-			         knot_rdata_soa_serial(ixfr->soa_from),
-			         knot_rdata_soa_serial(ixfr->soa_to));
+			         knot_rrs_soa_serial(&ixfr->soa_from->rrs),
+			         knot_rrs_soa_serial(&ixfr->soa_to->rrs));
 			break;
 		case KNOT_EUPTODATE: /* Our zone is same age/older, send SOA. */
 			IXFR_LOG(LOG_INFO, "Zone is up-to-date.");
@@ -385,20 +383,10 @@ int ixfr_process_answer(knot_pkt_t *pkt, knot_ns_xfr_t *xfr)
 		case XFRIN_RES_SOA_ONLY: {
 			// compare the SERIAL from the changeset with the zone's
 			// serial
-			const knot_node_t *apex = zone->contents->apex;
-			if (apex == NULL) {
-				return KNOT_ERROR;
-			}
-
-			const knot_rrset_t *zone_soa = knot_node_rrset(
-					apex, KNOT_RRTYPE_SOA);
-			if (zone_soa == NULL) {
-				return KNOT_ERROR;
-			}
-
+			uint32_t zone_serial = knot_zone_serial(zone->contents);
 			if (knot_serial_compare(
-			      knot_rdata_soa_serial(chgsets->first_soa),
-			      knot_rdata_soa_serial(zone_soa))
+			      knot_rrs_soa_serial(&chgsets->first_soa->rrs),
+			      zone_serial)
 			    > 0) {
 				if ((xfr->flags & XFR_FLAG_UDP) != 0) {
 					// IXFR over UDP
diff --git a/src/knot/nameserver/nsec_proofs.c b/src/knot/nameserver/nsec_proofs.c
index cdf53938a016433b812272c2bf5e33f0401c9217..ebb6fdfc3bcd1c4b4031cae28d65bfb0ff07485f 100644
--- a/src/knot/nameserver/nsec_proofs.c
+++ b/src/knot/nameserver/nsec_proofs.c
@@ -60,14 +60,14 @@ static int ns_put_nsec3_from_node(const knot_node_t *node,
                                   knot_pkt_t *resp)
 {
 	knot_rrset_t *rrset = knot_node_get_rrset(node, KNOT_RRTYPE_NSEC3);
-	const knot_rrset_t *rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
+	knot_rrset_t *rrsigs = knot_node_get_rrset(node, KNOT_RRTYPE_RRSIG);
 	if (rrset == NULL) {
 		// bad zone, ignore
 		return KNOT_EOK;
 	}
 
 	int res = KNOT_EOK;
-	if (knot_rrset_rr_count(rrset)) {
+	if (knot_rrset_rr_count(rrset) > 0) {
 		res = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE,
 		                KNOT_PF_CHECKDUP, qdata);
 	}
@@ -301,8 +301,8 @@ static int ns_put_nsec_wildcard(const knot_zone_contents_t *zone,
 		}
 	}
 
-	const knot_rrset_t *rrset = knot_node_rrset(previous, KNOT_RRTYPE_NSEC);
-	const knot_rrset_t *rrsigs = knot_node_rrset(previous, KNOT_RRTYPE_RRSIG);
+	knot_rrset_t *rrset = knot_node_get_rrset(previous, KNOT_RRTYPE_NSEC);
+	knot_rrset_t *rrsigs = knot_node_get_rrset(previous, KNOT_RRTYPE_RRSIG);
 
 	int ret = KNOT_EOK;
 
@@ -479,7 +479,7 @@ static int ns_put_nsec_nxdomain(const knot_dname_t *qname,
                                 knot_pkt_t *resp)
 {
 	knot_rrset_t *rrset = NULL;
-	const knot_rrset_t *rrsigs = NULL;
+	knot_rrset_t *rrsigs = NULL;
 
 	// check if we have previous; if not, find one using the tree
 	if (previous == NULL) {
@@ -713,7 +713,7 @@ static int ns_put_nsec_nsec3_nodata(const knot_node_t *node,
 		if ((rrset = knot_node_get_rrset(node, KNOT_RRTYPE_NSEC))
 		    != NULL) {
 			dbg_ns_detail("Putting the RRSet to Authority\n");
-			const knot_rrset_t *rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
+			knot_rrset_t *rrsigs = knot_node_get_rrset(node, KNOT_RRTYPE_RRSIG);
 			ret = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata);
 		}
 	}
@@ -768,7 +768,7 @@ int nsec_prove_dp_security(knot_pkt_t *pkt, struct query_data *qdata)
 	/* Add DS record if present. */
 	knot_rrset_t *rrset = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_DS);
 	if (rrset != NULL) {
-		const knot_rrset_t *rrsigs = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
+		knot_rrset_t *rrsigs = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
 		return ns_put_rr(pkt, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata);
 	}
 
diff --git a/src/knot/server/notify.c b/src/knot/server/notify.c
index 1102404428ab623edbc8ec1835db04a26699c051..f9667c59513e7eceb894b31a9b07217c97678503 100644
--- a/src/knot/server/notify.c
+++ b/src/knot/server/notify.c
@@ -95,7 +95,7 @@ int internet_notify(knot_pkt_t *pkt, struct query_data *qdata)
 	if (answer->count > 0) {
 		const knot_rrset_t *soa = answer->rr[0];
 		if (knot_rrset_type(soa) == KNOT_RRTYPE_SOA) {
-			serial = knot_rdata_soa_serial(soa);
+			serial = knot_rrs_soa_serial(&soa->rrs);
 			dbg_ns("%s: received serial %u\n", __func__, serial);
 		} else { /* Ignore */
 			dbg_ns("%s: NOTIFY answer != SOA_RR\n", __func__);
diff --git a/src/knot/server/zone-load.c b/src/knot/server/zone-load.c
index e34183fce4028f367773d04fad87c60ee45f7eee..7edc8455098a6e0ddc99bd1f2ebf203eac099e1b 100644
--- a/src/knot/server/zone-load.c
+++ b/src/knot/server/zone-load.c
@@ -197,10 +197,9 @@ static void log_zone_load_info(const zone_t *zone, const char *zone_name,
 
 	int64_t serial = 0;
 	if (zone->contents && zone->contents->apex) {
-		knot_rrset_t *soa;
-		soa = knot_node_get_rrset(zone->contents->apex, KNOT_RRTYPE_SOA);
-		serial = knot_rdata_soa_serial(soa);
-		knot_rrset_free(&soa, NULL);
+		const knot_rrs_t *soa = knot_node_rrs(zone->contents->apex,
+		                                      KNOT_RRTYPE_SOA);
+		serial = knot_rrs_soa_serial(soa);
 	}
 
 	log_zone_info("Zone '%s' %s (serial %" PRId64 ")\n", zone_name, action, serial);
diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c
index 9e022cd93dcac35de4d6d3653ad6a9e059e04193..233dd02f2ea1ba67dc9d4075d3a76d1d4513c184 100644
--- a/src/knot/server/zones.c
+++ b/src/knot/server/zones.c
@@ -64,7 +64,7 @@ static uint32_t zones_jitter(uint32_t interval)
  * \param rr_func RDATA specificator.
  * \return Timer in miliseconds.
  */
-static uint32_t zones_soa_timer(zone_t *zone, uint32_t (*rr_func)(const knot_rrset_t*))
+static uint32_t zones_soa_timer(zone_t *zone, uint32_t (*rr_func)(const knot_rrs_t*))
 {
 	if (!zone) {
 		dbg_zones_verb("zones: zones_soa_timer() called "
@@ -75,7 +75,7 @@ static uint32_t zones_soa_timer(zone_t *zone, uint32_t (*rr_func)(const knot_rrs
 	uint32_t ret = 0;
 
 	/* Retrieve SOA RDATA. */
-	const knot_rrset_t *soa_rrs = 0;
+	const knot_rrs_t *soa_rrs = NULL;
 
 	rcu_read_lock();
 
@@ -85,10 +85,9 @@ static uint32_t zones_soa_timer(zone_t *zone, uint32_t (*rr_func)(const knot_rrs
 		return 0;
 	}
 
-	soa_rrs = knot_node_rrset(zc->apex, KNOT_RRTYPE_SOA);
+	soa_rrs = knot_node_rrs(zc->apex, KNOT_RRTYPE_SOA);
 	assert(soa_rrs != NULL);
 	ret = rr_func(soa_rrs);
-	knot_rrset_free(&soa_rrs, NULL);
 
 	rcu_read_unlock();
 
@@ -104,7 +103,7 @@ static uint32_t zones_soa_timer(zone_t *zone, uint32_t (*rr_func)(const knot_rrs
  */
 static uint32_t zones_soa_refresh(zone_t *zone)
 {
-	return zones_soa_timer(zone, knot_rdata_soa_refresh);
+	return zones_soa_timer(zone, knot_rrs_soa_refresh);
 }
 
 /*!
@@ -115,7 +114,7 @@ static uint32_t zones_soa_refresh(zone_t *zone)
  */
 static uint32_t zones_soa_retry(zone_t *zone)
 {
-	return zones_soa_timer(zone, knot_rdata_soa_retry);
+	return zones_soa_timer(zone, knot_rrs_soa_retry);
 }
 
 /*!
@@ -126,7 +125,7 @@ static uint32_t zones_soa_retry(zone_t *zone)
  */
 static uint32_t zones_soa_expire(zone_t *zone)
 {
-	return zones_soa_timer(zone, knot_rdata_soa_expire);
+	return zones_soa_timer(zone, knot_rrs_soa_expire);
 }
 
 /*!
@@ -509,7 +508,7 @@ int zones_changesets_from_binary(knot_changesets_t *chgsets)
 		dbg_xfr_verb("xfr: reading RRSets to REMOVE, first RR is %hu\n",
 		             knot_rrset_type(rrset));
 		assert(knot_rrset_type(rrset) == KNOT_RRTYPE_SOA);
-		assert(chs->serial_from == knot_rdata_soa_serial(rrset));
+		assert(chs->serial_from == knot_rrs_soa_serial(&rrset->rrs));
 		knot_changeset_add_soa(chs, rrset, KNOT_CHANGESET_REMOVE);
 
 		/* Read remaining RRSets */
@@ -959,11 +958,11 @@ int zones_zonefile_sync(zone_t *zone, journal_t *journal)
 	}
 
 	/* Latest zone serial. */
-	const knot_rrset_t *soa_rrs = 0;
-	soa_rrs = knot_node_rrset(contents->apex, KNOT_RRTYPE_SOA);
+	const knot_rrs_t *soa_rrs = knot_node_rrs(contents->apex,
+	                                          KNOT_RRTYPE_SOA);
 	assert(soa_rrs != NULL);
 
-	int64_t serial_ret = knot_rdata_soa_serial(soa_rrs);
+	int64_t serial_ret = knot_rrs_soa_serial(soa_rrs);
 	if (serial_ret < 0) {
 		rcu_read_unlock();
 		pthread_mutex_unlock(&zone->lock);
@@ -1876,11 +1875,10 @@ int zones_journal_apply(zone_t *zone)
 	}
 
 	/* Fetch SOA serial. */
-	const knot_rrset_t *soa_rrs = 0;
-	soa_rrs = knot_node_rrset(contents->apex, KNOT_RRTYPE_SOA);
+	const knot_rrs_t *soa_rrs = 0;
+	soa_rrs = knot_node_rrs(contents->apex, KNOT_RRTYPE_SOA);
 	assert(soa_rrs != NULL);
-	int64_t serial_ret = knot_rdata_soa_serial(soa_rrs);
-	knot_rrset_free(&soa_rrs, NULL);
+	int64_t serial_ret = knot_rrs_soa_serial(soa_rrs);
 	if (serial_ret < 0) {
 		rcu_read_unlock();
 		return KNOT_EINVAL;
diff --git a/src/knot/updates/changesets.c b/src/knot/updates/changesets.c
index 23d21100bc84901668d135a0f7ce7bcf640ec272..27201e5536ccc65f465fed41c100d2de19612773 100644
--- a/src/knot/updates/changesets.c
+++ b/src/knot/updates/changesets.c
@@ -208,7 +208,7 @@ static void knot_changeset_store_soa(knot_rrset_t **chg_soa,
                                      uint32_t *chg_serial, knot_rrset_t *soa)
 {
 	*chg_soa = soa;
-	*chg_serial = knot_rdata_soa_serial(soa);
+	*chg_serial = knot_rrs_soa_serial(&soa->rrs);
 }
 
 void knot_changeset_add_soa(knot_changeset_t *changeset, knot_rrset_t *soa,
diff --git a/src/knot/updates/ddns.c b/src/knot/updates/ddns.c
index b12bb245e93c14aefe2b72c32e417d44b9cc7665..43a5fd91774090aeec466d7d14b744b68ab570f5 100644
--- a/src/knot/updates/ddns.c
+++ b/src/knot/updates/ddns.c
@@ -800,8 +800,8 @@ static int knot_ddns_process_add_soa(knot_node_t *node,
 		}
 
 		/* Check that the serial is indeed larger than the current one*/
-		assert(knot_serial_compare(knot_rdata_soa_serial(removed),
-		                         knot_rdata_soa_serial(rr)) < 0);
+		assert(knot_serial_compare(knot_rrs_soa_serial(&removed->rrs),
+		                           knot_rrs_soa_serial(&rr->rrs)) < 0);
 
 		/* 1) Store it to 'changes', together with its RRSIGs. */
 		ret = knot_changes_add_rrset(changes, removed, KNOT_CHANGES_OLD);
@@ -1645,11 +1645,9 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 	int ret = KNOT_EOK;
 
 	/* Copy base SOA RR. */
-	const knot_rrset_t *soa = knot_node_rrset(knot_zone_contents_apex(zone),
-						  KNOT_RRTYPE_SOA);
-	knot_rrset_t *soa_begin = NULL;
+	knot_rrset_t *soa_begin = knot_node_get_rrset(knot_zone_contents_apex(zone),
+	                                              KNOT_RRTYPE_SOA);
 	knot_rrset_t *soa_end = NULL;
-	ret = knot_rrset_copy(soa, &soa_begin, NULL);
 	if (ret == KNOT_EOK) {
 		knot_changeset_add_soa(changeset, soa_begin,
 		                       KNOT_CHANGESET_REMOVE);
@@ -1659,7 +1657,7 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 	}
 
 	/* Current SERIAL */
-	int64_t sn = knot_rdata_soa_serial(soa_begin);
+	int64_t sn = knot_rrs_soa_serial(&soa_begin->rrs);
 	int64_t sn_new;
 
 	/* Set the new serial according to policy. */
@@ -1710,7 +1708,7 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 		if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA
 		    && (knot_rrset_class(rr) == KNOT_CLASS_NONE
 		        || knot_rrset_class(rr) == KNOT_CLASS_ANY
-		        || knot_serial_compare(knot_rdata_soa_serial(rr),
+		        || knot_serial_compare(knot_rrs_soa_serial(&rr->rrs),
 		                               sn) <= 0)) {
 			// This ignores also SOA removals
 			dbg_ddns_verb("Ignoring SOA...\n");
@@ -1737,7 +1735,7 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 
 		// we need the RR copy, that's why this code is here
 		if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) {
-			int64_t sn_rr = knot_rdata_soa_serial(rr);
+			int64_t sn_rr = knot_rrs_soa_serial(&rr->rrs);
 			dbg_ddns_verb("Replacing SOA. Old serial: %"PRId64", "
 			              "new serial: %"PRId64"\n", sn_new, sn_rr);
 			assert(knot_serial_compare(sn_rr, sn) > 0);
@@ -1758,14 +1756,14 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 		}
 
 		/* If not set, create new SOA. */
-		ret = knot_rrset_copy(soa, &soa_end, NULL);
+		ret = knot_rrset_copy(soa_begin, &soa_end, NULL);
 		if (ret != KNOT_EOK) {
 			dbg_ddns("Failed to copy ending SOA: %s\n",
 			         knot_strerror(ret));
 			*rcode = KNOT_RCODE_SERVFAIL;
 			return ret;
 		}
-		knot_rdata_soa_serial_set(soa_end, sn_new);
+		knot_rrs_soa_serial_set(&soa_end->rrs, sn_new);
 
 		/* And replace it in the zone. */
 		ret = xfrin_replace_rrset_in_node(
diff --git a/src/knot/updates/xfr-in.c b/src/knot/updates/xfr-in.c
index 2ed9d06c1c5a7128c03693f33c4f73d538da0c24..28ee8b642f7bf912aedefdfe0a50c265b988fd30 100644
--- a/src/knot/updates/xfr-in.c
+++ b/src/knot/updates/xfr-in.c
@@ -63,10 +63,9 @@ int xfrin_transfer_needed(const knot_zone_contents_t *zone,
 	/*
 	 * Retrieve the local Serial
 	 */
-	const knot_rrset_t *soa_rrset =
-		knot_node_rrset(knot_zone_contents_apex(zone),
-				KNOT_RRTYPE_SOA);
-	if (soa_rrset == NULL) {
+	const knot_rrs_t *soa_rrs =
+		knot_node_rrs(knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA);
+	if (soa_rrs == NULL) {
 		char *name = knot_dname_to_str(knot_node_owner(
 				knot_zone_contents_apex(zone)));
 		dbg_xfrin("SOA RRSet missing in the zone %s!\n", name);
@@ -74,7 +73,7 @@ int xfrin_transfer_needed(const knot_zone_contents_t *zone,
 		return KNOT_ERROR;
 	}
 
-	int64_t local_serial = knot_rdata_soa_serial(soa_rrset);
+	int64_t local_serial = knot_rrs_soa_serial(soa_rrs);
 	if (local_serial < 0) {
 dbg_xfrin_exec(
 		char *name = knot_dname_to_str(knot_rrset_owner(soa_rrset));
@@ -94,7 +93,7 @@ dbg_xfrin_exec(
 		return KNOT_EMALF;
 	}
 
-	int64_t remote_serial = knot_rdata_soa_serial(answer->rr[0]);
+	int64_t remote_serial = knot_rrs_soa_serial(&answer->rr[0]->rrs);
 	if (remote_serial < 0) {
 		return KNOT_EMALF;	// maybe some other error
 	}
@@ -474,8 +473,8 @@ dbg_xfrin_exec_verb(
 				goto cleanup;
 			}
 
-			if (knot_rdata_soa_serial(rr)
-			    == knot_rdata_soa_serial((*chs)->first_soa)) {
+			if (knot_rrs_soa_serial(&rr->rrs)
+			    == knot_rrs_soa_serial(&(*chs)->first_soa->rrs)) {
 
 				/*! \note [TSIG] Check TSIG, we're at the end of
 				 *               transfer.
@@ -541,8 +540,8 @@ dbg_xfrin_exec_verb(
 			if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) {
 				log_zone_info("%s Serial %u -> %u.\n",
 					      xfr->msg,
-					      knot_rdata_soa_serial(chset->soa_from),
-					      knot_rdata_soa_serial(chset->soa_to));
+					      knot_rrs_soa_serial(&chset->soa_from->rrs),
+					      knot_rrs_soa_serial(&chset->soa_to->rrs));
 				state = -1;
 				continue;
 			} else {
@@ -1120,11 +1119,7 @@ dbg_xfrin_exec_verb(
 		is_nsec3 = 0;
 
 		// check if the RRSet belongs to the NSEC3 tree
-		if ((knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3)
-		    || (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG
-			&& knot_rdata_rrsig_type_covered(rr, 0)
-			    == KNOT_RRTYPE_NSEC3))
-		{
+		if (knot_rrset_is_nsec3rel(rr)) {
 			dbg_xfrin_verb("Removed RRSet belongs to NSEC3 tree.\n");
 			is_nsec3 = 1;
 		}
@@ -1197,11 +1192,7 @@ dbg_xfrin_exec_verb(
 		is_nsec3 = 0;
 
 		// check if the RRSet belongs to the NSEC3 tree
-		if ((knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3)
-		    || (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG
-			&& knot_rdata_rrsig_type_covered(rr, 0)
-			    == KNOT_RRTYPE_NSEC3))
-		{
+		if (knot_rrset_is_nsec3rel(rr)) {
 			dbg_xfrin_detail("This is NSEC3-related RRSet.\n");
 			is_nsec3 = 1;
 		}
@@ -1322,9 +1313,8 @@ static int xfrin_apply_changeset(knot_zone_contents_t *contents,
 		  chset->serial_from, chset->serial_to);
 
 	// check if serial matches
-	const knot_rrset_t *soa = knot_node_rrset(contents->apex,
-	                                          KNOT_RRTYPE_SOA);
-	if (soa == NULL || knot_rdata_soa_serial(soa)
+	const knot_rrs_t *soa = knot_node_rrs(contents->apex, KNOT_RRTYPE_SOA);
+	if (soa == NULL || knot_rrs_soa_serial(soa)
 			   != chset->serial_from) {
 		dbg_xfrin("SOA serials do not match!!\n");
 		return KNOT_ERROR;
diff --git a/src/knot/zone/node.c b/src/knot/zone/node.c
index 58b03eedf376e7319b94f95e8874e2f197a13b76..a0648731dccb3989b19cb7663bbd00b21e54903e 100644
--- a/src/knot/zone/node.c
+++ b/src/knot/zone/node.c
@@ -246,6 +246,26 @@ const knot_rrset_t *knot_node_rrset(const knot_node_t *node,
 	return knot_node_get_rrset(node, type);
 }
 
+const knot_rrs_t *knot_node_rrs(const knot_node_t *node, uint16_t type)
+{
+	return (const knot_rrs_t *)knot_node_get_rrs(node, type);
+}
+
+knot_rrs_t *knot_node_get_rrs(const knot_node_t *node, uint16_t type)
+{
+	if (node == NULL) {
+		return NULL;
+	}
+
+	for (uint16_t i = 0; i < node->rrset_count; ++i) {
+		if (node->rrs[i].type == type) {
+			return &node->rrs[i].rrs;
+		}
+	}
+
+	return NULL;
+}
+
 /*----------------------------------------------------------------------------*/
 
 knot_rrset_t *knot_node_get_rrset(const knot_node_t *node, uint16_t type)
@@ -745,22 +765,20 @@ bool knot_node_rrtype_is_signed(const knot_node_t *node, uint16_t type)
 		return false;
 	}
 
-	const knot_rrset_t *rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
+	const knot_rrs_t *rrsigs = knot_node_rrs(node, KNOT_RRTYPE_RRSIG);
 	if (rrsigs == NULL) {
 		return false;
 	}
 
-	uint16_t rrsigs_rdata_count = knot_rrset_rr_count(rrsigs);
+	uint16_t rrsigs_rdata_count = knot_rrs_rr_count(rrsigs);
 	for (uint16_t i = 0; i < rrsigs_rdata_count; ++i) {
 		const uint16_t type_covered =
-			knot_rdata_rrsig_type_covered(rrsigs, i);
+			knot_rrs_rrsig_type_covered(rrsigs, i);
 		if (type_covered == type) {
-			knot_rrset_free(&rrsigs, NULL);
 			return true;
 		}
 	}
 
-	knot_rrset_free(&rrsigs, NULL);
 	return false;
 }
 
@@ -778,3 +796,35 @@ bool knot_node_rrtype_exists(const knot_node_t *node, uint16_t type)
 
 	return false;
 }
+
+void knot_node_fill_rrset(const knot_node_t *node, uint16_t type,
+                          knot_rrset_t *rrset)
+{
+	if (node == NULL || rrset == NULL) {
+		return;
+	}
+	for (uint i = 0; i < node->rrset_count; ++i) {
+		if (node->rrs[i].type == type) {
+			rrset->owner = node->owner;
+			rrset->type = type;
+			rrset->rclass = KNOT_CLASS_IN;
+			rrset->rrs = node->rrs[i].rrs;
+			rrset->additional = NULL;
+		}
+	}
+}
+
+void knot_node_fill_rrset_pos(const knot_node_t *node, size_t pos,
+                              knot_rrset_t *rrset)
+{
+	if (node == NULL || pos >= node->rrset_count || rrset == NULL) {
+		return;
+	}
+	struct rr_data *rr_data = &node->rrs[pos];
+	rrset->owner = node->owner;
+	rrset->type = rr_data->type;
+	rrset->rclass = KNOT_CLASS_IN;
+	rrset->rrs = rr_data->rrs;
+	rrset->additional = NULL;
+}
+
diff --git a/src/knot/zone/node.h b/src/knot/zone/node.h
index 13ba18bebdd0b15c6b20fe6af59a22b8f5f7b65f..d783bafe3cb6d3fedc3f0345d056da1e44a30aab 100644
--- a/src/knot/zone/node.h
+++ b/src/knot/zone/node.h
@@ -157,6 +157,9 @@ int knot_node_add_rrset_no_merge(knot_node_t *node, knot_rrset_t *rrset);
 const knot_rrset_t *knot_node_rrset(const knot_node_t *node,
                                         uint16_t type);
 
+const knot_rrs_t *knot_node_rrs(const knot_node_t *node, uint16_t type);
+knot_rrs_t *knot_node_get_rrs(const knot_node_t *node, uint16_t type);
+
 /*!
  * \brief Returns the RRSet of the given type from the node (non-const version).
  *
@@ -415,6 +418,12 @@ bool knot_node_rrtype_is_signed(const knot_node_t *node, uint16_t type);
 
 bool knot_node_rrtype_exists(const knot_node_t *node, uint16_t type);
 
+void knot_node_fill_rrset(const knot_node_t *node, uint16_t type,
+                          knot_rrset_t *rrset);
+
+void knot_node_fill_rrset_pos(const knot_node_t *node, size_t pos,
+                              knot_rrset_t *rrset);
+
 #endif /* _KNOT_NODE_H_ */
 
 /*! @} */
diff --git a/src/knot/zone/semantic-check.c b/src/knot/zone/semantic-check.c
index f60d73fad65d79d91d4187b4b67e43f73cf5c6bf..8167262b1b8246c14fbbda29bcb9d63ad3adb579 100644
--- a/src/knot/zone/semantic-check.c
+++ b/src/knot/zone/semantic-check.c
@@ -327,7 +327,7 @@ static int check_dnskey_rdata(const knot_rrset_t *rrset, size_t rdata_pos)
  */
 static int check_rrsig_rdata(err_handler_t *handler,
                              const knot_node_t *node,
-                             const knot_rrset_t *rrsig,
+                             const knot_rrs_t *rrsig,
                              size_t rr_pos,
                              const knot_rrset_t *rrset,
                              const knot_rrset_t *dnskey_rrset)
@@ -341,7 +341,7 @@ static int check_rrsig_rdata(err_handler_t *handler,
 		return KNOT_ENOMEM;
 	}
 
-	if (knot_rdata_rrsig_type_covered(rrsig, 0) !=
+	if (knot_rrs_rrsig_type_covered(rrsig, 0) !=
 	    knot_rrset_type(rrset)) {
 		/* zoneparser would not let this happen
 		 * but to be on the safe side
@@ -352,7 +352,7 @@ static int check_rrsig_rdata(err_handler_t *handler,
 	}
 
 	/* label number at the 2nd index should be same as owner's */
-	uint8_t labels_rdata = knot_rdata_rrsig_labels(rrsig, rr_pos);
+	uint8_t labels_rdata = knot_rrs_rrsig_labels(rrsig, rr_pos);
 
 	int tmp = knot_dname_labels(knot_rrset_owner(rrset), NULL) - labels_rdata;
 
@@ -373,7 +373,7 @@ static int check_rrsig_rdata(err_handler_t *handler,
 
 	/* check original TTL */
 	uint32_t original_ttl =
-		knot_rdata_rrsig_original_ttl(rrsig, rr_pos);
+		knot_rrs_rrsig_original_ttl(rrsig, rr_pos);
 
 	uint16_t rr_count = knot_rrset_rr_count(rrset);
 	for (uint16_t i = 0; i < rr_count; ++i) {
@@ -385,7 +385,7 @@ static int check_rrsig_rdata(err_handler_t *handler,
 	}
 
 	/* Check for expired signature. */
-	if (knot_rdata_rrsig_sig_expiration(rrsig, rr_pos) < time(NULL)) {
+	if (knot_rrs_rrsig_sig_expiration(rrsig, rr_pos) < time(NULL)) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_RRSIG_RDATA_EXPIRATION,
 		                         info_str);
@@ -399,7 +399,7 @@ static int check_rrsig_rdata(err_handler_t *handler,
 
 	/* signer's name is same as in the zone apex */
 	const knot_dname_t *signer_name =
-		knot_rdata_rrsig_signer_name(rrsig, rr_pos);
+		knot_rrs_rrsig_signer_name(rrsig, rr_pos);
 
 	/* dnskey is in the apex node */
 	if (dnskey_rrset &&
@@ -415,12 +415,12 @@ static int check_rrsig_rdata(err_handler_t *handler,
 	 * before */
 	
 	int match = 0;
-	uint8_t rrsig_alg = knot_rdata_rrsig_algorithm(rrsig, rr_pos);
-	uint16_t key_tag_rrsig = knot_rdata_rrsig_key_tag(rrsig, rr_pos);
+	uint8_t rrsig_alg = knot_rrs_rrsig_algorithm(rrsig, rr_pos);
+	uint16_t key_tag_rrsig = knot_rrs_rrsig_key_tag(rrsig, rr_pos);
 	for (uint16_t i = 0; i < knot_rrset_rr_count(dnskey_rrset) &&
 	     !match; ++i) {
 		uint8_t dnskey_alg =
-			knot_rdata_dnskey_alg(dnskey_rrset, i);
+			knot_rrs_dnskey_alg(&dnskey_rrset->rrs, i);
 		if (rrsig_alg != dnskey_alg) {
 			continue;
 		}
@@ -479,18 +479,17 @@ static int check_rrsig_in_rrset(err_handler_t *handler,
 	if (ret < 0 || ret >= sizeof(info_str)) {
 		return KNOT_ENOMEM;
 	}
-	knot_rrset_t *rrsigs = NULL;
-	ret = knot_rrset_synth_rrsig(rrset->owner,
-	                             rrset->type,
-	                             knot_node_rrset(node, KNOT_RRTYPE_RRSIG),
-	                             &rrsigs, NULL);
+	knot_rrs_t rrsigs;
+	ret = knot_rrs_synth_rrsig(rrset->type,
+	                           knot_node_rrs(node, KNOT_RRTYPE_RRSIG),
+	                           &rrsigs, NULL);
 	if (ret != KNOT_EOK) {
 		if (ret != KNOT_ENOENT) {
 			return ret;
 		}
 	}
 
-	if (rrsigs == NULL) {
+	if (ret == KNOT_ENOENT) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_RRSIG_NO_RRSIG,
 		                         info_str);
@@ -504,37 +503,16 @@ static int check_rrsig_in_rrset(err_handler_t *handler,
 		                         info_str);
 		/* Safe to continue, nothing is malformed. */
 	}
-
-	/* Different owner, class, ttl */
-	if (knot_dname_cmp(knot_rrset_owner(rrset),
-				 knot_rrset_owner(rrsigs)) != 0) {
-		err_handler_handle_error(handler, node,
-		                         ZC_ERR_RRSIG_OWNER,
-		                         info_str);
-	}
-
-	if (knot_rrset_class(rrset) != knot_rrset_class(rrsigs)) {
-		err_handler_handle_error(handler, node,
-		                         ZC_ERR_RRSIG_CLASS,
-		                         info_str);
-	}
-
-	if (knot_rrset_rr_ttl(rrset, 0) != knot_rrset_rr_ttl(rrsigs, 0)) {
+	
+	const knot_rr_t *sig_rr = knot_rrs_rr(&rrsigs, 0);
+	if (knot_rrset_rr_ttl(rrset, 0) != knot_rr_ttl(sig_rr)) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_RRSIG_TTL,
 		                         info_str);
 	}
 
-	if (knot_rrset_rr_count(rrsigs) == 0) {
-		err_handler_handle_error(handler, node,
-		                         ZC_ERR_RRSIG_RDATA_SIGNED_WRONG,
-		                         info_str);
-		knot_rrset_free(&rrsigs, NULL);
-		return KNOT_EOK;
-	}
-	
-	for (uint16_t i = 0; i < knot_rrset_rr_count(rrsigs); ++i) {
-		int ret = check_rrsig_rdata(handler, node, rrsigs, i, rrset,
+	for (uint16_t i = 0; i < knot_rrs_rr_count(&rrsigs); ++i) {
+		int ret = check_rrsig_rdata(handler, node, &rrsigs, i, rrset,
 		                            dnskey_rrset);
 		if (ret != KNOT_EOK) {
 			dbg_semcheck("Could not check RRSIG properly (%s).\n",
@@ -543,7 +521,7 @@ static int check_rrsig_in_rrset(err_handler_t *handler,
 		}
 	}
 
-	knot_rrset_free(&rrsigs, NULL);
+	knot_rrs_clear(&rrsigs, NULL);
 	return ret;
 }
 
@@ -574,18 +552,17 @@ static int get_bit(uint8_t *bits, size_t index)
  * \retval KNOT_OK on success.
  * \retval KNOT_NOMEM on memory error.
  */
-static int rdata_nsec_to_type_array(const knot_rrset_t *rrset, size_t pos,
-				    uint16_t **array, size_t *count)
+static int rdata_nsec_to_type_array(const knot_rrs_t *rrs, uint16_t type,
+                                    size_t pos, uint16_t **array, size_t *count)
 {
 	assert(*array == NULL);
-	assert(rrset->type == KNOT_RRTYPE_NSEC || rrset->type == KNOT_RRTYPE_NSEC3);
 	
 	uint8_t *data = NULL;
 	uint16_t rr_bitmap_size = 0;
-	if (rrset->type == KNOT_RRTYPE_NSEC) {
-		knot_rdata_nsec_bitmap(rrset, &data, &rr_bitmap_size);
+	if (type == KNOT_RRTYPE_NSEC) {
+		knot_rrs_nsec_bitmap(rrs, &data, &rr_bitmap_size);
 	} else {
-		knot_rdata_nsec3_bitmap(rrset, pos, &data, &rr_bitmap_size);
+		knot_rrs_nsec3_bitmap(rrs, pos, &data, &rr_bitmap_size);
 	}
 	if (data == NULL) {
 		return KNOT_EMALF;
@@ -683,15 +660,14 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 
 			assert(nsec3_previous);
 
-			const knot_rrset_t *previous_rrset =
-				knot_node_rrset(nsec3_previous,
-						KNOT_RRTYPE_NSEC3);
+			const knot_rrs_t *previous_rrs =
+				knot_node_rrs(nsec3_previous, KNOT_RRTYPE_NSEC3);
 
-			assert(previous_rrset);
+			assert(previous_rrs);
 
 			/* check for Opt-Out flag */
 			uint8_t flags =
-				knot_rdata_nsec3_flags(previous_rrset, 0);
+				knot_rrs_nsec3_flags(previous_rrs, 0);
 			uint8_t opt_out_mask = 1;
 
 			if (!(flags & opt_out_mask)) {
@@ -704,32 +680,28 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 		}
 	}
 
-	const knot_rrset_t *nsec3_rrset =
-		knot_node_rrset(nsec3_node, KNOT_RRTYPE_NSEC3);
-
-	if (nsec3_rrset == NULL) {
+	const knot_rrs_t *nsec3_rrs = knot_node_rrs(nsec3_node,
+	                                            KNOT_RRTYPE_NSEC3);
+	if (nsec3_rrs == NULL) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_NSEC3_RDATA_CHAIN, NULL);
 		return KNOT_EOK;
 	}
 
-	const knot_rrset_t *soa_rrset =
-		knot_node_rrset(knot_zone_contents_apex(zone),
-	                        KNOT_RRTYPE_SOA);
-	assert(soa_rrset);
-	uint32_t minimum_ttl = knot_rdata_soa_minimum(soa_rrset);
-
-	if (knot_rrset_rr_ttl(nsec3_rrset, 0) != minimum_ttl) {
-			err_handler_handle_error(handler, node,
-						 ZC_ERR_NSEC3_RDATA_TTL, NULL);
+	const knot_rrs_t *soa_rrs = knot_node_rrs(zone->apex, KNOT_RRTYPE_SOA);
+	assert(soa_rrs);
+	uint32_t minimum_ttl = knot_rrs_soa_minimum(soa_rrs);
+	if (knot_rrs_rr_ttl(nsec3_rrs, 0) != minimum_ttl) {
+		err_handler_handle_error(handler, node,
+		                         ZC_ERR_NSEC3_RDATA_TTL, NULL);
 	}
 
 	/* Result is a dname, it can't be larger */
 	const knot_node_t *apex = knot_zone_contents_apex(zone);
 	uint8_t *next_dname_str = NULL;
 	uint8_t next_dname_size = 0;
-	knot_rdata_nsec3_next_hashed(nsec3_rrset, 0, &next_dname_str,
-	                             &next_dname_size);
+	knot_rrs_nsec3_next_hashed(nsec3_rrs, 0, &next_dname_str,
+	                           &next_dname_size);
 	knot_dname_t *next_dname = knot_nsec3_hash_to_dname(next_dname_str,
 	                                                    next_dname_size,
 	                                                    apex->owner);
@@ -747,11 +719,10 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 	size_t arr_size;
 	uint16_t *array = NULL;
 	/* TODO only works for one NSEC3 RR. */
-	int ret = rdata_nsec_to_type_array(nsec3_rrset, 0, &array, &arr_size);
+	int ret = rdata_nsec_to_type_array(nsec3_rrs,
+	                                   KNOT_RRTYPE_NSEC3, 0,
+	                                   &array, &arr_size);
 	if (ret != KNOT_EOK) {
-		dbg_semcheck("semchecks: check_nsec3_node: Could not "
-		             "convert NSEC to type array. Reason: %s\n",
-		             knot_strerror(ret));
 		return ret;
 	}
 	
@@ -763,7 +734,7 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 			continue;
 		}
 		
-		if (knot_node_rrset(node, type) == NULL) {
+		if (!knot_node_rrtype_exists(node, type)) {
 			err_handler_handle_error(handler, node,
 			                         ZC_ERR_NSEC3_RDATA_BITMAP,
 			                         NULL);
@@ -771,13 +742,8 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 	}
 	
 	/* Check that the node only contains NSEC3 and RRSIG. */
-	knot_rrset_t **rrsets = knot_node_rrsets(nsec3_node);
-	if (rrsets == NULL) {
-		return KNOT_ENOMEM;
-	}
-	
 	for (int i = 0; i < knot_node_rrset_count(nsec3_node); i++) {
-		uint16_t type = knot_rrset_type(rrsets[i]);
+		uint16_t type = nsec3_node->rrs[i].type;
 		if (!(type == KNOT_RRTYPE_NSEC3 ||
 		    type == KNOT_RRTYPE_RRSIG)) {
 			err_handler_handle_error(handler, nsec3_node,
@@ -786,7 +752,6 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 		}
 	}
 
-	knot_node_free_rrset_array(nsec3_node, rrsets);
 	free(array);
 
 	return KNOT_EOK;
@@ -847,28 +812,24 @@ static int sem_check_node_optional(const knot_zone_contents_t *zone,
                                    const knot_node_t *node,
                                    err_handler_t *handler)
 {
-	if (knot_node_is_deleg_point(node) || knot_zone_contents_apex(zone) ==
-	                node) {
-		const knot_rrset_t *ns_rrset =
-				knot_node_rrset(node, KNOT_RRTYPE_NS);
-		if (ns_rrset == NULL) {
-			err_handler_handle_error(handler, node,
-			                         ZC_ERR_MISSING_NS_DEL_POINT,
-			                         NULL);
-			return KNOT_EOK;
-		}
-
-		/* TODO How about multiple RRs? */
-		knot_dname_t *ns_dname =
-			knot_dname_copy(knot_rdata_ns_name(ns_rrset,
-		                             0));
-		if (ns_dname == NULL) {
-			return KNOT_ENOMEM;
-		}
+	if (!(knot_node_is_deleg_point(node) || knot_zone_contents_apex(zone) ==
+	                node)) {
+		return KNOT_EOK;
+	}
+	const knot_rrs_t *ns_rrs = knot_node_rrs(node, KNOT_RRTYPE_NS);
+	if (ns_rrs == NULL) {
+		err_handler_handle_error(handler, node,
+		                         ZC_ERR_MISSING_NS_DEL_POINT,
+		                         NULL);
+		return KNOT_EOK;
+	}
 
+	for (int i = 0; i < knot_rrs_rr_count(ns_rrs); ++i) {
+		const knot_dname_t *ns_dname =
+			knot_rrs_ns_name(ns_rrs, i);
 		const knot_node_t *glue_node =
-				knot_zone_contents_find_node(zone, ns_dname);
-		
+			knot_zone_contents_find_node(zone, ns_dname);
+	
 		if (knot_dname_is_sub(ns_dname,
 			      knot_node_owner(knot_zone_contents_apex(zone)))) {
 			if (glue_node == NULL) {
@@ -885,30 +846,20 @@ static int sem_check_node_optional(const knot_zone_contents_t *zone,
 					err_handler_handle_error(handler, node,
 							 ZC_ERR_GLUE_NODE,
 							NULL );
-				} else {
-					/* Look for A or AAAA. */
-					if ((knot_node_rrset(wildcard_node,
-					    KNOT_RRTYPE_A) == NULL) &&
-					    (knot_node_rrset(wildcard_node,
-					    KNOT_RRTYPE_AAAA) == NULL)) {
-						err_handler_handle_error(handler, node,
-								 ZC_ERR_GLUE_RECORD,
-								 NULL);
-					}
-				}	
-			} else {
-				if ((knot_node_rrset(glue_node,
-					       KNOT_RRTYPE_A) == NULL) &&
-				    (knot_node_rrset(glue_node,
-					       KNOT_RRTYPE_AAAA) == NULL)) {
-					err_handler_handle_error(handler, node,
-							 ZC_ERR_GLUE_RECORD,
-							 NULL);
+					// Cannot continue
+					return KNOT_EOK;
 				}
+				glue_node = wildcard_node;
+			}
+			if (!knot_node_rrtype_exists(glue_node, KNOT_RRTYPE_A) &&
+			    !knot_node_rrtype_exists(glue_node, KNOT_RRTYPE_AAAA)) {
+				err_handler_handle_error(handler, node,
+				                         ZC_ERR_GLUE_RECORD,
+				                         NULL);
 			}
 		}
-		knot_dname_free(&ns_dname);
 	}
+	
 	return KNOT_EOK;
 }
 
@@ -972,84 +923,74 @@ int sem_check_rrset(const knot_node_t *node,
  * \return Appropriate error code if error was found.
  */
 static int semantic_checks_dnssec(knot_zone_contents_t *zone,
-				  knot_node_t *node,
-				  knot_node_t **last_node,
-				  err_handler_t *handler,
-				  char nsec3)
+                                  knot_node_t *node,
+                                  knot_node_t **last_node,
+                                  err_handler_t *handler,
+                                  char nsec3)
 {
 	assert(handler);
 	assert(node);
-	char auth = !knot_node_is_non_auth(node);
-	char deleg = knot_node_is_deleg_point(node);
-	uint rrset_count = knot_node_rrset_count(node);
-	knot_rrset_t **rrsets = knot_node_rrsets(node);
+	bool auth = !knot_node_is_non_auth(node);
+	bool deleg = knot_node_is_deleg_point(node);
+	short rrset_count = knot_node_rrset_count(node);
 	const knot_rrset_t *dnskey_rrset =
 		knot_node_rrset(knot_zone_contents_apex(zone),
-				  KNOT_RRTYPE_DNSKEY);
+		                KNOT_RRTYPE_DNSKEY);
 
-	int ret = 0;
+	int ret = KNOT_EOK;
 
 	for (int i = 0; i < rrset_count; i++) {
-		const knot_rrset_t *rrset = rrsets[i];
-		if (auth && !deleg && rrset->type != KNOT_RRTYPE_RRSIG &&
+		knot_rrset_t rrset;
+		knot_node_fill_rrset_pos(node, i, &rrset);
+		if (auth && !deleg && rrset.type != KNOT_RRTYPE_RRSIG &&
 		    (ret = check_rrsig_in_rrset(handler, node,
-		                                rrset, dnskey_rrset)) != 0) {
+		                                &rrset, dnskey_rrset)) != 0) {
 			err_handler_handle_error(handler, node, ret, NULL);
 		}
 
 		if (!nsec3 && auth) {
 			/* check for NSEC record */
-			const knot_rrset_t *nsec_rrset =
-					knot_node_rrset(node,
-							  KNOT_RRTYPE_NSEC);
-
-			if (nsec_rrset == NULL) {
+			const knot_rrs_t *nsec_rrs =
+				knot_node_rrs(node, KNOT_RRTYPE_NSEC);
+			if (nsec_rrs == NULL) {
 				err_handler_handle_error(handler, node,
-							 ZC_ERR_NO_NSEC, NULL);
-			} else {
-				/* check NSEC/NSEC3 bitmap */
-				size_t count;
-				uint16_t *array = NULL;
-				
-				int ret = rdata_nsec_to_type_array(nsec_rrset,
-				                                   0,
-				                                   &array,
-				                                   &count);
-				if (ret != KNOT_EOK) {
-					dbg_semcheck("semchecks: "
-					             "Could not create type "
-					             "array. Reason: %s.\n",
-					             knot_strerror(ret));
-					knot_node_free_rrset_array(node, rrsets);
-					return ret;
-				}
+				                         ZC_ERR_NO_NSEC, NULL);
+				return KNOT_EOK;
+			}
+		
+			/* check NSEC/NSEC3 bitmap */
+			size_t count;
+			uint16_t *array = NULL;
+			int ret = rdata_nsec_to_type_array(nsec_rrs,
+			                                   KNOT_RRTYPE_NSEC,
+			                                   0,
+			                                   &array,
+			                                   &count);
+			if (ret != KNOT_EOK) {
+				return ret;
+			}
 
-				uint16_t type = 0;
-				for (int j = 0; j < count; j++) {
-					/* test for each type's presence */
-					type = array[j];
-					if (type == KNOT_RRTYPE_RRSIG) {
-						continue;
-					}
-					if (knot_node_rrset(node,
-							      type) == NULL) {
-					err_handler_handle_error(
-						handler,
-						node,
-						ZC_ERR_NSEC_RDATA_BITMAP, NULL);
-					}
+			uint16_t type = 0;
+			for (int j = 0; j < count; j++) {
+				/* test for each type's presence */
+				type = array[j];
+				if (type == KNOT_RRTYPE_RRSIG) {
+					continue;
+				}
+				if (!knot_node_rrtype_exists(node, type)) {
+					err_handler_handle_error(handler,
+					                         node,
+					                         ZC_ERR_NSEC_RDATA_BITMAP,
+					                         NULL);
 				}
-				free(array);
 			}
-
-			/* Test that only one record is in the
-				 * NSEC RRSet */
-
-			if (knot_rrset_rr_count(nsec_rrset) != 1) {
+			free(array);
+			/* Test that only one record is in the NSEC RRSet */
+			if (knot_rrs_rr_count(nsec_rrs) != 1) {
 				err_handler_handle_error(handler,
-						 node,
-						 ZC_ERR_NSEC_RDATA_MULTIPLE,
-				                NULL);
+				                         node,
+				                         ZC_ERR_NSEC_RDATA_MULTIPLE,
+				                         NULL);
 			}
 
 			/*
@@ -1059,31 +1000,26 @@ static int semantic_checks_dnssec(knot_zone_contents_t *zone,
 			 * so checking should only be matter of testing
 			 * the next link in each node.
 			 */
+			const knot_dname_t *next_domain =
+				knot_rrs_nsec_next(nsec_rrs);
+			// Convert name to lowercase for trie lookup
+			knot_dname_t *lowercase = knot_dname_copy(next_domain);
+			if (lowercase == NULL) {
+				return KNOT_ENOMEM;
+			}
+			knot_dname_to_lower(lowercase);
 
-			if (nsec_rrset != NULL) {
-				const knot_dname_t *next_domain =
-					knot_rdata_nsec_next(nsec_rrset);
-				assert(next_domain);
-				// Convert name to lowercase for trie lookup
-				knot_dname_t *lowercase = knot_dname_copy(next_domain);
-				if (lowercase == NULL) {
-					return KNOT_ENOMEM;
-				}
-				knot_dname_to_lower(lowercase);
-
-				if (knot_zone_contents_find_node(zone, lowercase) == NULL) {
-					err_handler_handle_error(handler, node,
-						ZC_ERR_NSEC_RDATA_CHAIN, NULL);
-				}
+			if (knot_zone_contents_find_node(zone, lowercase) == NULL) {
+				err_handler_handle_error(handler, node,
+				                         ZC_ERR_NSEC_RDATA_CHAIN,
+				                         NULL);
+			}
 
-				if (knot_dname_cmp(lowercase,
-				    knot_node_owner(knot_zone_contents_apex(zone)))
-					== 0) {
-					/* saving the last node */
-					*last_node = node;
-				}
-				knot_dname_free(&lowercase);
+			if (knot_dname_is_equal(lowercase, zone->apex->owner)) {
+				/* saving the last node */
+				*last_node = node;
 			}
+			knot_dname_free(&lowercase);
 		} else if (nsec3 && (auth || deleg)) { /* nsec3 */
 			int ret = check_nsec3_node_in_zone(zone, node,
 			                                   handler);
@@ -1092,14 +1028,11 @@ static int semantic_checks_dnssec(knot_zone_contents_t *zone,
 				              "Checking of NSEC3 node "
 				              "failed. Reason: %s.\n",
 				              knot_strerror(ret));
-				knot_node_free_rrset_array(node, rrsets);
 				return ret;
 			}
 		}
 	}
 
-	knot_node_free_rrset_array(node, rrsets);
-
 	return KNOT_EOK;
 }
 
@@ -1185,15 +1118,14 @@ void log_cyclic_errors_in_zone(err_handler_t *handler,
                                const knot_node_t *last_nsec3_node,
                                char do_checks)
 {
-	if (do_checks == 3) {
+	if (do_checks == SEM_CHECK_NSEC3) {
 		/* Each NSEC3 node should only contain one RRSET. */
 		if (last_nsec3_node == NULL || first_nsec3_node == NULL) {
 			return;
 		}
-		const knot_rrset_t *nsec3_rrset =
-			knot_node_rrset(last_nsec3_node,
-		                              KNOT_RRTYPE_NSEC3);
-		if (nsec3_rrset == NULL) {
+		const knot_rrs_t *nsec3_rrs =
+			knot_node_rrs(last_nsec3_node, KNOT_RRTYPE_NSEC3);
+		if (nsec3_rrs == NULL) {
 			err_handler_handle_error(handler, last_nsec3_node,
 						 ZC_ERR_NSEC3_RDATA_CHAIN, NULL);
 			return;
@@ -1203,8 +1135,8 @@ void log_cyclic_errors_in_zone(err_handler_t *handler,
 		const knot_node_t *apex = knot_zone_contents_apex(zone);
 		uint8_t *next_dname_str = NULL;
 		uint8_t next_dname_size = 0;
-		knot_rdata_nsec3_next_hashed(nsec3_rrset, 0, &next_dname_str,
-		                             &next_dname_size);
+		knot_rrs_nsec3_next_hashed(nsec3_rrs, 0, &next_dname_str,
+		                           &next_dname_size);
 		knot_dname_t *next_dname = knot_nsec3_hash_to_dname(next_dname_str,
 		                                                    next_dname_size,
 		                                                    apex->owner);
@@ -1237,18 +1169,16 @@ void log_cyclic_errors_in_zone(err_handler_t *handler,
 				ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC, NULL);
 				return;
 		} else {
-			const knot_rrset_t *nsec_rrset =
-				knot_node_rrset(last_node,
-						  KNOT_RRTYPE_NSEC);
+			const knot_rrs_t *nsec_rrs =
+				knot_node_rrs(last_node, KNOT_RRTYPE_NSEC);
 
-			if (nsec_rrset == NULL) {
+			if (nsec_rrs == NULL) {
 				err_handler_handle_error(handler, last_node,
 					 ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC, NULL);
 				return;
 			}
 
-			const knot_dname_t *next_dname =
-				knot_rdata_nsec_next(nsec_rrset);
+			const knot_dname_t *next_dname = knot_rrs_nsec_next(nsec_rrs);
 			assert(next_dname);
 
 			const knot_dname_t *apex_dname =
diff --git a/src/knot/zone/zone-contents.c b/src/knot/zone/zone-contents.c
index 47b65079777206ddbc48614c47f8780854eab14d..240284af997a8c2ded5282ae89054a6416daa32e 100644
--- a/src/knot/zone/zone-contents.c
+++ b/src/knot/zone/zone-contents.c
@@ -169,9 +169,7 @@ static int discover_additionals(struct rr_data *rr_data,
 
 	for (uint16_t i = 0; i < rdcount; i++) {
 		/* Try to find node for the dname in the RDATA. */
-		// TODO wrapper RRSET, remove
-		knot_rrset_t rrset = {.rrs = *rrs, .type = rr_data->type};
-		dname = knot_rdata_name(&rrset, i);
+		dname = knot_rrs_name(rrs, i, rr_data->type);
 		knot_zone_contents_find_dname(zone, dname, &node, &encloser, &prev);
 		if (node == NULL && encloser && encloser->wildcard_child) {
 			node = encloser->wildcard_child;
@@ -384,27 +382,27 @@ static int knot_zone_contents_find_in_tree(knot_zone_tree_t *tree,
 
 /*----------------------------------------------------------------------------*/
 
-static int knot_zc_nsec3_parameters_match(const knot_rrset_t *rrset,
+static int knot_zc_nsec3_parameters_match(const knot_rrs_t *rrs,
                                           const knot_nsec3_params_t *params,
                                           size_t rdata_pos)
 {
-	assert(rrset != NULL && params != NULL);
+	assert(rrs != NULL && params != NULL);
 
 	dbg_zone_detail("RDATA algo: %u, iterations: %u, salt length: %u, salt:"
 			" %.*s\n",
-			knot_rdata_nsec3_algorithm(rrset, rdata_pos),
-			knot_rdata_nsec3_iterations(rrset, rdata_pos),
-			knot_rdata_nsec3_salt_length(rrset, rdata_pos),
-			knot_rdata_nsec3_salt_length(rrset, rdata_pos),
-			knot_rdata_nsec3_salt(rrset, rdata_pos));
+			knot_rrs_nsec3_algorithm(rrs, rdata_pos),
+			knot_rrs_nsec3_iterations(rrs, rdata_pos),
+			knot_rrs_nsec3_salt_length(rrs, rdata_pos),
+			knot_rrs_nsec3_salt_length(rrs, rdata_pos),
+			knot_rrs_nsec3_salt(rrs, rdata_pos));
 	dbg_zone_detail("NSEC3PARAM algo: %u, iterations: %u, salt length: %u, "
 			"salt: %.*s\n",  params->algorithm, params->iterations,
 			params->salt_length, params->salt_length, params->salt);
 
-	return (knot_rdata_nsec3_algorithm(rrset, rdata_pos) == params->algorithm
-		&& knot_rdata_nsec3_iterations(rrset, rdata_pos) == params->iterations
-		&& knot_rdata_nsec3_salt_length(rrset, rdata_pos) == params->salt_length
-		&& strncmp((const char *)knot_rdata_nsec3_salt(rrset, rdata_pos),
+	return (knot_rrs_nsec3_algorithm(rrs, rdata_pos) == params->algorithm
+		&& knot_rrs_nsec3_iterations(rrs, rdata_pos) == params->iterations
+		&& knot_rrs_nsec3_salt_length(rrs, rdata_pos) == params->salt_length
+		&& strncmp((const char *)knot_rrs_nsec3_salt(rrs, rdata_pos),
 			   (const char *)params->salt, params->salt_length)
 		   == 0);
 }
@@ -1058,13 +1056,6 @@ dbg_zone_exec_detail(
 );
 	*nsec3_node = found;
 
-	// This check cannot be used now, the function returns proper return
-	// value if the node was not found
-//	if (*nsec3_node == NULL) {
-//		// there is no NSEC3 node even if there should be
-//		return KNOT_ENSEC3CHAIN;
-//	}
-
 	if (prev == NULL) {
 		// either the returned node is the root of the tree, or it is
 		// the leftmost node in the tree; in both cases node was found
@@ -1082,20 +1073,20 @@ dbg_zone_exec_detail(
 	 * from the right chain. Check iterations, hash algorithm and salt
 	 * values and compare them to the ones from NSEC3PARAM.
 	 */
-	const knot_rrset_t *nsec3_rrset = knot_node_rrset(*nsec3_previous,
-							  KNOT_RRTYPE_NSEC3);
-	assert(nsec3_rrset);
+	const knot_rrs_t *nsec3_rrs =
+		knot_node_rrs(*nsec3_previous, KNOT_RRTYPE_NSEC3);
+	assert(nsec3_rrs);
 	const knot_node_t *original_prev = *nsec3_previous;
 
 	int match = 0;
 
-	while (nsec3_rrset && !match) {
+	while (nsec3_rrs && !match) {
 		for (uint16_t i = 0;
-		     i < knot_rrset_rr_count(nsec3_rrset) && !match;
+		     i < nsec3_rrs->rr_count && !match;
 		     i++) {
-			if (knot_zc_nsec3_parameters_match(nsec3_rrset,
-							    &zone->nsec3_params,
-							    i)) {
+			if (knot_zc_nsec3_parameters_match(nsec3_rrs,
+			                                   &zone->nsec3_params,
+			                                   i)) {
 				/* Matching NSEC3PARAM match at position nr.: i. */
 				match = 1;
 			}
@@ -1107,8 +1098,7 @@ dbg_zone_exec_detail(
 
 		/* This RRSET was not a match, try the one from previous node. */
 		*nsec3_previous = knot_node_previous(*nsec3_previous);
-		nsec3_rrset = knot_node_rrset(*nsec3_previous,
-					      KNOT_RRTYPE_NSEC3);
+		nsec3_rrs = knot_node_rrs(*nsec3_previous, KNOT_RRTYPE_NSEC3);
 		dbg_zone_exec_detail(
 		char *name = (*nsec3_previous)
 				? knot_dname_to_str(
@@ -1120,7 +1110,7 @@ dbg_zone_exec_detail(
 			free(name);
 		}
 );
-		if (*nsec3_previous == original_prev || nsec3_rrset == NULL) {
+		if (*nsec3_previous == original_prev || nsec3_rrs == NULL) {
 			// cycle
 			*nsec3_previous = NULL;
 			break;
@@ -1304,22 +1294,18 @@ int knot_zone_contents_load_nsec3param(knot_zone_contents_t *zone)
 		return KNOT_EINVAL;
 	}
 
-	const knot_rrset_t *rrset = knot_node_rrset(zone->apex,
-						    KNOT_RRTYPE_NSEC3PARAM);
-
-	if (rrset != NULL) {
-		int r = knot_nsec3_params_from_wire(&zone->nsec3_params, rrset);
+	const knot_rrs_t *rrs = knot_node_rrs(zone->apex, KNOT_RRTYPE_NSEC3PARAM);
+	if (rrs!= NULL) {
+		int r = knot_nsec3_params_from_wire(&zone->nsec3_params, rrs);
 		if (r != KNOT_EOK) {
 			dbg_zone("Failed to load NSEC3PARAM (%s).\n",
 			         knot_strerror(r));
-			knot_rrset_free(&rrset, NULL);
 			return r;
 		}
 	} else {
 		memset(&zone->nsec3_params, 0, sizeof(knot_nsec3_params_t));
 	}
 
-	knot_rrset_free(&rrset, NULL);
 	return KNOT_EOK;
 }
 
@@ -1475,10 +1461,9 @@ void knot_zone_contents_deep_free(knot_zone_contents_t **contents)
 uint32_t knot_zone_serial(const knot_zone_contents_t *zone)
 {
 	if (!zone) return 0;
-	const knot_rrset_t *soa = NULL;
-	soa = knot_node_rrset(knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA);
-	uint32_t serial = knot_rdata_soa_serial(soa);
-	knot_rrset_free(&soa, NULL);
+	const knot_rrs_t *soa = NULL;
+	soa = knot_node_rrs(knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA);
+	uint32_t serial = knot_rrs_soa_serial(soa);
 	return serial;
 }
 
diff --git a/src/knot/zone/zone-diff.c b/src/knot/zone/zone-diff.c
index 8effef15d0e07c1b1187f6ec2b658d71ecc95207..672d51a82473b726d66a9fe1f4fd06cfae23ec75 100644
--- a/src/knot/zone/zone-diff.c
+++ b/src/knot/zone/zone-diff.c
@@ -46,74 +46,41 @@ static int knot_zone_diff_load_soas(const knot_zone_contents_t *zone1,
 	const knot_node_t *apex1 = knot_zone_contents_apex(zone1);
 	const knot_node_t *apex2 = knot_zone_contents_apex(zone2);
 	if (apex1 == NULL || apex2 == NULL) {
-		dbg_zonediff("zone_diff: "
-		             "both zones must have apex nodes.\n");
 		return KNOT_EINVAL;
 	}
 
 	knot_rrset_t *soa_rrset1 = knot_node_get_rrset(apex1, KNOT_RRTYPE_SOA);
 	knot_rrset_t *soa_rrset2 = knot_node_get_rrset(apex2, KNOT_RRTYPE_SOA);
 	if (soa_rrset1 == NULL || soa_rrset2 == NULL) {
-		dbg_zonediff("zone_diff: "
-		             "both zones must have apex nodes.\n");
 		return KNOT_EINVAL;
 	}
 
 	if (knot_rrset_rr_count(soa_rrset1) == 0 ||
 	    knot_rrset_rr_count(soa_rrset2) == 0) {
-		dbg_zonediff("zone_diff: "
-		             "both zones must have apex nodes with SOA "
-		             "RRs.\n");
 		return KNOT_EINVAL;
 	}
 
-	int64_t soa_serial1 =
-		knot_rdata_soa_serial(soa_rrset1);
-	if (soa_serial1 == -1) {
-		dbg_zonediff("zone_diff: load_soas: Got bad SOA.\n");
-	}
-
-	int64_t soa_serial2 =
-		knot_rdata_soa_serial(soa_rrset2);
-	if (soa_serial2 == -1) {
-		dbg_zonediff("zone_diff: load_soas: Got bad SOA.\n");
-	}
+	int64_t soa_serial1 = knot_rrs_soa_serial(&soa_rrset1->rrs);
+	int64_t soa_serial2 = knot_rrs_soa_serial(&soa_rrset2->rrs);
 
 	if (knot_serial_compare(soa_serial1, soa_serial2) == 0) {
-		dbg_zonediff("zone_diff: "
-		             "second zone must have higher serial than the "
-		             "first one. (%"PRId64" vs. %"PRId64")\n",
-		             soa_serial1, soa_serial2);
 		return KNOT_ENODIFF;
 	}
 
 	if (knot_serial_compare(soa_serial1, soa_serial2) > 0) {
-		dbg_zonediff("zone_diff: "
-		             "second zone must have higher serial than the "
-		             "first one. (%"PRId64" vs. %"PRId64")\n",
-		             soa_serial1, soa_serial2);
 		return KNOT_ERANGE;
 	}
 
 	assert(changeset);
 
-	int ret = knot_rrset_copy(soa_rrset1, &changeset->soa_from, NULL);
-	if (ret != KNOT_EOK) {
-		dbg_zonediff("zone_diff: load_soas: Cannot copy RRSet.\n");
-		return ret;
-	}
-
-	ret = knot_rrset_copy(soa_rrset2, &changeset->soa_to, NULL);
-	if (ret != KNOT_EOK) {
-		dbg_zonediff("zone_diff: load_soas: Cannot copy RRSet.\n");
-		return ret;
-	}
+	changeset->soa_from = soa_rrset1;
+	changeset->soa_to = soa_rrset2;
 
 	changeset->serial_from = soa_serial1;
 	changeset->serial_to = soa_serial2;
 
 	dbg_zonediff_verb("zone_diff: load_soas: SOAs diffed. (%"PRId64" -> %"PRId64")\n",
-	            soa_serial1, soa_serial2);
+	                  soa_serial1, soa_serial2);
 
 	return KNOT_EOK;
 }
@@ -212,7 +179,7 @@ static int knot_zone_diff_add_node(const knot_node_t *node,
 	for (uint i = 0; i < knot_node_rrset_count(node); i++) {
 		assert(rrsets[i]);
 		int ret = knot_zone_diff_changeset_add_rrset(changeset,
-		                                         rrsets[i]);
+		                                             rrsets[i]);
 		if (ret != KNOT_EOK) {
 			dbg_zonediff("zone_diff: add_node: Cannot add RRSet (%s).\n",
 			       knot_strerror(ret));
@@ -221,7 +188,7 @@ static int knot_zone_diff_add_node(const knot_node_t *node,
 		}
 	}
 
-	free(rrsets);
+	knot_node_free_rrset_array(node, rrsets);
 
 	return KNOT_EOK;
 }
@@ -259,7 +226,7 @@ static int knot_zone_diff_remove_node(knot_changeset_t *changeset,
 		}
 	}
 
-	free(rrsets);
+	knot_node_free_rrset_array(node, rrsets);
 
 	return KNOT_EOK;
 }
@@ -508,41 +475,14 @@ static int knot_zone_diff_node(knot_node_t **node_ptr, void *data)
 				free(rrsets);
 				return ret;
 			}
-
-//			dbg_zonediff_verb("zone_diff: diff_node: Changes in "
-//			            "RRSIGs.\n");
-//			/*! \todo There is ad-hoc solution in the function, maybe handle here. */
-//			ret = knot_zone_diff_rrsets(rrset->rrsigs,
-//			                                rrset_from_second_node->rrsigs,
-//			                                param->changeset);
-//			if (ret != KNOT_EOK) {
-//				dbg_zonediff("zone_diff: "
-//				             "Failed to diff RRSIGs.\n");
-//				return ret;
-//			}
 		}
 	}
 
-	free(rrsets);
+	knot_node_free_rrset_array(node, rrsets);
 
 	/*! \todo move to one function with the code above. */
 	rrsets = knot_node_rrsets(node_in_second_tree);
 	if (rrsets == NULL) {
-		dbg_zonediff("zone_diff: Node in second tree has no RRSets.\n");
-		/*
-		 * This can happen when node in second
-		 * tree is empty non-terminal and as such has no RRs.
-		 * Whole node from the first tree has to be removed.
-		 */
-		// TODO following code creates duplicated RR in diff.
-		// IHMO such case should be handled here
-//		int ret = knot_zone_diff_remove_node(param->changeset,
-//		                                     node);
-//		if (ret != KNOT_EOK) {
-//			dbg_zonediff("zone_diff: diff_node: "
-//			             "Cannot remove node. Reason: %s.\n",
-//			             knot_strerror(ret));
-//		}
 		return KNOT_EOK;
 	}
 
@@ -571,7 +511,7 @@ static int knot_zone_diff_node(knot_node_t **node_ptr, void *data)
 			if (ret != KNOT_EOK) {
 				dbg_zonediff("zone_diff: diff_node: "
 				             "Failed to add RRSet.\n");
-				free(rrsets);
+				knot_node_free_rrset_array(node, rrsets);
 				return ret;
 			}
 		} else {
@@ -580,7 +520,7 @@ static int knot_zone_diff_node(knot_node_t **node_ptr, void *data)
 		}
 	}
 
-	free(rrsets);
+	knot_node_free_rrset_array(node, rrsets);
 
 	return KNOT_EOK;
 }
diff --git a/src/libknot/dnssec/nsec3.c b/src/libknot/dnssec/nsec3.c
index 71bdd4b396598d938c6e6dd572e08b57102fd084..f5c152ee0924513ec6812bda090a05b29042f7ab 100644
--- a/src/libknot/dnssec/nsec3.c
+++ b/src/libknot/dnssec/nsec3.c
@@ -108,23 +108,21 @@ static int nsec3_sha1(const uint8_t *salt, uint8_t salt_length,
  * \brief Initialize the structure with NSEC3 params from NSEC3PARAM RR set.
  */
 int knot_nsec3_params_from_wire(knot_nsec3_params_t *params,
-                                const knot_rrset_t *rrset)
+                                const knot_rrs_t *rrs)
 {
-	if (params == NULL || rrset == NULL || knot_rrset_rr_count(rrset) == 0) {
+	if (params == NULL || rrs == NULL || rrs->rr_count == 0) {
 		return KNOT_EINVAL;
 	}
 
-	assert(rrset->type == KNOT_RRTYPE_NSEC3PARAM);
-
 	knot_nsec3_params_t result = { 0 };
 
-	result.algorithm   = knot_rrs_nsec3param_algorithm(rrset, 0);
-	result.iterations  = knot_rrs_nsec3param_iterations(rrset, 0);
-	result.flags       = knot_rrs_nsec3param_flags(rrset, 0);
-	result.salt_length = knot_rrs_nsec3param_salt_length(rrset, 0);
+	result.algorithm   = knot_rrs_nsec3param_algorithm(rrs, 0);
+	result.iterations  = knot_rrs_nsec3param_iterations(rrs, 0);
+	result.flags       = knot_rrs_nsec3param_flags(rrs, 0);
+	result.salt_length = knot_rrs_nsec3param_salt_length(rrs, 0);
 
 	if (result.salt_length > 0) {
-		result.salt = knot_memdup(knot_rrs_nsec3param_salt(rrset, 0),
+		result.salt = knot_memdup(knot_rrs_nsec3param_salt(rrs, 0),
 		                          result.salt_length);
 		if (!result.salt) {
 			return KNOT_ENOMEM;
diff --git a/src/libknot/dnssec/nsec3.h b/src/libknot/dnssec/nsec3.h
index 01ed467e4797f20303220dd8a0d90c189dfd57ad..7c0aa76938190a8b07fd6df6ae2508eccaf356c7 100644
--- a/src/libknot/dnssec/nsec3.h
+++ b/src/libknot/dnssec/nsec3.h
@@ -89,12 +89,12 @@ typedef struct {
  * \brief Initialize the structure with NSEC3 params from NSEC3PARAM RR set.
  *
  * \param params      Structure to initialize.
- * \param nsec3param  The NSEC3PARAM RR set.
+ * \param nsec3param  The NSEC3PARAM RRs.
  *
  * \return Error code, KNOT_EOK on success.
  */
 int knot_nsec3_params_from_wire(knot_nsec3_params_t *params,
-                                const knot_rrset_t *rrset);
+                                const knot_rrs_t *rrs);
 /*!
  * \brief Clean up structure with NSEC3 params (do not deallocate).
  *
diff --git a/src/libknot/dnssec/rrset-sign.c b/src/libknot/dnssec/rrset-sign.c
index 45a312d13efd0ea807a236866ab903249d45bfcc..222766c7bbeea376d6a4373eaf8000dae02d68dc 100644
--- a/src/libknot/dnssec/rrset-sign.c
+++ b/src/libknot/dnssec/rrset-sign.c
@@ -304,7 +304,7 @@ static bool is_expired_signature(const knot_rrset_t *rrsigs, size_t pos,
 	assert(rrsigs->type == KNOT_RRTYPE_RRSIG);
 	assert(policy);
 
-	uint32_t expiration = knot_rdata_rrsig_sig_expiration(rrsigs, pos);
+	uint32_t expiration = knot_rrs_rrsig_sig_expiration(&rrsigs->rrs, pos);
 
 	return (expiration <= policy->refresh_before);
 }
@@ -335,7 +335,7 @@ int knot_is_valid_signature(const knot_rrset_t *covered,
 
 	uint8_t *signature = NULL;
 	size_t signature_size = 0;
-	knot_rdata_rrsig_signature(rrsigs, pos, &signature, &signature_size);
+	knot_rrs_rrsig_signature(&rrsigs->rrs, pos, &signature, &signature_size);
 	if (!signature) {
 		return KNOT_EINVAL;
 	}
diff --git a/src/libknot/rdata.h b/src/libknot/rdata.h
index 3e8195a48381350777f697dc3993b37287931c35..1519b19a67dd42685cf00424759fea663db9320e 100644
--- a/src/libknot/rdata.h
+++ b/src/libknot/rdata.h
@@ -385,5 +385,23 @@ const knot_dname_t *knot_rrs_srv_name(const knot_rrs_t *rrs, size_t pos)
 	return data_offset(rrs, pos, 6);
 }
 
-#endif /* _knot_rrs_H_ */
+static inline
+const knot_dname_t *knot_rrs_name(const knot_rrs_t *rrs, size_t pos,
+                                  uint16_t type)
+{
+	switch (type) {
+		case KNOT_RRTYPE_NS:
+			return knot_rrs_ns_name(rrs, pos);
+		case KNOT_RRTYPE_MX:
+			return knot_rrs_mx_name(rrs, pos);
+		case KNOT_RRTYPE_SRV:
+			return knot_rrs_srv_name(rrs, pos);
+		case KNOT_RRTYPE_CNAME:
+			return knot_rrs_cname_name(rrs);
+	}
+
+	return NULL;
+}
+
+#endif /* _KNOT_RDATA_H_ */
 /*! @} */
diff --git a/src/libknot/rr.c b/src/libknot/rr.c
index 5cf73c2b59f0509864edacd620ae222fb3597e4c..57bad59a120c64079900d62802f4055b457bfe8c 100644
--- a/src/libknot/rr.c
+++ b/src/libknot/rr.c
@@ -3,6 +3,7 @@
 #include <stdio.h>
 
 #include "libknot/rr.h"
+#include "libknot/rdata.h"
 #include "libknot/common.h"
 #include "common/errcode.h"
 
@@ -87,6 +88,15 @@ size_t knot_rrs_size(const knot_rrs_t *rrs)
 	return total_size;
 }
 
+uint16_t knot_rrs_rr_count(const knot_rrs_t *rrs)
+{
+	if (rrs == NULL) {
+		return 0;
+	}
+	
+	return rrs->rr_count;
+}
+
 int knot_rrs_remove_rr_at_pos(knot_rrs_t *rrs, size_t pos, mm_ctx_t *mm)
 {
 	if (rrs == NULL || pos >= rrs->rr_count) {
@@ -127,6 +137,38 @@ int knot_rrs_remove_rr_at_pos(knot_rrs_t *rrs, size_t pos, mm_ctx_t *mm)
 	return KNOT_EOK;
 }
 
+const uint8_t *knot_rrs_rr_rdata(const knot_rrs_t *rrs, size_t pos)
+{
+	if (rrs == NULL || pos >= rrs->rr_count) {
+		return NULL;
+	}
+	
+	return knot_rr_rdata(knot_rrs_rr(rrs, pos));
+}
+
+uint8_t *knot_rrs_rr_get_rdata(const knot_rrs_t *rrs, size_t pos)
+{
+	return (uint8_t *)knot_rrs_rr_rdata(rrs, pos);
+}
+
+uint16_t knot_rrs_rr_size(const knot_rrs_t *rrs, size_t pos)
+{
+	if (rrs == NULL || pos >= rrs->rr_count) {
+		return 0;
+	}
+	
+	return knot_rr_size(knot_rrs_rr(rrs, pos));
+}
+
+uint32_t knot_rrs_rr_ttl(const knot_rrs_t *rrs, size_t pos)
+{
+	if (rrs == NULL || pos >= rrs->rr_count) {
+		return 0;
+	}
+	
+	return knot_rr_ttl(knot_rrs_rr(rrs, pos));
+}
+
 uint8_t* knot_rrs_create_rr_at_pos(knot_rrs_t *rrs,
                                    size_t pos, uint16_t size,
                                    uint32_t ttl, mm_ctx_t *mm)
@@ -198,6 +240,33 @@ uint8_t* knot_rrs_create_rr(knot_rrs_t *rrs, const uint16_t size,
 	return knot_rr_get_rdata(rr);
 }
 
+int knot_rrs_add_rr(knot_rrs_t *rrs, const knot_rr_t *rr, mm_ctx_t *mm)
+{
+	if (rrs == NULL || rr == NULL) {
+		return KNOT_EINVAL;
+	}
+	
+	uint8_t *data =
+		knot_rrs_create_rr(rrs, knot_rr_size(rr), knot_rr_ttl(rr), mm);
+	if (data == NULL) {
+		return KNOT_ENOMEM;
+	}
+	memcpy(data, knot_rr_rdata(rr), knot_rr_size(rr));
+	
+	return KNOT_EOK;
+}
+
+knot_rrs_t *knot_rrs_new(mm_ctx_t *mm)
+{
+	knot_rrs_t *rrs = mm_alloc(mm, sizeof(knot_rrs_t));
+	if (rrs == NULL) {
+		ERR_ALLOC_FAILED;
+		return NULL;
+	}
+	knot_rrs_init(rrs);
+	return rrs;
+}
+
 void knot_rrs_init(knot_rrs_t *rrs)
 {
 	if (rrs) {
@@ -240,3 +309,30 @@ int knot_rrs_copy(knot_rrs_t *dst, const knot_rrs_t *src, mm_ctx_t *mm)
 	return KNOT_EOK;
 }
 
+
+int knot_rrs_synth_rrsig(uint16_t type, const knot_rrs_t *rrsig_rrs,
+                         knot_rrs_t *out_sig, mm_ctx_t *mm)
+{
+	if (rrsig_rrs == NULL) {
+		return KNOT_ENOENT;
+	}
+	
+	if (out_sig == NULL || out_sig->rr_count > 0) {
+		return KNOT_EINVAL;
+	}
+	
+	for (int i = 0; i < rrsig_rrs->rr_count; ++i) {
+		if (type == knot_rrs_rrsig_type_covered(rrsig_rrs, i)) {
+			const knot_rr_t *rr_to_copy = knot_rrs_rr(rrsig_rrs, i);
+			int ret = knot_rrs_add_rr(out_sig, rr_to_copy, mm);
+			if (ret != KNOT_EOK) {
+				knot_rrs_clear(out_sig, mm);
+				return ret;
+			}
+		}
+	}
+	
+	return out_sig->rr_count > 0 ? KNOT_EOK : KNOT_ENOENT;
+}
+
+
diff --git a/src/libknot/rr.h b/src/libknot/rr.h
index 0583c9aa02d1c207abfb8530c5552e3a5b720a79..9325d4fd898292ad6cb1b49cd1ebbe71175bcd21 100644
--- a/src/libknot/rr.h
+++ b/src/libknot/rr.h
@@ -19,10 +19,17 @@ const uint8_t *knot_rr_rdata(const knot_rr_t *rr);
 uint8_t *knot_rr_get_rdata(knot_rr_t *rr);
 void knot_rr_set_size(knot_rr_t *rr, uint16_t size);
 void knot_rr_set_ttl(knot_rr_t *rr, uint32_t ttl);
+int knot_rrs_add_rr(knot_rrs_t *rrs, const knot_rr_t *rr, mm_ctx_t *mm);
+knot_rrs_t *knot_rrs_new(mm_ctx_t *mm);
 void knot_rrs_init(knot_rrs_t *rrs);
 size_t knot_rrs_size(const knot_rrs_t *rrs);
+uint16_t knot_rrs_rr_count(const knot_rrs_t *rrs);
 knot_rr_t *knot_rrs_get_rr(const knot_rrs_t *rrs, size_t pos);
 const knot_rr_t *knot_rrs_rr(const knot_rrs_t *rrs, size_t pos);
+const uint8_t *knot_rrs_rr_rdata(const knot_rrs_t *rrs, size_t pos);
+uint8_t *knot_rrs_rr_get_rdata(const knot_rrs_t *rrs, size_t pos);
+uint16_t knot_rrs_rr_size(const knot_rrs_t *rrs, size_t pos);
+uint32_t knot_rrs_rr_ttl(const knot_rrs_t *rrs, size_t pos);
 uint8_t* knot_rrs_create_rr(knot_rrs_t *rrs, const uint16_t size,
                             const uint32_t ttl, mm_ctx_t *mm);
 uint8_t* knot_rrs_create_rr_at_pos(knot_rrs_t *rrs,
@@ -32,4 +39,6 @@ int knot_rrs_remove_rr_at_pos(knot_rrs_t *rrs, size_t pos, mm_ctx_t *mm);
 void knot_rrs_free(knot_rrs_t *rrs, mm_ctx_t *mm);
 void knot_rrs_clear(knot_rrs_t *rrs, mm_ctx_t *mm);
 int knot_rrs_copy(knot_rrs_t *dst, const knot_rrs_t *src, mm_ctx_t *mm);
+int knot_rrs_synth_rrsig(uint16_t type, const knot_rrs_t *rrsig_rrs,
+                         knot_rrs_t *out_sig, mm_ctx_t *mm);
 
diff --git a/src/libknot/rrset.c b/src/libknot/rrset.c
index c93d650bceca7aaf4e95e181025a0458fc7518d4..1587b5811b15c14a6e0be1d81f4328cca1ba7df7 100644
--- a/src/libknot/rrset.c
+++ b/src/libknot/rrset.c
@@ -1089,7 +1089,7 @@ bool knot_rrset_is_nsec3rel(const knot_rrset_t *rr)
 	/* Is NSEC3 or non-empty RRSIG covering NSEC3. */
 	return ((knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3)
 	        || (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG
-	            && knot_rdata_rrsig_type_covered(rr, 0)
+	            && knot_rrs_rrsig_type_covered(&rr->rrs, 0)
 	            == KNOT_RRTYPE_NSEC3));
 }
 
@@ -1344,7 +1344,7 @@ static int add_rdata_to_rrsig(knot_rrset_t *new_sig, uint16_t type,
 	uint16_t rrsigs_rdata_count = knot_rrset_rr_count(rrsigs);
 	for (uint16_t i = 0; i < rrsigs_rdata_count; ++i) {
 		const uint16_t type_covered =
-			knot_rdata_rrsig_type_covered(rrsigs, i);
+			knot_rrs_rrsig_type_covered(&rrsigs->rrs, i);
 		if (type_covered == type) {
 			int ret = knot_rrset_add_rr_from_rrset(new_sig, rrsigs,
 			                                       i, mm);
diff --git a/src/utils/dig/dig_exec.c b/src/utils/dig/dig_exec.c
index aaab0621c50ccb9bde22cb5010e10d47b533708d..fd0b1bf9e5c4f85092500b26821021512db46173 100644
--- a/src/utils/dig/dig_exec.c
+++ b/src/utils/dig/dig_exec.c
@@ -125,7 +125,7 @@ static knot_pkt_t* create_query_packet(const query_t *query)
 		}
 
 		// Set SOA serial.
-		knot_rdata_soa_serial_set(soa, query->xfr_serial);
+		knot_rrs_soa_serial_set(&soa->rrs, query->xfr_serial);
 
 		// Add authority section.
 		knot_pkt_begin(packet, KNOT_AUTHORITY);
@@ -232,7 +232,7 @@ static int64_t first_serial_check(const knot_pkt_t *reply)
 	if (first->type != KNOT_RRTYPE_SOA) {
 		return -1;
 	} else {
-		return knot_rdata_soa_serial(first);
+		return knot_rrs_soa_serial(&first->rrs);
 	}
 }
 
@@ -248,8 +248,7 @@ static bool last_serial_check(const uint32_t serial, const knot_pkt_t *reply)
 	if (last->type != KNOT_RRTYPE_SOA) {
 		return false;
 	} else {
-		int64_t last_serial = knot_rdata_soa_serial(last);
-
+		int64_t last_serial = knot_rrs_soa_serial(&last->rrs);
 		if (last_serial == serial) {
 			return true;
 		} else {