mirror of
https://github.com/taigrr/arduinolibs
synced 2025-01-18 04:33:12 -08:00
259 lines
9.1 KiB
C++
259 lines
9.1 KiB
C++
/*
|
|
* Copyright (C) 2015 Southern Storm Software, Pty Ltd.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "TransistorNoiseSource.h"
|
|
#include "RNG.h"
|
|
#include "Crypto.h"
|
|
#include <Arduino.h>
|
|
|
|
/**
|
|
* \class TransistorNoiseSource TransistorNoiseSource.h <TransistorNoiseSource.h>
|
|
* \brief Processes the signal from a transistor-based noise source.
|
|
*
|
|
* This class processes input from a transistor-based noise source, such as
|
|
* that described by <a href="http://robseward.com/misc/RNG2/">Rob Seward</a>.
|
|
* See that Web page for full details on how such noise sources work,
|
|
* how the output should be used, and caveats for the unwary.
|
|
* For convenience, Rob's circuit is reproduced below:
|
|
*
|
|
* \image html transistor_noise_source.png
|
|
*
|
|
* The following example shows how to initialize a transistor-based noise
|
|
* source and use it with \link RNGClass RNG\endlink. The noise is read
|
|
* from the A1 pin on the Arduino and stirred into the random number pool
|
|
* on a regular basis. For more information, see the documentation for
|
|
* \link RNGClass RNG\endlink.
|
|
*
|
|
* \code
|
|
* #include <Crypto.h>
|
|
* #include <RNG.h>
|
|
* #include <TransistorNoiseSource.h>
|
|
*
|
|
* // Noise source to seed the random number generator.
|
|
* TransistorNoiseSource noise(A1);
|
|
*
|
|
* void setup() {
|
|
* // Initialize the random number generator with the application tag
|
|
* // "MyApp 1.0" and load the previous seed from EEPROM address 500.
|
|
* RNG.begin("MyApp 1.0", 500);
|
|
*
|
|
* // ...
|
|
* }
|
|
*
|
|
* void loop() {
|
|
* // ...
|
|
*
|
|
* // If the noise source has accumulated new entropy, then stir it in.
|
|
* RNG.stir(noise);
|
|
*
|
|
* // Perform regular housekeeping on the random number generator.
|
|
* RNG.loop();
|
|
*
|
|
* // ...
|
|
* }
|
|
* \endcode
|
|
*
|
|
* \sa \link RNGClass RNG\endlink, NoiseSource
|
|
*/
|
|
|
|
/*
|
|
|
|
Theory of operation:
|
|
|
|
From Rob Seward's original design we need to find the median of the input
|
|
signal. That is, the threshold at which half the signal is below the
|
|
threshold (a zero) and the other half is above the threshold (a one).
|
|
Rob used a histogram table to find the median which is memory-intensive.
|
|
We cannot afford to spend that much memory finding the median.
|
|
|
|
In this implementation we divide the signal up into "buckets" of 1024
|
|
samples. We pick a starting threshold and count the number of ones
|
|
within the bucket. If the number of ones is between 45% to 55% of the
|
|
total number of samples, then we use that threshold to convert the
|
|
bucket into output bits. Otherwise we adjust the threshold up or down,
|
|
discard the bucket, and try again.
|
|
|
|
After a few buckets, the threshold naturally settles at the median without
|
|
needing a histogram. The rest of the bucket processing can be done online
|
|
with storage needed only for the debiased output bits.
|
|
|
|
If the input voltage to the noise source is too low to generate noise,
|
|
then the delta between the minimum and maximum samples in the bucket will
|
|
be quite small. This is used to detect disconnection of the noise source.
|
|
No output is generated when the noise source is disconnected.
|
|
|
|
With 1024 raw input samples we get roughly 256 output bits after
|
|
Von Neumann debiasing. As a further check, the output will be discarded
|
|
if less than 192 bits are generated. This can happen when the noise source
|
|
is connected or disconnected: only part of the bucket is valid.
|
|
|
|
One of the properties of Rob's circuit design is that over time the median
|
|
changes due to environmental factors and component wear. Because we adjust
|
|
the threshold from bucket to bucket, it should naturally float up or down
|
|
to the new median level as the circuit's properties change.
|
|
|
|
*/
|
|
|
|
// Number of ADC values that can be generated by analogRead().
|
|
#define ADC_NUM 1024
|
|
|
|
// Number of samples to collect for a single noise "bucket".
|
|
#define SAMPLES_NUM 1024
|
|
|
|
// Calculate a percentage of the sample bucket size.
|
|
#define SAMPLES_PCT(num) ((int)(((long)SAMPLES_NUM) * (num) / 100L))
|
|
|
|
// Expected spread between the minimum and maximum ADC readings for
|
|
// the noise source to be considered as operating correctly.
|
|
#define NOISE_SPREAD (ADC_NUM / 8)
|
|
|
|
// Calibration states.
|
|
#define NOISE_NOT_CALIBRATING 0
|
|
#define NOISE_CALIBRATING 1
|
|
|
|
/**
|
|
* \brief Constructs a new transitor-based noise source handler.
|
|
*
|
|
* \param pin The analog input pin that the noise will appear on.
|
|
*/
|
|
TransistorNoiseSource::TransistorNoiseSource(uint8_t pin)
|
|
: threshold(ADC_NUM / 2)
|
|
, _pin(pin)
|
|
, calState(NOISE_CALIBRATING)
|
|
{
|
|
// Configure the pin as an analog input with no pull-up.
|
|
pinMode(pin, INPUT);
|
|
digitalWrite(pin, LOW);
|
|
|
|
// Start the bit collection routines.
|
|
restart();
|
|
}
|
|
|
|
TransistorNoiseSource::~TransistorNoiseSource()
|
|
{
|
|
restart();
|
|
}
|
|
|
|
bool TransistorNoiseSource::calibrating() const
|
|
{
|
|
return calState != NOISE_NOT_CALIBRATING;
|
|
}
|
|
|
|
void TransistorNoiseSource::stir()
|
|
{
|
|
// Keep track of the minimum and maximum while generating data
|
|
// so that we can detect when the input voltage falls too low
|
|
// for the circuit to generate noise.
|
|
int value = analogRead(_pin);
|
|
if (value < minValue)
|
|
minValue = value;
|
|
if (value > maxValue)
|
|
maxValue = value;
|
|
|
|
// Collect two bits of input and remove bias using the Von Neumann method.
|
|
// If both bits are the same, then discard both. Otherwise choose one
|
|
// of the bits and output that one. We have to do this carefully so that
|
|
// instruction timing does not reveal the value of the bit that is chosen.
|
|
uint8_t bit = ((threshold - value) >> 15) & 1; // Subtract and extract sign.
|
|
if (count & 1) {
|
|
if (prevBit ^ bit) {
|
|
// The bits are different: add the new bit to the buffer.
|
|
if (posn < sizeof(buffer)) {
|
|
buffer[posn] = (buffer[posn] << 1) | bit;
|
|
if (++bitNum >= 8) {
|
|
++posn;
|
|
bitNum = 0;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
prevBit = bit;
|
|
}
|
|
|
|
// Keep a count of the number of raw 1 bits.
|
|
ones += bit;
|
|
|
|
// Bail out if we haven't collected enough samples for a full bucket yet.
|
|
if (++count < SAMPLES_NUM)
|
|
return;
|
|
|
|
// If the maximum minus the minimum is too small, then there probably
|
|
// is no signal or the input voltage is insufficient to generate noise.
|
|
// Discard the entire bucket and return to calibration.
|
|
if ((maxValue - minValue) < NOISE_SPREAD) {
|
|
restart();
|
|
calState = NOISE_CALIBRATING;
|
|
threshold = ADC_NUM / 2; // Reacquire threshold when the signal returns.
|
|
return;
|
|
}
|
|
|
|
// If the number of 1's is between 45% and 55% of the total count,
|
|
// then we have a good bucket. The threshold is at an appropriate level.
|
|
if (ones >= SAMPLES_PCT(45) && ones <= SAMPLES_PCT(55)) {
|
|
if (posn >= (sizeof(buffer) * 3 / 4)) {
|
|
// The buffer is at least three-quarters full of debiased bits
|
|
// so pass them onto output(). There may be less bits if we
|
|
// lost or gained the signal half-way through the bucket.
|
|
// Credit 4 bits of entropy for every 8 bits of output.
|
|
output(buffer, posn, posn * 4);
|
|
}
|
|
restart();
|
|
calState = NOISE_NOT_CALIBRATING;
|
|
return;
|
|
}
|
|
|
|
// The threshold is not close enough to the mid-point of the signal.
|
|
// Adjust the threshold, discard the bucket, and try again.
|
|
if (ones < SAMPLES_PCT(25) || ones > SAMPLES_PCT(75)) {
|
|
// We are a long way away from the mid-point, so move the threshold
|
|
// by a large amount based on the delta to get closer quicker.
|
|
threshold -= (SAMPLES_PCT(50) - ones) / 8;
|
|
} else if (ones < SAMPLES_PCT(50)) {
|
|
// Not enough ones so move the threshold down a bit.
|
|
--threshold;
|
|
} else {
|
|
// Too many ones so move the threshold up a bit.
|
|
++threshold;
|
|
}
|
|
if (threshold < 0)
|
|
threshold = 0;
|
|
else if (threshold >= ADC_NUM)
|
|
threshold = ADC_NUM - 1;
|
|
restart();
|
|
calState = NOISE_CALIBRATING;
|
|
}
|
|
|
|
/**
|
|
* \brief Restarts the bit collection process for the next bucket.
|
|
*/
|
|
void TransistorNoiseSource::restart()
|
|
{
|
|
clean(buffer);
|
|
prevBit = 0;
|
|
posn = 0;
|
|
bitNum = 0;
|
|
minValue = ADC_NUM - 1;
|
|
maxValue = 0;
|
|
count = 0;
|
|
ones = 0;
|
|
}
|