1 /*******************************************************************************
2 
3     Variadic-sized value type to represent a hash
4 
5     A `BitBlob` is a value type representing a hash.
6     The argument is the size in bits, e.g. for sha256 it is 256.
7     It can be initialized from the hexadecimal string representation
8     or an `ubyte[]`, making it easy to interact with `std.digest`
9 
10     Author:         Mathias 'Geod24' Lang
11     License:        MIT (See LICENSE.txt)
12     Copyright:      Copyright (c) 2017-2018 Mathias Lang. All rights reserved.
13 
14 *******************************************************************************/
15 
16 module geod24.bitblob;
17 
18 static import std.ascii;
19 import std.algorithm;
20 import std.range;
21 import std.format;
22 import std.utf;
23 
24 ///
25 @nogc @safe pure nothrow unittest
26 {
27     import std.digest.sha;
28     alias Hash = BitBlob!256;
29     Hash k1 = sha256Of("Hello World");
30 }
31 
32 /*******************************************************************************
33 
34     A value type representing a hash
35 
36     Params:
37       Bits = The size of the hash, in bits. Must be a multiple of 8.
38 
39 *******************************************************************************/
40 public struct BitBlob (size_t Bits)
41 {
42     /// Used by std.format
43     /// Cannot be `nothrow @nogc` since sformat is not, but does not allocate
44     public void toString (scope void delegate(const(char)[]) @safe sink) const @safe
45     {
46         sink("0x");
47         char[2] data;
48         // retro because the data is stored in little endian
49         this.data[].retro.each!(
50             (bin)
51             {
52                 sformat(data, "%0.2x", bin);
53                 sink(data);
54             });
55     }
56 
57     /// Used for serialization
58     public string toString () const @safe
59     {
60         size_t idx;
61         char[Width * 2 + 2] buffer = void;
62         scope sink = (const(char)[] v) {
63                 buffer[idx .. idx + v.length] = v;
64                 idx += v.length;
65             };
66         this.toString(sink);
67         return buffer.idup;
68     }
69 
70     pure nothrow @nogc @safe:
71 
72     /***************************************************************************
73 
74         Create a BitBlob from binary data, e.g. serialized data
75 
76         Params:
77             bin  = Binary data to store in this `BitBlob`.
78             isLE = `true` if the data is little endian, `false` otherwise.
79                    Internally the data will be stored in little endian.
80 
81         Throws:
82             If `bin.length != typeof(this).Width`
83 
84     ***************************************************************************/
85 
86     public this (scope const ubyte[] bin, bool isLE = true)
87     {
88         assert(bin.length == Width);
89         this.data[] = bin[];
90         if (!isLE)
91             this.data[].reverse;
92     }
93 
94     /***************************************************************************
95 
96         Create a BitBlob from an hexadecimal string representation
97 
98         Params:
99             hexstr = String representation of the binary data in base 16.
100                      The hexadecimal prefix (0x) is optional.
101                      Can be upper or lower case.
102 
103         Throws:
104             If `hexstr_without_prefix.length != (typeof(this).Width * 2)`.
105 
106     ***************************************************************************/
107 
108     public this (scope const(char)[] hexstr)
109     {
110         assert(hexstr.length == (Width * 2)
111                || hexstr.length == (Width * 2) + "0x".length);
112 
113         auto range = hexstr.byChar.map!(std.ascii.toLower!(char));
114         range.skipOver("0x".byChar);
115         // Each doesn't work
116         foreach (size_t idx, chunk; range.map!(fromHex).chunks(2).retro.enumerate)
117             this.data[idx] = cast(ubyte)((chunk[0] << 4) + chunk[1]);
118     }
119 
120     /// Used for deserialization
121     static auto fromString (const(char)[] str)
122     {
123         return BitBlob!(Bits)(str);
124     }
125 
126     static assert (
127         Bits % 8 == 0,
128         "Argument to BitBlob must be a multiple of 8");
129 
130     /// The width of this aggregate, in octets
131     public static immutable Width = Bits / 8;
132 
133     /// Store the internal data
134     private ubyte[Width] data;
135 
136     /// Returns: If this BitBlob has any value
137     public bool isNull () const
138     {
139         return this.data[].all!((v) => v == 0);
140     }
141 
142     /// Used for sha256Of
143     public const(ubyte)[] opIndex () const
144     {
145         return this.data;
146     }
147 
148     /// Public because of a visibility bug
149     public static ubyte fromHex (char c)
150     {
151         if (c >= '0' && c <= '9')
152             return cast(ubyte)(c - '0');
153         if (c >= 'a' && c <= 'f')
154             return cast(ubyte)(10 + c - 'a');
155         assert(0, "Unexpected char in string passed to BitBlob");
156     }
157 
158     public int opCmp (ref const typeof(this) s) const
159     {
160         // Reverse because little endian
161         foreach_reverse (idx, b; this.data)
162             if (b != s.data[idx])
163                 return b - s.data[idx];
164         return 0;
165     }
166 }
167 
168 pure @safe nothrow @nogc unittest
169 {
170     alias Hash = BitBlob!256;
171 
172     Hash gen1 = GenesisBlockHashStr;
173     Hash gen2 = GenesisBlockHash;
174     assert(gen1.data == GenesisBlockHash);
175     assert(gen1 == gen2);
176 
177     Hash gm1 = GMerkle_str;
178     Hash gm2 = GMerkle_bin;
179     assert(gm1.data == GMerkle_bin);
180     assert(gm1 == gm2);
181 
182     Hash empty;
183     assert(empty.isNull);
184     assert(!gen1.isNull);
185 
186     // Test opCmp
187     assert(empty < gen1);
188     assert(gm1 > gen2);
189 }
190 
191 /// Test toString
192 unittest
193 {
194     import std.format;
195     alias Hash = BitBlob!256;
196     Hash gen1 = GenesisBlockHashStr;
197     assert(format("%s", gen1) == GenesisBlockHashStr);
198 }
199 
200 version (unittest)
201 {
202 private:
203     /// Bitcoin's genesis block hash
204     static immutable GenesisBlockHashStr =
205         "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
206     static immutable ubyte[32] GenesisBlockHash = [
207         0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, 0xc1, 0xa6, 0xa2, 0x46,
208         0xae, 0x63, 0xf7, 0x4f, 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
209         0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00 ];
210 
211     /// Bitcoin's genesis block Merkle root hash
212     static immutable GMerkle_str =
213         "0X4A5E1E4BAAB89F3A32518A88C31BC87F618F76673E2CC77AB2127B7AFDEDA33B";
214     static immutable ubyte[] GMerkle_bin = [
215         0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e,
216         0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
217         0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a ];
218 }