1
0
mirror of https://github.com/taigrr/arduinolibs synced 2025-01-18 04:33:12 -08:00

Random number generator class based on ChaCha

This commit is contained in:
Rhys Weatherley 2015-03-01 12:08:35 +10:00
parent 4fc27f1005
commit 6ec1b93cf9
14 changed files with 1410 additions and 24 deletions

View File

@ -753,6 +753,7 @@ IMAGE_PATH = ../libraries/BlinkLED/examples/Cylon \
../libraries/LCD/examples/HelloWorld \
../libraries/LCD/examples/Form \
../libraries/RTC/examples/AlarmClock \
../libraries/Crypto \
../libraries/DMD \
../libraries/IR \
../libraries/I2C

View File

@ -30,6 +30,7 @@
\li Block cipher modes: CTR, CFB, CBC, OFB
\li Stream ciphers: ChaCha
\li Hash algorithms: SHA1, SHA256, BLAKE2s
\li Random number generation: \link RNGClass RNG\endlink, TransistorNoiseSource
All cryptographic algorithms have been optimized for 8-bit Arduino platforms
like the Uno. Memory usage is also reduced, particularly for SHA1 and SHA256

View File

@ -94,6 +94,7 @@ realtime clock and the LCD library to implement an alarm clock.
\li Block cipher modes: CTR, CFB, CBC, OFB
\li Stream ciphers: ChaCha
\li Hash algorithms: SHA1, SHA256, BLAKE2s
\li Random number generation: \link RNGClass RNG\endlink, TransistorNoiseSource
More information can be found on the \ref crypto "Cryptographic Library" page.

View File

@ -0,0 +1,105 @@
/*
* 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 "NoiseSource.h"
#include "RNG.h"
/**
* \class NoiseSource NoiseSource.h <NoiseSource.h>
* \brief Abstract base class for random noise sources.
*
* \sa \link RNGClass RNG\endlink, TransistorNoiseSource
*/
/**
* \brief Constructs a new random noise source.
*/
NoiseSource::NoiseSource()
{
}
/**
* \brief Destroys this random noise source.
*/
NoiseSource::~NoiseSource()
{
}
/**
* \fn bool NoiseSource::calibrating() const
* \brief Determine if the noise source is still calibrating itself.
*
* \return Returns true if calibration is in progress; false if the noise
* source is generating valid random data.
*
* Noise sources that require calibration start doing so at system startup
* and then switch over to random data generation once calibration is complete.
* Since no random data is being generated during calibration, the output
* from \link RNGClass::rand() RNG.rand()\endlink may be predictable.
* Use \link RNGClass::available() RNG.available()\endlink to determine
* when sufficient entropy is available to generate good random values.
*
* It is possible that the noise source never exits calibration. This can
* happen if the input voltage is insufficient to trigger noise or if the
* noise source is not connected. Noise sources may also periodically
* recalibrate themselves.
*
* \sa stir()
*/
/**
* \fn void NoiseSource::stir()
* \brief Stirs entropy from this noise source into the global random
* number pool.
*
* This function should call output() to add the entropy from this noise
* source to the global random number pool.
*
* The noise source should batch up the entropy data, providing between
* 16 and 48 bytes of data each time. If the noise source does not have
* sufficient entropy data at the moment, it should return without stiring
* the current data in.
*
* \sa calibrating(), output()
*/
/**
* \brief Called from subclasses to output noise to the global random
* number pool.
*
* \param data Points to the noise data.
* \param len Number of bytes of noise data.
* \param credit The number of bits of entropy to credit for the data.
* Note that this is bits, not bytes.
*
* The default implementation of this function calls
* \link RNGClass::stir() RNG.stir()\endlink to add the entropy from
* this noise source to the global random number pool.
*
* This function may be overridden by subclasses to capture the raw
* output from the noise source before it is mixed into the pool to
* allow the raw data to be analyzed for randomness.
*/
void NoiseSource::output(const uint8_t *data, size_t len, unsigned int credit)
{
RNG.stir(data, len, credit);
}

View File

@ -0,0 +1,42 @@
/*
* 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.
*/
#ifndef CRYPTO_NOISESOURCE_H
#define CRYPTO_NOISESOURCE_H
#include <inttypes.h>
#include <stddef.h>
class NoiseSource
{
public:
NoiseSource();
virtual ~NoiseSource();
virtual bool calibrating() const = 0;
virtual void stir() = 0;
protected:
virtual void output(const uint8_t *data, size_t len, unsigned int credit);
};
#endif

540
libraries/Crypto/RNG.cpp Normal file
View File

@ -0,0 +1,540 @@
/*
* 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 "RNG.h"
#include "NoiseSource.h"
#include "ChaCha.h"
#include "Crypto.h"
#include "utility/ProgMemUtil.h"
#include <Arduino.h>
#include <avr/eeprom.h>
#include <string.h>
/**
* \class RNGClass RNG.h <RNG.h>
* \brief Pseudo random number generator suitable for cryptography.
*
* Random number generators must be seeded properly before they can
* be used or an adversary may be able to predict the random output.
* Seed data may be:
*
* \li Device-specific, for example serial numbers or MAC addresses.
* \li Application-specific, unique to the application. The tag that is
* passed to begin() is an example of an application-specific value.
* \li Noise-based, generated by a hardware random number generator
* that provides unpredictable values from a noise source.
*
* The following example demonstrates how to initialise the random
* number generator:
*
* \code
* #include <SPI.h>
* #include <Ethernet.h>
* #include <Crypto.h>
* #include <RNG.h>
* #include <TransistorNoiseSource.h>
*
* // Noise source to seed the random number generator.
* TransistorNoiseSource noise(A1);
*
* // MAC address for Ethernet communication.
* byte mac_address[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
*
* void setup() {
* // Initialize the Ethernet shield.
* Ethernet.begin(mac_address);
*
* // 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);
*
* // Stir in the Ethernet MAC address.
* RNG.stir(mac_address, sizeof(mac_address));
*
* // ...
* }
* \endcode
*
* The application should regularly call stir() to mix in new data from
* the noise source and also regularly call loop():
*
* \code
* 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
*
* The loop() function will automatically save the random number seed on a
* regular basis. By default the seed is saved every hour but this can be
* changed using setAutoSaveTime().
*
* Keep in mind that saving too often may cause the EEPROM to wear out quicker.
* It is wise to limit saving to once an hour or once a day depending
* upon how long you intend to field the device before replacing it.
* For example, an EEPROM rated for 100k erase/write cycles will last about
* 69 days saving once a minute or 11 years saving once an hour.
*
* The application can still elect to call save() at any time if wants.
* For example, if the application can detect power loss or shutdown
* conditions programmatically, then it may make sense to force a save()
* of the seed upon shutdown.
*
* \sa NoiseSource
*/
/**
* \brief Global random number generator instance.
*
* \sa RNGClass
*/
RNGClass RNG;
/**
* \var RNGClass::SEED_SIZE
* \brief Size of a saved random number seed in EEPROM space.
*/
// Number of ChaCha hash rounds to use for random number generation.
#define RNG_ROUNDS 20
// Force a rekey after this many blocks of random data.
#define RNG_REKEY_BLOCKS 16
// Maximum entropy credit that can be contained in the pool.
#define RNG_MAX_CREDITS 384
/** @cond */
// Tag for 256-bit ChaCha20 keys. This will always appear in the
// first 16 bytes of the block. The remaining 48 bytes are the seed.
static const char tagRNG[16] PROGMEM = {
'e', 'x', 'p', 'a', 'n', 'd', ' ', '3',
'2', '-', 'b', 'y', 't', 'e', ' ', 'k'
};
// Initialization seed. This is the ChaCha20 output of hashing
// "expand 32-byte k" followed by 48 bytes set to the numbers 1 to 48.
// The ChaCha20 output block is then truncated to the first 48 bytes.
//
// This value is intended to start the RNG in a semi-chaotic state if
// we don't have a previously saved seed in EEPROM.
static const uint8_t initRNG[48] PROGMEM = {
0xB0, 0x2A, 0xAE, 0x7D, 0xEE, 0xCB, 0xBB, 0xB1,
0xFC, 0x03, 0x6F, 0xDD, 0xDC, 0x7D, 0x76, 0x67,
0x0C, 0xE8, 0x1F, 0x0D, 0xA3, 0xA0, 0xAA, 0x1E,
0xB0, 0xBD, 0x72, 0x6B, 0x2B, 0x4C, 0x8A, 0x7E,
0x34, 0xFC, 0x37, 0x60, 0xF4, 0x1E, 0x22, 0xA0,
0x0B, 0xFB, 0x18, 0x84, 0x60, 0xA5, 0x77, 0x72
};
/** @endcond */
/**
* \brief Constructs a new random number generator instance.
*
* This constructor must be followed by a call to begin() to
* properly initialize the random number generator.
*
* \sa begin()
*/
RNGClass::RNGClass()
: address(0)
, credits(0)
, firstSave(1)
, timer(0)
, timeout(3600000UL) // 1 hour in milliseconds
{
}
/**
* \brief Destroys this random number generator instance.
*/
RNGClass::~RNGClass()
{
clean(block);
clean(stream);
}
/**
* \brief Initializes the random number generator.
*
* \param tag A string that is stirred into the random pool at startup;
* usually this should be a value that is unique to the application and
* version such as "MyApp 1.0" so that different applications do not
* generate the same sequence of values upon first boot.
* \param eepromAddress The EEPROM address to load the previously saved
* seed from and to save new seeds when save() is called. There must be
* at least SEED_SIZE (49) bytes of EEPROM space available at the address.
*
* This function should be followed by calls to stir() to mix in
* additional entropy data from noise sources to initialize the random
* number generator properly.
*
* \sa stir(), save()
*/
void RNGClass::begin(const char *tag, int eepromAddress)
{
// Save the EEPROM address for use by save().
address = eepromAddress;
// Initialize the ChaCha20 input block from the saved seed.
memcpy_P(block, tagRNG, sizeof(tagRNG));
memcpy_P(block + 4, initRNG, sizeof(initRNG));
if (eeprom_read_byte((const uint8_t *)address) == 'S') {
// We have a saved seed: XOR it with the initialization block.
for (int posn = 0; posn < 12; ++posn) {
block[posn + 4] ^=
eeprom_read_dword((const uint32_t *)(address + posn * 4 + 1));
}
}
// No entropy credits for the saved seed.
credits = 0;
// Trigger an automatic save once the entropy credits max out.
firstSave = 1;
// Rekey the random number generator immediately.
rekey();
// Stir in the supplied tag data but don't credit any entropy to it.
if (tag)
stir((const uint8_t *)tag, strlen(tag));
// Re-save the seed to obliterate the previous value and to ensure
// that if the system is reset without a call to save() that we won't
// accidentally generate the same sequence of random data again.
save();
}
/**
* \brief Sets the amount of time between automatic seed saves.
*
* \param minutes The number of minutes between automatic seed saves.
*
* The default time between automatic seed saves is 1 hour.
*
* This function is intended to help with EEPROM wear by slowing down how
* often seed data is saved as noise is stirred into the random pool.
* The exact period to use depends upon how long you intend to field
* the device before replacing it. For example, an EEPROM rated for
* 100k erase/write cycles will last about 69 days saving once a minute
* or 11 years saving once an hour.
*
* \sa save(), stir()
*/
void RNGClass::setAutoSaveTime(uint16_t minutes)
{
if (!minutes)
minutes = 1; // Just in case.
timeout = ((uint32_t)minutes) * 60000U;
}
/**
* \brief Generates random bytes into a caller-supplied buffer.
*
* \param data Points to the buffer to fill with random bytes.
* \param len Number of bytes to generate.
*
* Calling this function will decrease the amount of entropy in the
* random number pool by \a len * 8 bits. If there isn't enough
* entropy, then this function will still return \a len bytes of
* random data generated from what entropy it does have.
*
* If the application requires a specific amount of entropy before
* generating important values, the available() function can be
* polled to determine when sufficient entropy is available.
*
* \sa available(), stir()
*/
void RNGClass::rand(uint8_t *data, size_t len)
{
// Decrease the amount of entropy in the pool.
if (len > (credits / 8))
credits = 0;
else
credits -= len * 8;
// Generate the random data.
uint8_t count = 0;
while (len > 0) {
// Force a rekey if we have generated too many blocks in this request.
if (count >= RNG_REKEY_BLOCKS) {
rekey();
count = 1;
} else {
++count;
}
// Increment the low counter word and generate a new keystream block.
++(block[12]);
ChaCha::hashCore(stream, block, RNG_ROUNDS);
// Copy the data to the return buffer.
if (len < 64) {
memcpy(data, stream, len);
break;
} else {
memcpy(data, stream, 64);
data += 64;
len -= 64;
}
}
// Force a rekey after every request.
rekey();
}
/**
* \brief Determine if there is sufficient entropy available for a
* specific request size.
*
* \param len The number of bytes of random data that will be requested
* via a call to rand().
* \return Returns true if there is at least \a len * 8 bits of entropy
* in the random number pool, or false if not.
*
* This function can be used by the application to wait for sufficient
* entropy to become available from the system's noise sources before
* generating important values. For example:
*
* \code
* bool haveKey = false;
* byte key[32];
*
* void loop() {
* ...
*
* if (!haveKey && RNG.available(sizeof(key))) {
* RNG.rand(key, sizeof(key));
* haveKey = true;
* }
*
* ...
* }
* \endcode
*
* If \a len is larger than the maximum number of entropy credits supported
* by the random number pool (384 bits, 48 bytes), then the maximum will be
* used instead. For example, asking if 512 bits (64 bytes) are available
* will return true if in reality only 384 bits are available. If this is a
* problem for the application's security requirements, then large requests
* for random data should be broken up into smaller chunks with the
* application waiting for the entropy pool to refill between chunks.
*
* \sa rand()
*/
bool RNGClass::available(size_t len) const
{
if (len >= (RNG_MAX_CREDITS / 8))
return credits >= RNG_MAX_CREDITS;
else
return len <= (credits / 8);
}
/**
* \brief Stirs additional entropy data into the random pool.
*
* \param data Points to the additional data to be stirred in.
* \param len Number of bytes to be stirred in.
* \param credit The number of bits of entropy to credit for the
* data that is stirred in. Note that this is bits, not bytes.
*
* The maximum credit allowed is \a len * 8 bits, indicating that
* every bit in the input \a data is good and random. Practical noise
* sources are rarely that good, so \a credit will usually be smaller.
* For example, to credit 2 bits of entropy per byte, the function
* would be called as follows:
*
* \code
* RNG.stir(noise_data, noise_bytes, noise_bytes * 2);
* \endcode
*
* If \a credit is zero, then the \a data will be stirred in but no
* entropy credit is given. This is useful for static values like
* serial numbers and MAC addresses that are different between
* devices but highly predictable.
*
* \sa loop()
*/
void RNGClass::stir(const uint8_t *data, size_t len, unsigned int credit)
{
// Increase the entropy credit.
if ((credit / 8) >= len)
credit = len * 8;
if ((RNG_MAX_CREDITS - credits) > credit)
credits += credit;
else
credits = RNG_MAX_CREDITS;
// Process the supplied input data.
if (len > 0) {
// XOR the data with the ChaCha input block in 48 byte
// chunks and rekey the ChaCha cipher for each chunk to mix
// the data in. This should scatter any "true entropy" in
// the input across the entire block.
while (len > 0) {
size_t templen = len;
if (templen > 48)
templen = 48;
uint8_t *output = ((uint8_t *)block) + 16;
len -= templen;
while (templen > 0) {
*output++ ^= *data++;
--templen;
}
rekey();
}
} else {
// There was no input data, so just force a rekey so we
// get some mixing of the state even without new data.
rekey();
}
// Save if this is the first time we have reached max entropy.
// This provides some protection if the system is powered off before
// the first auto-save timeout occurs.
if (firstSave && credits >= RNG_MAX_CREDITS) {
firstSave = 0;
save();
}
}
/**
* \brief Stirs in data from a noise source into the random pool.
*
* \param source The noise source to obtain entropy data from.
*
* \sa save(), NoiseSource::stir()
*/
void RNGClass::stir(NoiseSource &source)
{
source.stir();
}
/**
* \brief Saves the random seed to EEPROM.
*
* During system startup, noise sources typically won't have accumulated
* much entropy. But startup is usually the time when the system most
* needs to generate random data for session keys, IV's, and the like.
*
* The purpose of this function is to pass some of the accumulated entropy
* from one session to the next after a loss of power. Thus, once the system
* has been running for a while it will get progressively better at generating
* random values and the accumulated entropy will not be completely lost.
*
* Normally it isn't necessary to call save() directly. The loop() function
* will automatically save the seed on a periodic basis (default of 1 hour).
*
* The seed that is saved is generated in such a way that it cannot be used
* to predict random values that were generated previously or subsequently
* in the current session. So a compromise of the EEPROM contents of a
* captured device should not result in compromise of random values
* that have already been generated. However, if power is lost and the
* system restarted, then there will be a short period of time where the
* random state will be predictable from the seed. For this reason it is
* very important to stir() in new noise data at startup.
*
* \sa loop(), stir()
*/
void RNGClass::save()
{
// Generate random data from the current state and save
// that as the seed. Then force a rekey.
++(block[12]);
ChaCha::hashCore(stream, block, RNG_ROUNDS);
eeprom_write_block(stream, (void *)(address + 1), 48);
eeprom_update_byte((uint8_t *)address, 'S');
rekey();
timer = millis();
}
/**
* \brief Run periodic housekeeping tasks on the random number generator.
*
* This function must be called on a regular basis from the application's
* main "loop()" function.
*/
void RNGClass::loop()
{
// Save the seed if the auto-save timer has expired.
if ((millis() - timer) >= timeout)
save();
}
/**
* \brief Destroys the data in the random number pool and the saved seed
* in EEPROM.
*
* This function attempts to throw away any data that could theoretically be
* used to predict previous and future outputs of the random number generator
* if the device is captured, sold, or otherwise compromised.
*
* After this function is called, begin() must be called again to
* re-initialize the random number generator, followed by stir() to
* add in new entropy from system noise sources.
*
* \note The rand() and save() functions take some care to manage the
* random number pool in a way that makes prediction of past outputs from a
* captured state very difficult. Future outputs may be predictable if
* noise or other high-entropy data is not mixed in with stir() on a
* regular basis.
*
* \sa begin()
*/
void RNGClass::destroy()
{
clean(block);
clean(stream);
for (int posn = 0; posn < SEED_SIZE; ++posn)
eeprom_write_byte((uint8_t *)(address + posn), 0xFF);
}
/**
* \brief Rekeys the random number generator.
*/
void RNGClass::rekey()
{
// Rekey the cipher for the next request by generating a new block.
// This is intended to make it difficult to wind the random number
// backwards if the state is captured later. The first 16 bytes of
// "block" remain set to "tagRNG".
++(block[12]);
ChaCha::hashCore(stream, block, RNG_ROUNDS);
memcpy(block + 4, stream, 48);
// Permute the high word of the counter using the system microsecond
// counter to introduce a little bit of non-stir randomness for each
// request. Note: If random data is requested on a predictable schedule
// then this may not help very much. It is still necessary to stir in
// high quality entropy data on a regular basis using stir().
block[13] ^= micros();
}

69
libraries/Crypto/RNG.h Normal file
View File

@ -0,0 +1,69 @@
/*
* 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.
*/
#ifndef CRYPTO_RNG_h
#define CRYPTO_RNG_h
#include <inttypes.h>
#include <stddef.h>
class NoiseSource;
class RNGClass
{
public:
RNGClass();
~RNGClass();
void begin(const char *tag, int eepromAddress);
void setAutoSaveTime(uint16_t minutes);
void rand(uint8_t *data, size_t len);
bool available(size_t len) const;
void stir(const uint8_t *data, size_t len, unsigned int credit = 0);
void stir(NoiseSource &source);
void save();
void loop();
void destroy();
static const int SEED_SIZE = 49;
private:
uint32_t block[16];
uint32_t stream[16];
int address;
uint16_t credits : 15;
uint16_t firstSave : 1;
unsigned long timer;
unsigned long timeout;
void rekey();
};
extern RNGClass RNG;
#endif

View File

@ -0,0 +1,258 @@
/*
* 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;
}

View File

@ -0,0 +1,55 @@
/*
* 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.
*/
#ifndef CRYPTO_TRANSISTORNOISESOURCE_H
#define CRYPTO_TRANSISTORNOISESOURCE_H
#include <inttypes.h>
#include "NoiseSource.h"
class TransistorNoiseSource : public NoiseSource
{
public:
explicit TransistorNoiseSource(uint8_t pin);
virtual ~TransistorNoiseSource();
bool calibrating() const;
void stir();
private:
int threshold;
uint8_t _pin;
uint8_t prevBit;
uint8_t posn;
uint8_t bitNum;
uint8_t calState;
uint8_t buffer[32];
int minValue;
int maxValue;
int count;
int ones;
void restart();
};
#endif

View File

@ -27,6 +27,7 @@ This example runs tests on the ChaCha implementation to verify correct behaviour
#include <Crypto.h>
#include <ChaCha.h>
#include <string.h>
#include <avr/pgmspace.h>
#define MAX_PLAINTEXT_SIZE 64
#define MAX_CIPHERTEXT_SIZE 64
@ -50,7 +51,7 @@ struct TestVector
// specification doesn't contain test vectors - these were generated
// using the reference implementation from http://cr.yp.to/chacha.html.
static TestVector const testVectorChaCha20_128 = {
static TestVector const testVectorChaCha20_128 PROGMEM = {
.name = "ChaCha20 128-bit",
.key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
.keySize = 16,
@ -76,7 +77,7 @@ static TestVector const testVectorChaCha20_128 = {
.size = 64
};
static TestVector const testVectorChaCha20_256 = {
static TestVector const testVectorChaCha20_256 PROGMEM = {
.name = "ChaCha20 256-bit",
.key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
201, 202, 203, 204, 205, 206, 207, 208, 209, 210,
@ -104,7 +105,7 @@ static TestVector const testVectorChaCha20_256 = {
.size = 64
};
static TestVector const testVectorChaCha12_128 = {
static TestVector const testVectorChaCha12_128 PROGMEM = {
.name = "ChaCha12 128-bit",
.key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
.keySize = 16,
@ -130,7 +131,7 @@ static TestVector const testVectorChaCha12_128 = {
.size = 64
};
static TestVector const testVectorChaCha12_256 = {
static TestVector const testVectorChaCha12_256 PROGMEM = {
.name = "ChaCha12 256-bit",
.key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
201, 202, 203, 204, 205, 206, 207, 208, 209, 210,
@ -158,7 +159,7 @@ static TestVector const testVectorChaCha12_256 = {
.size = 64
};
static TestVector const testVectorChaCha8_128 = {
static TestVector const testVectorChaCha8_128 PROGMEM = {
.name = "ChaCha8 128-bit",
.key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
.keySize = 16,
@ -184,7 +185,7 @@ static TestVector const testVectorChaCha8_128 = {
.size = 64
};
static TestVector const testVectorChaCha8_256 = {
static TestVector const testVectorChaCha8_256 PROGMEM = {
.name = "ChaCha8 256-bit",
.key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
201, 202, 203, 204, 205, 206, 207, 208, 209, 210,
@ -212,6 +213,8 @@ static TestVector const testVectorChaCha8_256 = {
.size = 64
};
TestVector testVector;
ChaCha chacha;
byte buffer[128];
@ -272,6 +275,9 @@ void testCipher(ChaCha *cipher, const struct TestVector *test)
{
bool ok;
memcpy_P(&testVector, test, sizeof(TestVector));
test = &testVector;
Serial.print(test->name);
Serial.print(" ... ");
@ -291,20 +297,15 @@ void testCipher(ChaCha *cipher, const struct TestVector *test)
Serial.println("Failed");
}
// The data space of this sketch is too big if we try to test the
// performance of all of setKey(), encrypt(), and decrypt().
// Since decryption is almost identical to encryption, only test
// that if the PERF_DECRYPT option is enabled, suppressing setKey().
//#define PERF_DECRYPT 1
#if !defined(PERF_DECRYPT)
void perfCipherSetKey(ChaCha *cipher, const struct TestVector *test)
{
unsigned long start;
unsigned long elapsed;
int count;
memcpy_P(&testVector, test, sizeof(TestVector));
test = &testVector;
Serial.print(test->name);
Serial.print(" SetKey ... ");
@ -322,14 +323,15 @@ void perfCipherSetKey(ChaCha *cipher, const struct TestVector *test)
Serial.println(" per second");
}
#endif
void perfCipherEncrypt(ChaCha *cipher, const struct TestVector *test)
{
unsigned long start;
unsigned long elapsed;
int count;
memcpy_P(&testVector, test, sizeof(TestVector));
test = &testVector;
Serial.print(test->name);
Serial.print(" Encrypt ... ");
@ -348,14 +350,15 @@ void perfCipherEncrypt(ChaCha *cipher, const struct TestVector *test)
Serial.println(" bytes per second");
}
#if defined(PERF_DECRYPT)
void perfCipherDecrypt(ChaCha *cipher, const struct TestVector *test)
{
unsigned long start;
unsigned long elapsed;
int count;
memcpy_P(&testVector, test, sizeof(TestVector));
test = &testVector;
Serial.print(test->name);
Serial.print(" Decrypt ... ");
@ -374,17 +377,11 @@ void perfCipherDecrypt(ChaCha *cipher, const struct TestVector *test)
Serial.println(" bytes per second");
}
#endif
void perfCipher(ChaCha *cipher, const struct TestVector *test)
{
#if !defined(PERF_DECRYPT)
perfCipherSetKey(cipher, test);
perfCipherEncrypt(cipher, test);
#else
perfCipherEncrypt(cipher, test);
perfCipherDecrypt(cipher, test);
#endif
}
void setup()

View File

@ -0,0 +1,44 @@
// This example dumps the raw data from a transistor noise source
// without any of the whitening normally performed by the RNG class.
#include <Crypto.h>
#include <TransistorNoiseSource.h>
char const hexchars[] = "0123456789ABCDEF";
class RawNoiseSource : public TransistorNoiseSource
{
public:
RawNoiseSource(uint8_t pin) : TransistorNoiseSource(pin) {}
protected:
void output(const uint8_t *data, size_t len, unsigned int credit)
{
for (size_t posn = 0; posn < len; ++posn) {
uint8_t value = data[posn];
Serial.print(hexchars[(value >> 4) & 0x0F]);
Serial.print(hexchars[value & 0x0F]);
}
Serial.println();
}
};
RawNoiseSource noise(A1);
bool calibrating = true;
void setup() {
Serial.begin(9600);
Serial.println();
Serial.println("calibrating");
}
void loop() {
noise.stir();
bool nowCalibrating = noise.calibrating();
if (nowCalibrating != calibrating) {
calibrating = nowCalibrating;
if (calibrating)
Serial.println("calibrating");
}
}

View File

@ -0,0 +1,70 @@
// Example of initializing and using the random number generator.
#include <Crypto.h>
#include <RNG.h>
#include <TransistorNoiseSource.h>
// Change "MyApp 1.0" to some other tag for your application
// so that different applications will generate different results
// even if the input noise or seed data is otherwise identical.
#define RNG_APP_TAG "MyApp 1.0"
// EEPROM address to save the random number seed at.
#define RNG_EEPROM_ADDRESS 500
// Noise source to seed the random number generator.
TransistorNoiseSource noise(A1);
bool calibrating = false;
byte data[32];
unsigned long startTime;
void setup() {
Serial.begin(9600);
Serial.println("start");
// Initialize the random number generator.
RNG.begin(RNG_APP_TAG, RNG_EEPROM_ADDRESS);
startTime = millis();
}
void printHex(const byte *data, unsigned len)
{
static char const hexchars[] = "0123456789ABCDEF";
unsigned long time = millis() - startTime;
Serial.print(time / 1000);
Serial.print('.');
Serial.print((time / 100) % 10);
Serial.print(": ");
while (len > 0) {
int b = *data++;
Serial.print(hexchars[(b >> 4) & 0x0F]);
Serial.print(hexchars[b & 0x0F]);
--len;
}
Serial.println();
}
void loop() {
// Track changes to the calibration state on the noise source.
bool newCalibrating = noise.calibrating();
if (newCalibrating != calibrating) {
calibrating = newCalibrating;
if (calibrating)
Serial.println("calibrating");
}
// 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();
// Generate output whenever 32 bytes of entropy have been accumulated.
if (RNG.available(sizeof(data))) {
RNG.rand(data, sizeof(data));
printHex(data, sizeof(data));
}
}

View File

@ -0,0 +1,203 @@
#FIG 3.2 Produced by xfig version 3.2.5c
Landscape
Center
Metric
A4
100.00
Single
-2
1200 2
0 32 #404040
0 33 #808080
0 34 #c0c0c0
0 35 #e0e0e0
0 36 #808080
0 37 #c0c0c0
0 38 #e0e0e0
0 39 #444444
0 40 #8e8f8e
6 3465 4275 3690 4590
5 1 0 1 0 -1 0 0 -1 0.000 1 0 0 0 3600.000 4600.000 3510 4480 3600 4450 3690 4480
2 1 0 1 0 -1 0 0 -1 0.000 0 1 -1 0 0 2
3600 4450 3600 4590
2 1 0 1 0 -1 0 0 -1 0.000 0 1 -1 0 0 2
3510 4410 3690 4410
2 1 0 1 0 -1 0 0 -1 0.000 0 1 -1 0 0 2
3600 4410 3600 4275
2 1 0 1 0 -1 0 0 -1 0.000 0 1 -1 0 0 2
3536 4304 3536 4374
2 1 0 1 0 -1 0 0 -1 0.000 0 1 -1 0 0 2
3506 4339 3566 4339
-6
6 4725 4500 5220 4950
1 3 0 1 0 -1 0 0 -1 0.000 1 0.0000 4995 4725 186 186 4995 4725 5040 4905
2 1 0 1 0 -1 0 0 20 0.000 0 0 -1 0 0 4
5095 4870 5050 4780 5005 4825 5095 4870
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
4950 4725 5175 4950
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
4950 4725 5175 4500
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
4950 4590 4950 4860
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
4950 4725 4725 4725
-6
6 4680 3600 4770 4050
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
4725 3955 4725 4050
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 7
4725 3690 4685 3717 4765 3771 4685 3825 4765 3879 4685 3933
4725 3960
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
4725 3600 4725 3695
-6
6 4230 4500 4725 4950
1 3 0 1 0 -1 0 0 -1 0.000 1 0.0000 4455 4725 186 186 4455 4725 4410 4545
2 1 0 1 0 -1 0 0 20 0.000 0 0 -1 0 0 4
4355 4580 4400 4670 4445 4625 4355 4580
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
4500 4725 4275 4500
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
4500 4725 4275 4950
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
4500 4860 4500 4590
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
4500 4725 4725 4725
-6
6 5490 4185 5805 4365
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
5625 4275 5490 4275
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
5670 4275 5805 4275
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
5625 4365 5625 4185
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
5670 4365 5670 4185
-6
6 6075 4050 6570 4500
1 3 0 1 0 -1 0 0 -1 0.000 1 0.0000 6345 4275 186 186 6345 4275 6390 4455
2 1 0 1 0 -1 0 0 20 0.000 0 0 -1 0 0 4
6445 4420 6400 4330 6355 4375 6445 4420
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
6300 4275 6525 4500
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
6300 4275 6525 4050
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
6300 4140 6300 4410
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
6300 4275 6075 4275
-6
6 5895 3600 5985 4050
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
5940 3955 5940 4050
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 7
5940 3690 5900 3717 5980 3771 5900 3825 5980 3879 5900 3933
5940 3960
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
5940 3600 5940 3695
-6
6 3555 3330 3645 3420
1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 3600 3375 30 30 3600 3375 3600 3405
-6
6 4680 3330 4770 3420
1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 4725 3375 30 30 4725 3375 4725 3405
-6
6 5895 3330 5985 3420
1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 5940 3375 30 30 5940 3375 5940 3405
-6
6 5895 4230 5985 4320
1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 5940 4275 30 30 5940 4275 5940 4305
-6
6 5130 4230 5220 4320
1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 5175 4275 30 30 5175 4275 5175 4305
-6
6 4680 4230 4770 4320
1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 4725 4275 30 30 4725 4275 4725 4305
-6
6 5130 5355 5220 5445
1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 5175 5400 30 30 5175 5400 5175 5430
-6
6 3555 5355 3645 5445
1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 3600 5400 30 30 3600 5400 3600 5430
-6
6 6480 5355 6570 5445
1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 6525 5400 30 30 6525 5400 6525 5430
-6
6 7155 4635 7245 5085
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
7200 4990 7200 5085
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 7
7200 4725 7160 4752 7240 4806 7160 4860 7240 4914 7160 4968
7200 4995
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
7200 4635 7200 4730
-6
6 7155 3510 7245 3960
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
7200 3865 7200 3960
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 7
7200 3600 7160 3627 7240 3681 7160 3735 7240 3789 7160 3843
7200 3870
2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2
7200 3510 7200 3605
-6
6 7155 4005 7245 4095
1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 7200 4050 30 30 7200 4050 7200 4080
-6
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
5175 4950 5175 5400
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
3600 4545 3600 5400
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
3600 4275 3600 3375
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
4725 4050 4725 4275
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
4275 4950 4275 5085
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4
4275 4500 4275 4275 5175 4275 5175 4500
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
4725 3600 4725 3375
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
6525 4500 6525 5400
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
5490 4275 5130 4275
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
5760 4275 6120 4275
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
5940 4050 5940 4275
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
5940 3645 5940 3375
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
7200 5085 7200 5400
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
7200 3510 7200 3375
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2
1 1 1.00 60.00 120.00
6525 4050 7650 4050
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
7200 3915 7200 4635
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 1 2
8 1 1.00 60.00 120.00
2925 5400 7200 5400
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 1 2
8 1 1.00 60.00 120.00
2925 3375 7200 3375
4 0 0 50 -1 0 12 0.0000 4 135 420 3015 4455 10uF\001
4 0 0 50 -1 0 12 0.0000 4 135 345 6705 3780 10K\001
4 0 0 50 -1 0 12 0.0000 4 135 390 6705 4905 4.7K\001
4 0 0 50 -1 0 12 0.0000 4 135 435 5400 3870 1.5M\001
4 0 0 50 -1 0 12 0.0000 4 135 390 4185 3915 4.7K\001
4 0 0 50 -1 0 12 0.0000 4 135 345 3060 4680 25V\001
4 0 0 50 -1 0 12 0.0000 4 135 525 5400 4545 100nF\001
4 0 0 50 -1 0 12 0.0000 4 165 240 5265 4770 Q2\001
4 0 0 50 -1 0 12 0.0000 4 165 240 6615 4320 Q3\001
4 0 0 50 -1 0 12 0.0000 4 135 270 3915 5175 NC\001
4 0 0 50 -1 0 12 0.0000 4 165 240 3960 4770 Q1\001
4 0 0 50 -1 0 12 0.0000 4 135 405 2385 5490 GND\001
4 0 0 50 -1 0 12 0.0000 4 180 1185 7785 4005 To Analog Pin\001
4 0 0 50 -1 0 12 0.0000 4 135 960 7875 4230 On Arduino\001
4 0 0 50 -1 0 12 0.0000 4 180 3735 3330 5805 All transistors: BC548, 2N3904, or equivalent\001
4 0 0 50 -1 0 12 0.0000 4 180 4440 3015 6075 Collector of Q1 must be left not connected to anything\001
4 0 0 50 -1 0 12 0.0000 4 135 810 2070 3600 10 to 15V\001
4 0 0 50 -1 0 12 0.0000 4 135 330 2295 3375 VIN\001

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB