Making a Rainbow
Now that we know about Hunk
s, and most importantly, the raindrop
/evaporate
functions,
we can actually build some Rainbow
s.
When a proof is found, the proof must come with some parameters used to create the
Rainbow
. Without the parameters, the proof is invalid (in fact, it can't be validated
at all!)
These parameters are:
#![allow(unused)] fn main() { pub struct RainbowParams { /// A unique, random value. rainbow_id: Hunk, /// Your public key. miner: Hunk, /// The developers public key. dev: Hunk, /// dev_fee/256 * block rewards goes to developer. You get the rest. dev_fee: u8, /// The compression level, 1-32. z: u8, ... }
Note: You'll notice a dev/dev_fee parameter. The reference implemnation will not require a dev fee for rainbow tables. However, I want to make sure other developers can cleanly implement a dev fee. Dev fees are very common in the crypto world, and it would be nice if they weren't random, and both the dev and miner got what they agreed upon in each found block. By putting the fee here, it guarantees that this
Rainbow
will always use this dev fee structure.
However, in addition to those parameters, we must also compute a value called fast_hash
.
This is technically not required, but speeds up generating the rainbow table significantly.
#![allow(unused)] fn main() { ... fast_hash: Hunk, } impl RainbowParams { /// Combine all the parameters into one hash. fn compute_fast_hash(&mut self) { self.fast_hash = Hunk::combine( Hunk::combine( self.rainbow_id, self.miner, ), Hunk::combine( self.dev.combine_hash(self.dev_fee), Hunk::hash(self.z as u32), ) ) } /// Use the raindrop function of fast_hash as our hashing /// algorithm for _this_ rainbow table. fn raindrop(&self, value: u32) -> Hunk { self.fast_hash.raindrop(value) } } }
The Rainbow Chain (not the chain in Blockchain)
We will first create a bunch of chains. A chain is a starting value: start: u32
, and a final value, end: u32
.
We will start with a bunch of arbitrary numbers for start
. To find end
, we will take the raindrop
of start (which
will give us a Hunk
), evaporate
that Hunk
(to get a u32
), then take the raindrop
of that u32
... and so on, calling
raindrop
then evaporate
then raindrop
... until the u32
part matches the terminating condition.
The terminating condition will be, when the first self.z
bits of u32
are all 0
s, that u32
is the end
.
#![allow(unused)] fn main() { impl RainbowParams { pub fn terminator(&self, value: u32) -> bool { value.leading_zeros() >= self.z } } }
This computation will look like the following:
start -> a = raindrop(start) -> b = evaporate(a) => c = raindrop(b) -> d = evaporate(c) -> e = raindrop(d) -> f = evaporate(e)
until the final value has self.z
0's at the start. We will then throw out all the middle-bits, because we can regenerate
them at any time with just the start
value.
Saving the rainbow for later
We will keep a separate sqlite database for the RainbowParams
structure. Each RainbowParams
will be given a sequential,
incrementing id starting at 0. This is important for some space-saving tricks.
Once the RainbowParams
are saved, and the ID is correct, all we actually need to do is append the start
value in a file
called end
.
The index in the file will bring us back to the current RainbowParams
, and the end
is the key we want to look up by.
This end
file will now contain a start
value for all rainbow tables. When we read from it,
we will get many results, which is many chances to win! And, that read is now sequential, rather
than random, so we can read even more!
If a particular Rainbow
does not have an end
, we place a dummy value in the file. We can
filter it out later. It doesn't matter what the dummy value is.