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 }