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 }