1 module dcrypt.random.fortuna.accumulator; 2 3 import dcrypt.random.drng; 4 import dcrypt.bitmanip; 5 import dcrypt.util: wipe; 6 7 private enum minPoolSize = 64; /// return empty entropy if pool0's size is < MINPOOLSIZE 8 private enum bufferSize = 32; /// size of the output buffer and internal state 9 10 // Test shared and non-shared Accumulator 11 unittest { 12 auto acc = new Accumulator!HashDRNG_SHA3_512; 13 auto accShared = new shared Accumulator!HashDRNG_SHA3_512; 14 15 ubyte[32] buf1; 16 ubyte[32] buf2; 17 18 foreach(i; 0..32) { 19 20 acc.extractEntropy(buf1); 21 accShared.extractEntropy(buf2); 22 23 assert(buf1 == buf2, "Accumulator does not behave deterministically!"); 24 25 acc.addEntropy(0, i%acc.pools, buf1); 26 accShared.addEntropy(0, i%accShared.pools, buf2); 27 } 28 29 // change only one accumulator 30 acc.addEntropy(0, 0, buf1); 31 32 acc.extractEntropy(buf1); 33 accShared.extractEntropy(buf2); 34 35 assert(buf1 != buf2, "Outputs should be different!"); 36 } 37 38 39 40 41 /// This class is a core component of the Fortuna algorithm and is responsible for collecting 42 /// and accumulating entropy from various sources. 43 /// 44 /// Params: 45 /// DRNG = A deterministic RNG with input. This type is used as entropy pool. 46 @safe 47 package class Accumulator(DRNG, uint num_pools = 32) 48 if(isDRNGWithInput!DRNG) 49 { 50 51 alias num_pools pools; 52 53 nothrow @nogc: 54 55 /// Returns: Amount of new seed bytes in pool0. 56 @property 57 uint freshEntropyLength() { 58 return entropyPools[0].freshEntropy; 59 } 60 61 62 /// Multithreading aware version of `extractEntropy()` 63 @safe 64 synchronized void extractEntropy(ubyte[] buf) { 65 transaction(0, 0, null, buf); 66 } 67 68 /// Multithreading aware version of `addEntropy()` 69 @safe 70 synchronized void addEntropy(in ubyte sourceID, in size_t pool, in ubyte[] data) { 71 transaction(sourceID, pool, data, null); 72 } 73 74 /** 75 * Params: 76 * reseedCount = Used to determine from which pools entropy should be fetched. 77 * buf = Write the seed in this buffer. Length must be `bufferSize`. 78 */ 79 void extractEntropy(ubyte[] buf) { 80 81 ubyte[32] iBuf; 82 83 scope(exit) { 84 counter++; 85 wipe(iBuf); 86 } 87 88 foreach(i, pool; entropyPools) { 89 if(counter % (1<<i) == 0) { // reseedCount divisible by 2^i ? 90 pool.extractEntropy(iBuf); 91 masterPool.addEntropy(iBuf); 92 }else { 93 // won't be divisible by 2^(i+1) either 94 break; 95 } 96 } 97 98 masterPool.extractEntropy(buf); 99 } 100 101 /// Accumulate an entropy event. 102 /// 103 /// Params: 104 /// sourceID = A number assigned to the source. 105 /// pool = The pool to add the entropy. 0 <= pool < Accumulator.pools 106 /// data = Entropy data. 107 @safe 108 void addEntropy(in ubyte sourceID, in size_t pool, in ubyte[] data...) 109 in { 110 assert(pool < pools, "Pool ID out of range."); 111 } 112 body { 113 ubyte[5] iBuf; // contains sourceID and length of event data 114 115 // pack sourceID and data.length in buffer 116 iBuf[0] = sourceID; 117 toLittleEndian(cast(uint)data.length, iBuf[1..5]); 118 119 entropyPools[pool].addEntropy(iBuf); 120 entropyPools[pool].addEntropy(data); 121 } 122 123 /// Provides synchronized access to the accumulator. 124 /// Used to add entropy or to extract entropy or both at the same time. 125 /// 126 /// Params: 127 /// sourceID = the ID of the entropy source. 128 /// pool = The pool to add the entropy. 129 /// data = Entropy data. Can be `null`. 130 /// buf = 32 bytes buffer for random data. Can also be `null`. 131 @trusted 132 private synchronized void transaction(in ubyte sourceID, in size_t pool, in ubyte[] data, ubyte[] buf = null) { 133 if(data !is null) { 134 (cast(Accumulator) this).addEntropy(sourceID, pool, data); 135 } 136 if(buf !is null) { 137 (cast(Accumulator) this).extractEntropy(buf); 138 } 139 } 140 141 private { 142 EntropyPool!DRNG[pools] entropyPools; 143 EntropyPool!DRNG masterPool; 144 uint counter = 0; // count how many times extractEntropy() has been called 145 } 146 147 148 } 149 150 @safe 151 private struct EntropyPool(DRNG) 152 if(isDRNGWithInput!DRNG) { 153 154 private DRNG accumulator; 155 private uint freshEntropyBytes = 0; 156 157 nothrow @nogc: 158 159 /// Extract a block of entropy bits out of this pool. 160 /// The internal state is not leaked. 161 /// 162 /// Returns: Slice pointing to the extracted data 163 ubyte[] extractEntropy(ubyte[] oBuf) 164 body { 165 freshEntropyBytes = 0; // out of fresh entropy 166 167 accumulator.nextBytes(oBuf); 168 169 return oBuf; 170 } 171 172 /// accumulate some bytes in the entropy pool 173 /// Params: 174 /// b = the entropy to add 175 void addEntropy(in ubyte[] b...) { 176 accumulator.addSeed(b); 177 freshEntropyBytes += b.length; 178 } 179 180 /// Returns: the number of bytes that have flown in this pool since the last call of extractEntropy(). 181 @property 182 uint freshEntropy() { 183 return freshEntropyBytes; 184 } 185 186 ~this() { 187 wipe(accumulator); 188 } 189 }