Sunday, 31 January 2016

Random Numbers from Salt (Radiation)

I bought a cheap Geiger Counter Kit from China to generate some random numbers. I wired it up to an Arduino Uno and wrote a basic sketch (lots plagiarised, but I have references back to their sources in the code) that generates random numbers from radioactive decay events. This is something that should, by its very nature be totally chaotic and not be predictable using classical or even quantum physics.

The basic theory is to record the time of 4 consecutive detected decay events. The time interval between the first two events will be greater than the time interval between the second two events 50% of the time on average. Allocate a 1 bit if this occurs and allocate a 0 bit if the opposite occurs [first interval is less than the second interval].

Wiring

Arduino 5V <-> middle pin of the three on the left side of the detector (labelled 5V).
Arduino Ground <-> top pin of the three on the left side of the detector (labelled GND).
Arduino Pin 2 <-> bottom pin of the three on the left side of the detector (labelled VIN)
I also removed jumper J1 on the detector to stop it clicking. The jumper actually still on the board but only on one pin, instead of bridging the both pins as it would by default.

One nice thing about this setup is that the Arduino is powering up the Geiger Counter Kit (both together are using about 0.04A @ 5.00 V =0.2 Watts - measured by a "USB Charger Doctor").

Radiation sources

The Geiger Müller tube used is sensitive to Beta and Gamma radiation (M4011), so I did a quick search for sources of radiation that I would feel comfortable owning, or ordering online and having delivered (for some odd reason I can picture the postman freaking out carrying a package with a big yellow radiation warning sticker on it).

Bananas have so little Potassium-40 in them, to be nearly invisible from background radiation (which is very comforting if you like to eat Bananas) - 0.098
μSv. And since the human body strictly controls the percentage of Potassium at a constant level, you should not let this information distract you from eating bananas their goodness far out ways any negative.

You can call me crazy but I do not really feel comfortable with most radiation sources, so for my test I decided to use background radiation and some easily accessible Potassium-40 which should be around about 10x background levels.

I only used two sources for my radiation. One thing to keep in mind is that my Geiger Müller tube itself is slightly radioactive, just like everything, so I get about 0.2 pulses/second from the tube.

I could always check into using Caesium-137 or a Strontium-90 source at some
later date. But in all honesty I think that I'll go in a different
direction for generating random numbers. The sources I would feel comfortable using are not all that cheap as well.

Here is the sketch that I used to measure the CPM of the background and LO-SALT and print the result every 60 seconds:

And, here is the sketch that I ran on my Arduino to generate the random numbers:

// This code generates random numbers based on nuclear decay events, // In my mind they would be the highest quality of random numbers.// The only downside is that it is very very very very very slow. //// Using only background radiation of about 18CPM it would take// about 30 hours to generate 1k of random goodness.// Using low-sodium kitchen salt, you can get about 10x background radiation.// Using LoSalt (66% potassium Chloride) 161CPM it would take about // 202 minutes to generate 1k of random goodness.// Using some crazy lethal radiation source of 170,000CPM (60*1000000/350)// (maximum with my GM tube recovery time of ~350uS) it would take about // 200 minutes to generate 1MiB of random numbers//// This is one of the reasons that I would not use a GM tube as my final solution // to generate high quality truly random numbers, but it is interesting to test.//// Using background where I am is about 18 CPM I can generate about 0.53 bytes/minute// Using Lo-Salt (66% Potassium Chloride) 161 CPM I can generate about 5 bytes/minute//// 2016-01-31// Remove all elegance and make it dumb, just bit bang pins until they change state.// For optimal timing resolution sometimes doing things as basic as // possible is the most efficient technique.//// 2016-01-30// This is a rewrite to gather 4 decays and generate one bit of randomness without// using interrupts also we will use the rising edge instead of the falling edge as// the trigger for a new decay This is so that code is running while the GM tube // is recovering from the previous decay event//// 2016-01-13// some critical code from // https://www.fourmilab.ch/hotbits/source/hotbits-c3.html// and some code is from // https://github.com/revspace/geiger// and some useful info from// http://tinkerman.eldiariblau.net/geiger-counter///// The Geiger Counter is connected to pin 2 // each pulse every time a decay is detected about ~350 microseconds in duration // I measured that with the following very basic code:// unsigned long array[101];
// int counter=0;
//
// void setup(){
// Serial.begin(115200);
// pinMode(2, INPUT);
//}
//
//void loop(){
// array[counter++]=pulseIn(2,LOW);
// if(counter > 100)
// for(;--counter;counter>1)
// Serial.println(array[counter]);
//}// For my Geiger Counter Kit it's default is low, it pulses high for ~350uS // on a decay event and returns to lowconstintGMpin=2;constintLEDpin=13;intLEDstate=HIGH;unsignedlongtriggeredTime[4];unsignedcharbits;unsignedlongduration1,duration2;intflipper=0;// flip bit that gets toggled every time a new bit is generated. intshift=0;// track how many bits have we shiftedvoidsetup(){Serial.begin(115200);pinMode(GMpin,INPUT);

digitalWrite(LEDpin,LEDstate);pinMode(LEDpin,OUTPUT);

digitalWrite(GMpin,LOW);// turn on internal pullup resistor}voidloop(){// 4 highs and 4 lows is one bit, or 4 independent decay events// 0L-(unknown)->1H-(~350uS)->2L-(unknown)->3H-(~350uS)->4L// 4L-(unknown)->5H-(~350uS)->6L-(unknown)-7H->(~350uS)->0L//// Wait 0L->1Hwhile(digitalRead(GMpin)==LOW){};// wait here while the GM is low.triggeredTime[0]=micros();// first decay has occurreddigitalWrite(LEDpin,LEDstate);// toggle the LED once for each new bitLEDstate=!LEDstate;// Wait 1H->2L (~350uS)while(digitalRead(GMpin)==HIGH){};// wait here while the GM is high ~350uS// Wait 2L->3Hwhile(digitalRead(GMpin)==LOW){};// wait here while the GM is low.triggeredTime[1]=micros();// second decay has occurred// Wait 3H->4L (~350uS)while(digitalRead(GMpin)==HIGH){};// wait here while the GM is high ~350uS// Wait 4L->5Hwhile(digitalRead(GMpin)==LOW){};// wait here while the GM is low.triggeredTime[2]=micros();// third decay has occurreddigitalWrite(LEDpin,LEDstate);// toggle the LED once for each new bitLEDstate=!LEDstate;// Wait 5H->6L (~350uS)while(digitalRead(GMpin)==HIGH){};// wait here while the GM is high ~350uS// Wait 6L->7Hwhile(digitalRead(GMpin)==LOW){};// wait here while the GM is low.triggeredTime[3]=micros();// fourth decay has occurredduration1=triggeredTime[1]-triggeredTime[0];// first two decay events 3H-1Hduration2=triggeredTime[3]-triggeredTime[2];// second two decay events 7H-5Hif(duration1!=duration2){/* There remains the possibility of a very slight bias due to long-term effects such as ionisation of a Geiger tube, poisoning of a silicon detector, or (absurdly small for any practical source) decrease in radioactivity of the source over time. To mitigate this, we invert the sense of the magnitude test between the first and second samples for alternate samples. This pushes the effect of any long-term bias to a much higher order effect. */flipper^=1;bits=(bits<<1)|(flipper^(duration1>duration2));shift++;if(shift>7){// we have bits 0-7 collected, time to display the two nibbles.if((bits&0xF0)==0x00)// is the first nibble zeroSerial.print("0");// if it is then print the leading zero.Serial.print(bits,HEX);// this command does not print out leading zeroesshift=0;// time to start collecting a new byte.}}// I could just assume that my code above will take longer than ~350uS // (5600 clock cycles @ 16MHz] which would be a bad assumption. So I added the // following line just to be safe. The worse case is that I miss one decay event.// Wait 7H->0L (~350uS)while(digitalRead(GMpin)==HIGH){};// wait here while the GM is high ~350uS}

The above code sends hex digits one byte at a time to the serial port. To capture the random numbers and store them to file I used the following commands on Linux "screen -L /dev/ttyACM0 115200" [^A D - to exit the screen session and return to the console] this creates a logfile called screenlog.0 which captures all data coming to the USB serial port from the Arduino. Or you could use "(stty -F /dev/ttyACM0 raw ispeed 115200 ; cat > random.txt) < /dev/ttyACM0"

And to convert them from ASCII into binary I used the following UNIX command:
$ cat screenlog.0 | xxd -r -p - random.bin

And this binary file can be tested with rngtest if the filesize is at least 2500 bytes (20000 bits). Unfortunately since the randomness generation rate is low, it would take multiple centuries to generate the Gigabytes of data required for one run of the full dieharder randomness test suite, using a single Geiger Müller tube even with the most radioactive source possible. So as much as it was fun playing with a Geiger counter, for generating random numbers, I feel that it is a dead end, at least for me. The quality of the randomness it fantastic but the generation rate is far too low, at least for my needs.