1 /// Basic fuinction and types used in the DART database
2 module tagion.dart.DARTBasic;
3 
4 @safe:
5 import std.format;
6 import std.traits;
7 import std.typecons;
8 import std.array;
9 import tagion.basic.Types : Buffer;
10 import tagion.crypto.SecureInterfaceNet : HashNet;
11 import tagion.crypto.Types : BufferType, Fingerprint;
12 import tagion.dart.DARTFile : KEY_SPAN;
13 import tagion.hibon.Document;
14 import tagion.hibon.HiBONRecord : isHiBONRecord;
15 import tagion.hibon.HiBONRecord : HiBONPrefix, STUB;
16 
17 /**
18 * This is the raw-hash value of a message and is used when message is signed.
19 */
20 alias DARTIndex = Typedef!(Buffer, null, BufferType.HASHPOINTER.stringof);
21 
22 /**
23  * Calculates the fingerprint used as an index for the DART
24  * Handles the hashkey '#' and stub used in the DART
25  * Params:
26  *   net = Hash function interface
27  *   doc = document to be hashed
28  * Returns: 
29  *   The DART fingerprint
30  */
31 
32 immutable(DARTIndex) dartIndex(const(HashNet) net, const(Document) doc) {
33     if (!doc.empty && (doc.keys.front[0] is HiBONPrefix.HASH)) {
34         if (doc.keys.front == STUB) {
35             return doc[STUB].get!DARTIndex;
36         }
37         auto first = doc[].front;
38         immutable value_data = first.data[0 .. first.size];
39         return DARTIndex(net.rawCalcHash(value_data));
40     }
41     return DARTIndex(cast(Buffer) net.calcHash(doc));
42 }
43 
44 /// Ditto
45 immutable(DARTIndex) dartIndex(T)(const(HashNet) net, T value) if (isHiBONRecord!T) {
46     return net.dartIndex(value.toDoc);
47 }
48 
49 unittest { // Check the #key hash with types
50     import tagion.crypto.SecureInterfaceNet : HashNet;
51     import tagion.crypto.SecureNet : StdHashNet;
52     import tagion.hibon.HiBONRecord : HiBONRecord, label;
53 
54     const(HashNet) net = new StdHashNet;
55     static struct HashU32 {
56         @label("#key") uint x;
57         string extra_name;
58         mixin HiBONRecord;
59     }
60 
61     static struct HashU64 {
62         @label("#key") ulong x;
63         mixin HiBONRecord;
64     }
65 
66     HashU32 hash_u32;
67     HashU64 hash_u64;
68     hash_u32.x = 42;
69     hash_u64.x = 42;
70     import std.stdio;
71 
72     assert(net.dartIndex(hash_u32) != net.dartIndex(hash_u64));
73     auto other_hash_u32 = hash_u32;
74     other_hash_u32.extra_name = "extra";
75     assert(net.dartIndex(hash_u32) == net.dartIndex(other_hash_u32),
76             "Archives with the same #key should have the same dart-Index");
77     assert(net.calcHash(hash_u32) != net.calcHash(other_hash_u32),
78             "Two archives with same #key and different data should have different fingerprints");
79 }
80 
81 DARTIndex dartKey(T)(const(HashNet) net, const(char[]) name, T val) {
82     import std.stdio;
83     import tagion.hibon.HiBON;
84 
85     const key = (name[0] == HiBONPrefix.HASH) ? name.idup : (HiBONPrefix.HASH ~ name).idup;
86     auto h = new HiBON;
87     h[key] = val;
88     return net.dartIndex(Document(h));
89 }
90 
91 unittest {
92     import std.format;
93     import std.traits;
94     import std.typecons;
95     import tagion.crypto.SecureNet : StdHashNet;
96     import tagion.hibon.BigNumber;
97     import tagion.hibon.HiBONBase : Type;
98     import tagion.hibon.HiBONRecord : HiBONRecord, label;
99     import tagion.utils.StdTime;
100 
101     const net = new StdHashNet;
102 
103     static struct DARTKey(T) {
104         @label("#key") T key;
105         int x;
106 
107         mixin HiBONRecord!(q{
108             this(T key, int x) {
109                 this.key=key;
110                 this.x=x;
111             }
112         });
113     }
114 
115     auto dartKeyT(T)(T key, int x) {
116         return DARTKey!T(key, x);
117     }
118 
119     alias Table = Tuple!(
120             BigNumber, Type.BIGINT.stringof,
121             bool, Type.BOOLEAN.stringof,
122             float, Type.FLOAT32.stringof,
123             double, Type.FLOAT64.stringof,
124             int, Type.INT32.stringof,
125             long, Type.INT64.stringof,
126             sdt_t, Type.TIME.stringof,
127             uint, Type.UINT32.stringof,
128             ulong, Type.UINT64.stringof,
129             immutable(ubyte)[], Type.BINARY.stringof,
130             string, Type.STRING.stringof,
131 
132     );
133     // dfmt on
134 
135     Table test_table;
136     test_table.FLOAT32 = 1.23;
137     test_table.FLOAT64 = 1.23e200;
138     test_table.INT32 = -42;
139     test_table.INT64 = -0x0123_3456_789A_BCDF;
140     test_table.UINT32 = 42;
141     test_table.UINT64 = 0x0123_3456_789A_BCDF;
142     test_table.BIGINT = BigNumber("-1234_5678_9123_1234_5678_9123_1234_5678_9123");
143     test_table.BOOLEAN = true;
144     test_table.TIME = 1001;
145     test_table.BINARY = [1, 2, 3];
146     test_table.STRING = "Text";
147     import std.stdio;
148 
149     foreach (i, t; test_table) {
150         const dart_index = net.dartKey("#key", t);
151         const dart_key = dartKeyT(t, 42);
152         assert(dart_index == net.dartIndex(dart_key), format("%s dartKey failed", Fields!Table[i].stringof));
153         assert(dart_index != net.calcHash(dart_key.toDoc), format("%s dart_index should not be equal to the fingerpint", Fields!Table[i]
154             .stringof));
155     }
156 }
157 
158 immutable(Buffer) binaryHash(const(HashNet) net, scope const(ubyte[]) h1, scope const(ubyte[]) h2)
159 in {
160     assert(h1.length is 0 || h1.length is net.hashSize,
161             format("h1 is not a valid hash (length=%d should be 0 or %d", h1.length, net.hashSize));
162     assert(h2.length is 0 || h2.length is net.hashSize,
163             format("h2 is not a valid hash (length=%d should be 0 or %d", h2.length, net.hashSize));
164 }
165 out (result) {
166     if (h1.length is 0) {
167         assert(h2 == result);
168     }
169     else if (h2.length is 0) {
170         assert(h1 == result);
171     }
172 }
173 do {
174     assert(h1.length is 0 || h1.length is net.hashSize,
175             format("h1 is not a valid hash (length=%d should be 0 or %d", h1.length, net.hashSize));
176     assert(h2.length is 0 || h2.length is net.hashSize,
177             format("h2 is not a valid hash (length=%d should be 0 or %d", h2.length, net.hashSize));
178     if (h1.length is 0) {
179         return h2.idup;
180     }
181     if (h2.length is 0) {
182         return h1.idup;
183     }
184     return net.rawCalcHash(h1 ~ h2);
185 }
186 
187 Fingerprint binaryHash(const(HashNet) net, scope const(Fingerprint) h1, scope const(Fingerprint) h2) {
188     return Fingerprint(binaryHash(net, cast(Buffer) h1, cast(Buffer) h2));
189 }
190 /**
191 
192  * Calculates the sparsed Merkle root from the branch-table list
193 * The size of the table must be KEY_SPAN
194 * Leaves in the branch table which doen't exist should have the value null
195  * Params:
196  *   net = The hash object/function used to calculate the hashs
197  *   table = List if hash-value(fingerprint) in the branch
198  * Returns: 
199  *  The Merkle root
200  */
201 Buffer sparsed_merkletree(const HashNet net, const(Buffer[]) table)
202 in (table.length == KEY_SPAN)
203 do {
204     immutable(Buffer) merkletree(
205             const(Buffer[]) left,
206     const(Buffer[]) right) {
207         Buffer _left_fingerprint;
208         Buffer _right_fingerprint;
209         if ((left.length == 1) && (right.length == 1)) {
210             _left_fingerprint = left[0];
211             _right_fingerprint = right[0];
212         }
213         else {
214             immutable left_mid = left.length >> 1;
215             immutable right_mid = right.length >> 1;
216             _left_fingerprint = merkletree(left[0 .. left_mid], left[left_mid .. $]);
217             _right_fingerprint = merkletree(right[0 .. right_mid], right[right_mid .. $]);
218         }
219         if (_left_fingerprint is null) {
220             return _right_fingerprint;
221         }
222         else if (_right_fingerprint is null) {
223             return _left_fingerprint;
224         }
225         else {
226             return net.binaryHash(_left_fingerprint, _right_fingerprint);
227         }
228     }
229 
230     immutable mid = table.length >> 1;
231     return merkletree(table[0 .. mid], table[mid .. $]);
232 }
233 
234 Fingerprint sparsed_merkletree(const HashNet net, const(Fingerprint[]) table, const Flag!"flat" flat = No.flat) @trusted {
235     if (flat) {
236         return net.calcHash((cast(Buffer[]) table).join);
237     }
238     return Fingerprint(sparsed_merkletree(net, cast(const(Buffer[])) table));
239 }
240 
241 unittest { // StdHashNet
242     //import tagion.utils.Miscellaneous : toHex=toHexString;
243     import core.exception : AssertError;
244     import std.exception : assertThrown;
245     import std.string : representation;
246     import tagion.hibon.HiBONRecord : hasHashKey, isStub;
247 
248     // import std.stdio;
249 
250     import tagion.crypto.SecureNet : StdHashNet;
251     import tagion.hibon.HiBON;
252 
253     const net = new StdHashNet;
254     Document doc; // This is the data which is filed in the DART
255     {
256         auto hibon = new HiBON;
257         hibon["text"] = "Some text";
258         doc = Document(hibon);
259     }
260 
261     immutable doc_fingerprint = net.rawCalcHash(doc.serialize);
262 
263     {
264         assert(net.binaryHash(null, null).length is 0);
265         assert(net.binaryHash(doc_fingerprint, null) == doc_fingerprint);
266         assert(net.binaryHash(null, doc_fingerprint) == doc_fingerprint);
267     }
268 
269 }