/// <summary> /// Reconstructs a minimal tree representation from the given bytestream. /// <remarks> /// In order to verify transaction inclusion in a certain DLT block, a minimum tree can be requested from any node, after which /// you must call the `reconstructMinimumTree()` function, followed by the `calculateTreeHash()` function. /// The root hash will be calculated based on the provided data. You can then compare this value with the value in a block header. /// </remarks> /// </summary> /// <param name="data"></param> public void reconstructMinimumTree(byte[] data) { lock (threadLock) { root = new PITNode(0); root.childNodes = new SortedList <byte, PITNode>(); using (BinaryReader br = new BinaryReader(new MemoryStream(data))) { PIT_MinimumTreeType type = (PIT_MinimumTreeType)br.ReadByte(); levels = br.ReadByte(); hashLength = br.ReadByte(); if (type == PIT_MinimumTreeType.SingleTX) { readMinTreeInt(br, root); } else if (type == PIT_MinimumTreeType.Anonymized) { readMinTreeAInt(br, root); } else if (type == PIT_MinimumTreeType.Matcher) { readMinTreeMInt(br, root); } } } }
private bool delIntRec(byte[] binaryTxid, PITNode cn) { if (cn.data != null) { // we've reached the last non-leaf level if (cn.data.RemoveWhere(x => x.SequenceEqual(binaryTxid)) > 0) { cn.hash = null; return(true); // something has changed } } else if (cn.childNodes != null) { bool changed = false; byte cb = binaryTxid[cn.level]; if (cn.childNodes.ContainsKey(cb)) { PITNode t_node = cn.childNodes[cb]; changed = delIntRec(binaryTxid, t_node); if ((t_node.childNodes == null || t_node.childNodes.Count == 0) && (t_node.data == null || t_node.data.Count == 0)) { // the child node at `cb` has neither further children nor data, we can drop it cn.childNodes.Remove(cb); changed = true; } if (changed) { // child node has no leaves cn.hash = null; } } return(changed); } return(false); }
private void readMinTreeInt(BinaryReader br, PITNode cn) { byte type = br.ReadByte(); if (type == 255) { // final non-leaf node, what follows are TXids int num_tx = br.ReadInt32(); cn.data = new SortedSet <byte[]>(new ByteArrayComparer()); for (int i = 0; i < num_tx; i++) { int txid_len = br.ReadInt32(); byte[] txid = br.ReadBytes(txid_len); cn.data.Add(txid); } } else if (type == 254) { // normal non-leaf node, following are hashes for child nodes and then the next node down int num_child = br.ReadInt32(); // children except for the downward cn.childNodes = new SortedList <byte, PITNode>(num_child + 1); for (int i = 0; i < num_child; i++) { byte cb1 = br.ReadByte(); PITNode n = new PITNode(cn.level + 1); n.hash = br.ReadBytes(hashLength); cn.childNodes.Add(cb1, n); } // downwards direction: byte cb = br.ReadByte(); PITNode n_down = new PITNode(cn.level + 1); readMinTreeInt(br, n_down); cn.childNodes.Add(cb, n_down); } }
private void writeMinTreeAInt(BinaryWriter wr, PITNode cn) { if (cn.data != null) { // leaf node - write node's hash and transactions (only if more than one) wr.Write((byte)255); // marker for the leaf node if (cn.data.Count > 1) { wr.Write(cn.data.Count); foreach (var txid in cn.data) { wr.Write(txid.Length); wr.Write(txid); } } else { wr.Write(0); // zero transactions needed wr.Write(cn.hash); // in this case, only the hash needs to be given } } if (cn.childNodes != null) { wr.Write((byte)254); // marker for non-leaf node wr.Write(cn.childNodes.Count); foreach (var n in cn.childNodes) { wr.Write(n.Key); writeMinTreeAInt(wr, n.Value); } } }
private void writeMinTreeInt(byte[] binaryTxid, BinaryWriter wr, PITNode cn) { if (cn.data != null) { // final node - write all txids, because they are required to calculate node hash wr.Write((byte)255); // marker for the leaf node wr.Write(cn.data.Count); foreach (var txid in cn.data) { wr.Write(txid.Length); wr.Write(txid); } } if (cn.childNodes != null) { // intermediate node - write all prefixes and hashes, except for the downward tree, so the partial tree can be reconstructed wr.Write((byte)254); // marker for the non-leaf node wr.Write(cn.childNodes.Count - 1); byte cb = binaryTxid[cn.level]; foreach (var n in cn.childNodes) { if (n.Key == cb) { // skip our target branch - we will write that last continue; } wr.Write(n.Key); wr.Write(n.Value.hash); } // follow the downwards direction for the transaction we're adding wr.Write(cb); writeMinTreeInt(binaryTxid, wr, cn.childNodes[cb]); } }
private void getAllIntRec(List <byte[]> output, PITNode cn) { if (cn.data != null) { // leaf node output.AddRange(cn.data); } else if (cn.childNodes != null) { foreach (PITNode n in cn.childNodes.Values) { getAllIntRec(output, n); } } }
private bool containsIntRec(byte[] binaryTxid, PITNode cn) { byte cb = binaryTxid[cn.level]; if (cn.data != null) { if (cn.data.Any(x => x.SequenceEqual(binaryTxid))) { return(true); } } if (cn.childNodes != null && cn.childNodes.ContainsKey(cb)) { return(containsIntRec(binaryTxid, cn.childNodes[cb])); } return(false); }
private void readMinTreeMInt(BinaryReader br, PITNode cn) { byte type = br.ReadByte(); if (type == 255) { // leaf node int count_txids = br.ReadInt32(); cn.data = new SortedSet <byte[]>(new ByteArrayComparer()); if (count_txids > 0) { for (int i = 0; i < count_txids; i++) { int txid_len = br.ReadInt32(); byte[] txid = br.ReadBytes(txid_len); cn.data.Add(txid); } } } else if (type == 254) { byte num_pruned = br.ReadByte(); cn.childNodes = new SortedList <byte, PITNode>(num_pruned); for (int i = 0; i < num_pruned; i++) { byte pb = br.ReadByte(); PITNode n = new PITNode(cn.level + 1); n.hash = br.ReadBytes(hashLength); cn.childNodes.Add(pb, n); } byte num_full = br.ReadByte(); for (int i = 0; i < num_full; i++) { byte fb = br.ReadByte(); PITNode n = new PITNode(cn.level + 1); readMinTreeMInt(br, n); cn.childNodes.Add(fb, n); } } }
private bool addIntRec(byte[] binaryTxid, PITNode cn, byte level) { byte cb = binaryTxid[level]; if (level >= (levels - 1)) { // we've reached last (leaf) level if (cn.data == null) { cn.data = new SortedSet <byte[]>(new ByteArrayComparer()); } if (!cn.data.Contains(binaryTxid)) { cn.data.Add(binaryTxid); cn.hash = null; return(true); // something has changed } return(false); // nothing has changed } bool changed = false; if (!cn.childNodes.ContainsKey(cb)) { PITNode n = new PITNode(level + 1); if (level + 1 < levels - 1) { n.childNodes = new SortedList <byte, PITNode>(); } cn.childNodes.Add(cb, n); changed = true; } changed |= addIntRec(binaryTxid, cn.childNodes[cb], (byte)(level + 1)); if (changed) { cn.hash = null; } return(changed); }
private void calcHashInt(PITNode cn) { if (cn.hash != null) { // hash is already cached (or retrieved in the minimal tree) return; } if (cn.data != null) { // last non-leaf level int all_hashes_len = cn.data.Aggregate(0, (sum, x) => sum + x.Length); byte[] indata = new byte[all_hashes_len]; int idx = 0; foreach (var d in cn.data) { Array.Copy(d, 0, indata, idx, d.Length); idx += d.Length; } cn.hash = Crypto.sha512sqTrunc(indata, 0, indata.Length, hashLength); } else if (cn.childNodes != null) { byte[] indata = new byte[cn.childNodes.Count * hashLength]; int idx = 0; foreach (var n in cn.childNodes) { if (n.Value.hash == null) { calcHashInt(n.Value); } Array.Copy(n.Value.hash, 0, indata, idx * hashLength, n.Value.hash.Length); idx += 1; } cn.hash = Crypto.sha512sqTrunc(indata, 0, indata.Length, hashLength); } }
private void writeMinTreeMInt(BinaryWriter bw, IEnumerable <byte[]> included, PITNode cn) { if (cn.data != null) { // leaf node, we need to write all txids bw.Write((byte)255); // marker for the leaf node bw.Write(cn.data.Count); foreach (var txid in cn.data) { bw.Write(txid.Length); bw.Write(txid); } } else if (cn.childNodes != null) { bw.Write((byte)254); // marker for the non-leaf node // all child nodes/txids which should be followed List <byte> full_downward = cn.childNodes.Keys.Where(b => included.Any(i => i[cn.level] == b)).ToList(); byte num_pruned = (byte)(cn.childNodes.Count - full_downward.Count); bw.Write(num_pruned); foreach (byte pb in cn.childNodes.Keys.Except(full_downward)) { bw.Write(pb); bw.Write(cn.childNodes[pb].hash); } // write full explore nodes, if any bw.Write((byte)(cn.childNodes.Count - num_pruned)); foreach (byte fb in full_downward) { bw.Write(fb); writeMinTreeMInt(bw, included.Where(i => i[cn.level] == fb), cn.childNodes[fb]); } } }