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.

Opening an old fire alarm for its Americium-241 ionizing source would be useless since it is an alpha emitter [and low energy gamma rays of 60 keV]. (alpha can't get into my detector and most gamma pass right through my detector unnoticed) [An average smoke detector for domestic use contains about 0.29 micrograms of Am-241 (in the form of americium dioxide), so its activity is around 37,000 becquerel (or about 1 µCurie)]. But in terms of danger factor, versus happiness - I'll give it a very wide berth.

I could scour antique shops looking for old radioactive consumer products like Fiesta Ware or Vaseline/Uranium Glass but I do not feel the need to add radioactive items into my living environment.

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.

background radiation (where I live)
~ one decay event every 3 seconds
~18 counts per minute (CPM) [0.122 μSievert]
~0.29 counts per second (CPS)
~0.0725 bits/second
~0.009 bytes/second
~ 0.54 bytes/minute
~783 bytes/day

LO-SALT [66% Potassium Chloride] emits Beta particles from K-40
~ three decay events every one second
~161 counts per minute (CPM) [~1.06 μSievert]
~2.68 counts per second (CPS)
~0.67 bits/second
~0.084 bytes/second
~ 5.05 bytes/minute 
~7260 bytes/day
 




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:
// http://www.rhelectronics.net/store/radiation-detector-geiger-counter-diy-kit-second-edition.html
// https://github.com/revspace/geiger
// http://forum.arduino.cc/index.php?topic=336041.25;wap2
#define LOG_PERIOD 60000 //Logging period in milliseconds, recommended value 15000-60000.
#define MAX_PERIOD 60000 //Maximum logging period without modifying this sketch

unsigned long counts;     //variable for GM Tube events
unsigned long cpm;        //variable for CPM
unsigned int multiplier;  //variable for calculation CPM in this sketch
unsigned long previousMillis;  //variable for time measurement
float uSv;            // the measured microSiverts
float ratio = 151.5; // 151 CPM = 1uSv/h for M4011 GM Tube

void tube_impulse(){               //procedure for capturing events from Geiger Kit
  counts++;
}

void setup(){                                               //setup procedure
  counts = 0;
  cpm = 0;
  multiplier = MAX_PERIOD / LOG_PERIOD;  //calculating multiplier, depend on your log period
  Serial.begin(115200);                                    // start serial monitor
 // uncommennt if you have time-out problem to connect with Radiation Logger
 //  delay(2000);
 //  Serial.write('0'); // sending zero to avoid connection time out with radiation logger
 //  delay(2000);
 //  Serial.write('0'); // sending zero to avoid connection time out with radiation logger
  pinMode(2, INPUT);    // set pin INT0 input for capturing GM Tube events
  digitalWrite(2, LOW); // turn on internal pullup resistors, solder C-INT on the PCB
  attachInterrupt(0, tube_impulse, FALLING);  //define external interrupts
}

 

void loop(){                                               //main cycle
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > LOG_PERIOD){
    previousMillis = currentMillis;
    cpm = counts * multiplier;    
    Serial.print(cpm);
    Serial.print(' CPM ');
    uSv = cpm / ratio ;
    Serial.print(uSv); 
    Serial.println(' uSv ');
    counts = 0;
  }
}


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 low

const int GMpin = 2;
const int LEDpin = 13;
int LEDstate=HIGH;
unsigned long triggeredTime[4]; 
unsigned char bits;
unsigned long duration1, duration2;
int flipper = 0; // flip bit that gets toggled every time a new bit is generated. 
int shift = 0; // track how many bits have we shifted


void setup() {
 Serial.begin(115200);
 pinMode(GMpin, INPUT);
 digitalWrite(LEDpin,LEDstate); 
 pinMode(LEDpin, OUTPUT);
 digitalWrite(GMpin,LOW); // turn on internal pullup resistor
}


void loop() {
// 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->1H
 while (digitalRead(GMpin) == LOW) {}; // wait here while the GM is low.
 triggeredTime[0] = micros(); // first decay has occurred
 digitalWrite(LEDpin,LEDstate); // toggle the LED once for each new bit
 LEDstate=!LEDstate;
// Wait 1H->2L (~350uS)
 while (digitalRead(GMpin) == HIGH) {}; // wait here while the GM is high ~350uS
// Wait 2L->3H
 while (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->5H
 while (digitalRead(GMpin) == LOW) {}; // wait here while the GM is low.
 triggeredTime[2] = micros(); // third decay has occurred
 digitalWrite(LEDpin,LEDstate); // toggle the LED once for each new bit
 LEDstate=!LEDstate;
// Wait 5H->6L (~350uS)
 while (digitalRead(GMpin) == HIGH) {}; // wait here while the GM is high ~350uS
// Wait 6L->7H
 while (digitalRead(GMpin) == LOW) {}; // wait here while the GM is low.
 triggeredTime[3] = micros(); // fourth decay has occurred
 
 duration1=triggeredTime[1]-triggeredTime[0]; // first two decay events 3H-1H
 duration2=triggeredTime[3]-triggeredTime[2]; // second two decay events 7H-5H
 if (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 zero
       Serial.print("0");    // if it is then print the leading zero.
     Serial.print(bits, HEX); // this command does not print out leading zeroes
     shift=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.

No comments:

Post a comment