This text file discusses the ecbs algorithm mainly using ecbs8() as the example, but it all applies the same to ecbs64(), just with everything 8 times bigger.

#----------------------------------------------------------------------------
# testecbs8.readme
#----------------------------------------------------------------------------

ecbs8 is a variable state size algorithm using only 8 bit unsigned chars
for the algortihm to reduce as much as possible to a minimm state size
in order to expose weaknesses and to study the effectiveness of the
various components.

ecbs8 is composed of LFSR elements, taps, counters, a shift counter,
and some structural constants, and in ecbs8, some semiconstants,
variables that normally do not change, but may change rarely.

The algorithm generally goes like this.

   1  increment taps
   2  increment 4 adders as one giant 256 bit unsigned integer
   3  add adder values to the elements selected by four of the 8 taps.
      These taps don't matter, and they can be plus minus XOR or
      multiply, but I avoid multiply because its supposedly slow.
   4  update lfsr[tap1] with formula using tap1, tap2, tap3, and tap4.
   5  decrement shift, rolling from 0 to some tap element & 7. If it's
      still 0, then set to 7. Value zero is not used.
   6  rotate right lfsr[tap1] shift bits
   7  use lfsr elements of two selected taps to select a tap to swap
   8  swap selected tap value with its neighbor to the right, cycling
      back to swap with tap1 when tap8 is selected. (There are 8 taps,
      named tapa1 to tapa8)
   9  update lfsr[tap5] with formula using tap5, tap6, tap7, and tap8
  10  decrement shift, rolling from 0 to some tap element & 7. If it's
      still 0, then set to 7. Value zero is not used.
  11  rotate lfsr[tap5] shift bits
  12  check reset value and call ecbs8setsize if match.
  13  return(lfsra[tapa1] ^ lfsra[tapa5]);
 

          So where a traditional LFSR does

increment taps
do formaula
return value of tap1

                    ecbs8 does

increment taps
increment adders
mix adders into LFSR
do formula for lfsra[tapa1]
rotate lfsr[tap1]
swap taps
do formula for lfsra[tapa5]
rotate lfsr[tap5]
check resize and possibly call ecbs8setsize
return value (lfsr[tap1] ^ lfsr[tap5])


ecbs8 has 8 taps and 9 - 127 lfsr elements. Each tap points to
an element of the LFSR. The tap values MUST be unique.

Each tap value is decremented and when it drops below 0, is reset
to the last LFSR element. Apart from being unique, and within the
range 0 to last element, there is no restrictions on tap values.
They do not need to be uniformly distributed, or irreducible primitive
polynomials modulo 2. The tap values do have an effect on period. They
can make shorter or longer underlying periods, but that's irrelevant
because it's overcome by tap swaps and counters. Also, because the
formulas use ADD, SUB, MULT and XOR, it's really a NLFSR but I just
use LFSR from force of habit. But it's nonlinear and chaotic and so
tap values don't matter. This is not a maximal length LFSR, but its
got enough bits that get used enough to make for very long overall
periods.

ecbs8 also has four 8 bit counters used together as a single monotonic
counter. The variable names are adder1, adder2, adder3, and adder4. I did
not name them "counter*" because I might use counter or counter* for
some other debugging tasks. adder1 is incremented on every call to
ecbs8. When adder1 rolls over from 255 to 0, then adder2 is incremented.
When adder2 rolls over, adder3 is incremented, and so on.

Regardless whether the adders are incremented or not, their values are
added to LFSR elements based on the newly incremented tap values.
This means they are easy seed targets along with LFSR elements, since
starting values don't matter.

Now we are ready for a standard LFSR update in which we XOR, ADD,
and MULTIPLY LFSR elements based on tap values and add them to the
primary tap LFSR element. This is the first formula line. There is
quite a bit to learn about the formula and its effects when combining
ADD, XOR and MULTIPLY. I don't use SUBTRACT because its effects are
identical to ADD. How you parenthesize the formulas doesn't make much
difference and can generate many different formulas consisting of
identical components.

In the traditional basic LFSR algorithm, the primary tap is updated all
in one long formula, and that value is returned by the function call,
or maybe XOR'd with something such as a counter, and returned.

In ecbs8, we have two of those formula lines, one for taps 1-4 and
a second one for taps 5-8. In between, those updates we do two things.
First the LFSR element of tapa1 is rotated based on a shift value which
is a 3 bit decrementing variable. 

After that we also have another thing unique to ecbs8. We select one of
the taps, based on XOR of two LFSR elements, and swap its contents with
its neighbor tap to the right, cycling back to tap1 when tap8 is selected.

Additionally we also rotate a secondary tap, tap5, decrementing and
reusing the same shift variable to specify how many bits to rotate.
This second rotate uses the same shift variable but since we only use
the 7 possible rotate amounts, the rotate values alternate between
the tap1 and tap5 rotates so that each one gets all possible rotate
values, in order as shown below. These are swaping the values of the
taps which are indexes into the LFSR array.
   rotate amount 7 6 5 4 3 2 1 7 6 5 4 3 2 1 7 6 5 4 3 2 1
       which tap 1 5 1 5 1 5 1 5 1 5 1 5 1 5 1 5 1 5 1 5 1
           tap5>   6   4   2   7   5   3   1   6   4   2   7   5   3   1
           tap1> 7   5   3   1   6   4   2   7   5   3   1   6   4   2
So they use the same rotate values in the same order, just offset by 3.
The shift variable is not randomized because that seems to cause
correlations, and we have too much memory access already anyway. The
benefit is not worth the computation needed.

UPDATE! I changed my mind and added random shift variable by using some
lfsr element AND with 7 and if still 0, then reset to 7. This should
produce a very long shift period.

So where a normal LFSR with no fancy stuff, just the absolute minimum,
looks like this

unsigned char lfsr8 (void) {

    ++tap1;
    if (tap1 > length) {
        tap1 = 0;
    }
    ++tap2;
    if (tap2 > length) {
        tap2 = 0;
    }
    ++tap3;
    if (tap3 > length) {
        tap3 = 0;
    }
    ++tap4;
    if (tap4 > length) {
        tap4 = 0;
    }
    ++tap5;
    if (tap5 > length) {
        tap5 = 0;
    }
    ++tap6;
    if (tap6 > length) {
        tap6 = 0;
    }
    ++tap7;
    if (tap7 > length) {
        tap7 = 0;
    }
    ++tap8;
    if (tap8 > length) {
        tap8 = 0;
    }
    lfsr[tap1] ^= ( lfsr[tap2] ^ lfsr[tap3] ^ lfsr[tap4] ^ lfsr[tap5] ^
                    lfsr[tap6] ^ lfsr[tap7] ^ lfsr[tap8] );
    return( lfsr[tap1] ^ lfsr[tap5]);
}

ecbs8 looks like this. Just scroll down and skim through it to get
the feel for what it does. I'll discuss some interesting aspects below.

/* function implementation */
unsigned char ecbs8 (void) {
unsigned char swap;
unsigned char tmp;
static char shift = 7;

    --tapa1;
    if (tapa1 > maxtap) {
        tapa1 = maxtap;
    }
    --tapa2;
    if (tapa2 > maxtap) {
        tapa2 = maxtap;
    }
    --tapa3;
    if (tapa3 > maxtap) {
        tapa3 = maxtap;
    }
    --tapa4;
    if (tapa4 > maxtap) {
        tapa4 = maxtap;
    }
    --tapa5;
    if (tapa5 > maxtap) {
        tapa5 = maxtap;
    }
    --tapa6;
    if (tapa6 > maxtap) {
        tapa6 = maxtap;
    }
    --tapa7;
    if (tapa7 > maxtap) {
        tapa7 = maxtap;
    }
    --tapa8;
    if (tapa8 > maxtap) {
        tapa8 = maxtap;
    }

    ++adder1;
    if (adder1  == 0) {
        ++adder2;
        if (adder2  == 0) {
            ++adder3;
            if (adder3  == 0) {
                ++adder4;
            }
        }
    }
    lfsra[tapa2] += adder1;
    lfsra[tapa3] += adder2;
    lfsra[tapa6] += adder3;
    lfsra[tapa7] += adder4;

    lfsra[tapa1] += ((lfsra[tapa1] ^  (lfsra[tapa2] * (lfsra[tapa3]|1))) + lfsra[tapa4]);

    --shift;
    if (shift == 0) {
        shift = (lfsra[6] & 7);
        if (shift == 0) {
            shift = 7;
        }
    }
    lfsra[tapa1] = ((lfsra[tapa1] >> shift) | (lfsra[tapa1] << (8-shift)));

    swap = (lfsra[tapa2] ^ lfsra[tapa3]) & 7;
    switch (swap) {
    case 0: tmp = tapa1;
        tapa1 = tapa2;
        tapa2 = tmp;
        break;
    case 1: tmp = tapa2;
        tapa2 = tapa3;
        tapa3 = tmp;
        break;
    case 2: tmp = tapa3;
        tapa3 = tapa4;
        tapa4 = tmp;
        break;
    case 3: tmp = tapa4;
        tapa4 = tapa5;
        tapa5 = tmp;
        break;
    case 4: tmp = tapa5;
        tapa5 = tapa6;
        tapa6 = tmp;
        break;
    case 5: tmp = tapa6;
        tapa6 = tapa7;
        tapa7 = tmp;
        break;
    case 6: tmp = tapa7;
        tapa7 = tapa8;
        tapa8 = tmp;
        break;
    case 7: tmp = tapa8;
        tapa8 = tapa1;
        tapa1 = tmp;
        break;
    default: break;
    }

    lfsra[tapa1] += ((lfsra[tapa5] ^  (lfsra[tapa6] * (lfsra[tapa7]|1))) + lfsra[tapa8]);

    --shift;
    if (shift == 0) {
        shift = (lfsra[1] & 7);
        if (shift == 0) {
            shift = 7;
        }
    }
    lfsra[tapa5] = ((lfsra[tapa5] >> shift) | (lfsra[tapa5] << (8-shift)));

/*
   This whole thing is because of using 8bit unsigned chars to match
   match values. In the 64bit version it's just a single line.
*/
    switch (reset) {
    case 1 : 
        if ( (lfsra[tapa1] ^ 255) == power) {
            setsize = (lfsra[tapa8] + MINLEN + 1) & (MAXLEN-1);
            setsize |= (MAXLEN/2);
            ecbs8setsize(setsize);
        }
        break;
    case 2 : 
        if ( ((lfsra[tapa1] ^ 255) == 0) &&
             ((lfsra[tapa2] & power) == power)) {
            setsize = (lfsra[tapa8] + MINLEN + 1) & (MAXLEN-1);
            setsize |= (MAXLEN/2);
            ecbs8setsize(setsize);
        }
        break;
    case 3 : 
        if ( ((lfsra[tapa1] ^ 255) == 0) && ((lfsra[tapa2] ^ 255) == 0) &&
             ((lfsra[tapa3] & power) == power) ) {
            setsize = (lfsra[tapa8] + MINLEN + 1) & (MAXLEN-1);
            setsize |= (MAXLEN/2);
            ecbs8setsize(setsize);
        }
        break;
    case 4 : 
        if ( ((lfsra[tapa1] & 255) == 0) && ((lfsra[tapa2] & 255) == 0) &&
             ((lfsra[tapa3] & 255) == 0) &&
             ((lfsra[tapa8] & power) == power) ) {
            
            setsize = (lfsra[tapa8] + MINLEN + 1) & (MAXLEN-1);
            setsize |= (MAXLEN/2);
            ecbs8setsize(setsize);
        }
        break;
    case 5 : 
        if ( ((lfsra[tapa1] & 255) == 0) && ((lfsra[tapa2] & 255) == 0) &&
             ((lfsra[tapa3] & 255) == 0) && ((lfsra[tapa4] & 255) == 0) &&
             ((lfsra[tapa5] & power) == power) ) {
            setsize = (lfsra[tapa8] + MINLEN + 1) & (MAXLEN-1);
            setsize |= (MAXLEN/2);
            ecbs8setsize(setsize);
        }
        break;
    case 6 : 
        if ( ((lfsra[tapa1] & 255) == 0) && ((lfsra[tapa2] & 255) == 0) &&
             ((lfsra[tapa3] & 255) == 0) && ((lfsra[tapa4] & 255) == 0) &&
             ((lfsra[tapa5] & 255) == 0) && 
             ((lfsra[tapa6] & power) == power) ) {
            setsize = (lfsra[tapa8] + MINLEN + 1) & (MAXLEN-1);
            setsize |= (MAXLEN/2);
            ecbs8setsize(setsize);
        }
        break;
    case 7 : 
        if ( ((lfsra[tapa1] & 255) == 0) && ((lfsra[tapa2] & 255) == 0) &&
             ((lfsra[tapa3] & 255) == 0) && ((lfsra[tapa4] & 255) == 0) &&
             ((lfsra[tapa5] & 255) == 0) &&  ((lfsra[tapa6] & 255) == 0)  &&
             ((lfsra[tapa7] & power) == power) ) {
            setsize = (lfsra[tapa8] + MINLEN + 1) & (MAXLEN-1);
            setsize |= (MAXLEN/2);
            ecbs8setsize(setsize);
        }
        break;
    case 8 : 
        if ( ((lfsra[tapa1] & 255) == 0) && ((lfsra[tapa2] & 255) == 0) &&
             ((lfsra[tapa3] & 255) == 0) && ((lfsra[tapa4] & 255) == 0) &&
             ((lfsra[tapa5] & 255) == 0) &&  ((lfsra[tapa6] & 255) == 0)  &&
             ((lfsra[tapa7] & 255) == 0) &&
             ((lfsra[tapa8] & power) == 0) ) {
            setsize = ((adder1+adder2+adder3+adder4) + MINLEN + 1);
            setsize |= (MAXLEN/2);
            ecbs8setsize(setsize);
        }
        break;
    default : break;
    }
    return(lfsra[tapa1] ^ lfsra[tapa5]);

} /* END ecbs8 */


Ignore the big switch (reset) junk at the end for now. I'll get to it later.

First of all, I use decrements for the taps and shift register because
it should be easier for the compiler to optimize that way. 

So we go through and decrement all the taps, and the four counters, named
adder1 to adder4.  Then the adders are added into selected taps LFSR
elements.  The ADDs could be any combination of ADD, SUB, MULT, and XOR.
I just use ADD to keep it simple. One of each would be better. Note that I
don't update lfsra[tapa1] and lfsra[tapa5] because they already get
updated elsewhere.

    lfsra[tapa2] += adder1;
    lfsra[tapa3] += adder2;
    lfsra[tapa6] += adder3;
    lfsra[tapa7] += adder4;

Now if my LFSR was to enter a short cycle, then by mixing in the adders,
of which at least one changes on every cycle, then my LFSR will be in
a state that it could not have entered by the primary tapa1 and tapa5
updates using the formulas only. So if there is a short cycle, I am
virtually guranteed to enter a completely different cycle, usually 
the next time around, or at least within a few calls. So as long as we
use the adders, short cycles cease to exist because we are constantly
flipping through them from the outside influence of the adders. This
also means you shouldn't muck with the adders too much, but the
ecbs8setsize function does update them. But it should not be used more
than 2^16, or really far less then that. Default resetpower is 24,
approximately once every 16 million calls. However in testing extremely
bad configurations, a lower resetpower can keep the same initializtion
passing bigger and bigger tests in practrand. For random full configurations
the resetpower can be effectively turned off by setting it higher than
the amount of numbers we can generate in a reasonable time.

Finally, we do the first of our two formulas.

    lfsra[tapa1] += ((lfsra[tapa1] ^  (lfsra[tapa2] * (lfsra[tapa3]|1))) + lfsra[tapa4]);

This formula can get stuck in all zero's state, so we must use the adders
to prevent that. As long as we have at least one adder, the all zero state
is not a problem.  Also notice that we have an XOR, and MULTIPLY and an ADD
of LFSR elements. The multiply is OR'd with 1 to make it always be odd.
This makes it non-bijective, It means that one state can be entered from
multiple previous states. This is normally considered a very bad thing. But
for security it's a good thing because it prevents past preditibility from
known state and preserves predetermined output of forward state. The bit
argument against non-bijective state transitions is that we are guranteed
not to utilize all possible states, because whatever state we enter, there
are three other possible states that would have entered the exact same state.
My counter argument is that if you have enough states, using a small sample
of them is still good enough. And if the number of states you do have is
larger than the seed data, it doesn't matter anyway. Your period is always
limited by the amount of seed data anyway.

The other argument for bijective and against non bijective algorithms is
that you have many short periods, and also typically including the infamous
repeating length 1 period. But that cannot happen in ecbs because of the
adders, so it's not an issue. We have jillions of smaller periods that we
take one number from each. So if we have of jillions of really tiny short
cycles, it doesn't matter because we only take one number from that
particular configuration and then switch to another by swapping taps and
adding the adders.

But now that we've done the formula, we next rotate the primary tap.

Decrement the shift variable and rotate that amount. This generally
doubles the normal period that it would have had. This helps scramble
the formula effects and we do two formuals to use up all 8 taps,
and rotating tapa5 after use in the second formula.

But first we gotta do a tap swap. We use the XOR of two LFSR elements
selected by least used taps and AND with 7 to select a tap, then swap
that tap with the next tap to its right, cycling back to tap1 when
tap8 is selected to swap. This is a bubble sort in reverse used to
scamble the taps order. The tap values are never changed so all taps
stay in their same relative positions, but the elements they point
to are changed between calls such that the formula elements selected
for the the operations in the formulas are NOT what they would have
been for those same hardcoded taps, because they swapped the LFSR
elements they point to. This is another way of breaking out of a
short cycle. We never use the same LFSR twice, just another version
of the same length LFSR but with the operations on the tap elements
all swoapped around. It's the exact same as having all possible
combinations of operations on the same LFSR elements in the code,
just doing it dynamically as we go.

So we've swapped taps, and ecbs8 does the second formula using
taps 5 - 8. It's the same formula, just different taps. Then we
decrement the shift variable and rotate tap5 which is another
tap position that gets updated. The tapa5 update happens AFTER
the second formula so its previous value was used in the formula.

Then we check the resize function and execute or not, and finally
we return the XOR of tapa1 and tapa5. That's why I had the rotate
for tapa5, so we would use a different value than used in the
formula line, for the return value. That tapa5 update might be
better used only for the return value and not update the LFSR element,
but I think it's obscure enough because of the XOR and we need the
diffusion.  But with tapa1, and tapa5, and the adders, we can have
6 LFSR elements update on each cycle, and thats about 50 percent if
you have very small lfsr lengths. But using normal two or three digit
lengths, its very slow diffusion. It's not really that bad because of
the tap swaps, we constantly pick from randomly changing selection
of elements instead of stepping through them linearly.

I still haven't explained the ecbs8setsize function.

This function is called after seeding the LFSR and adders. The
setsize function sets the lfsrlength to the value given and then it
scrambles the taps to assign new values for them. It does that by
assigning each element of a temporary array, it's own index value
as the element value. That array of numbers, ordered 0 to lfsrlength,
is then scrambled by swapping the elements at random by calling the
current ecbs8() function to get pseudo random numbers. This is just
a naive swap, not Fischer-Yates, but it's good enough until I fix it.
After mixing up the temp array we select the first 8 values as our tap
positions. At this point, the function sets the lfsrlength and maxtap
global variables to the new values.

From now on the ecbs8 function is using the new length and new taps.
Then the ecbs8setsize function uses the temporary array to reset the
taps a second time, using the new taps and new lfsrlength. After
this reset, the length is not changed, but the taps are swapped a
second time from scratch. Finally the ecbs8setsize function uses ecbs8
calls to add a number to each adder, thus changing them all. The adders
update is why ecbs8setsize should not be called too often.

While the ecbs8setsize function is using ecbs8 to get pseudo random
numbers, it might turn out that ecbs8 calls ecbs8setsize becuase of
its natural occurance every 2^n calls or so. ecbs8setsize just
uses a busy flag and ignores those calls. Not efficient, but not bad
for normal use when it will only be called once in millions of cycles
at most. ecbs8setsize is only going to call ecbs8() for random number
a few thousand times at most so it won't hit the busy flag very often.

Finally, if ecbs8setsize is called, the (tapa1 XOR tapa5) return value
will be different from what was just calculated for return. That return
value could be stored in a temp variable and returned as it was
before the ecbs8setsize call, but I think just skipping that value is
fastest and best for the algorithms resistance to analysis.  For security
and better unpredictibilty I leave it as it is now.

One thing about the 8 bit version, or even the 64 bit version is
that you can have more than 4 adders. The 8 bit version really needs
8 adders and since they rarely get updated, the extra processing is
insignificant to the overall algorithm speed.

So because of all the uncertainty in which values are used for what,
I think this ia a good example of what you are NOT supposed to do.
This is a randomly generated random number generator. Not a good idea
supposedly, but can you find all the flaws in it that mathematics is
certain exist?   

If there is a short period can you find a way for it to exist for any
length of time without being negated by the adders, tap swaps, and
periodic size resets?

Also notice that with ecbs8, the entire algorithm is really a single
example of many variants. Virtually any hardcoded value can be changed
without affecting the quality of the PRNG. That is in stark contrast
to all others where almost any change of any constant will cause toial
failure immediately.

For example, you can change any of the taps used to select taps to swap.
You can use only one tap element or XOR many of them together. You can
hardcode the LFSR elements to update from the adders almost any way you
want. No matter how you change these constants, the algorithm will
function the same way, because the underlying hardcoded taps are
constantly changing anyway. This means that almost any hardcoded values
in the algorithm can be arbitraily changed and it is still be the exact
same underlying algorithm.

But what this also means is that anybody can have their own unqiue
version of this algorithm that is essentially the exact same algorithm
but structurally seeded such that it will never ever produce the same
numbers as any other version.

If you want cryptographically secure, then just add entropy to any
of the LFSR elements or one of the adders, or both alternating, or
damm near anything else you can think of. Just adding /dev/urandom
once in a while is good enough.

I think of things this way. For this algorithm with n bits of state,
you may get only about 2^(n/2) or some such lesser figure of usefull
states. If I try to calculate all the states of ecbs8, I can't really
decide what is state and what is not. Are the temporary swap variables
state. I know all the ones that store information are state, but
some of them don't store but use and modify state. Lets just call
the standard stuff the primary state. For ecbs8 that is

4 adders
9+ LFSR elements
8 taps
1 shift
1 resetpower
1 resetcheckvalue

In minimal size thats 4*8 + 9*8 + 8*8 + 1*3 + 1*7 + 24 = 202
The actual final period may be far less then 2^202-1. Far more likely
its closer to 2^64 because we only have 32 bits of adders and 72 bits of
LFSR elements, so those are going to be 2^32 * 2^72 = 2^104 possible
states. It will most likely hit them all given enough time, and 
very uniformly randomly at that. Average period of length 9 ecbs8
is probably greater than 2^64 and less than 2^104. Now thats for ecbs8,
but the exact same thing exists in 16, 32 and 64 bit versions, and
those are many many orders of magnitude larger periods. For the 16 bit
version, period length is the least of your concerns. For the 64 bit
version, it's not a concern.


This is the scaled down version of ecbs. You can run the program with
CLI switches in various configurations and using dump files as special
key files. testecbs8 and testecbs64 have a -f CLI option that specifies
a key file to set state to any exact state you want. You can reproduce
any state you have by using the dumpstate function or flag, to create
a dump file that specifies the exact complete state of the PRNG.

By doing this you can easily test the same CLI switches with different
seed values without specifiying all that junk on the command line. The
file looks like this.

[me@mymachine ecbs8test]$ ./testecbs8 -t  -z -dd
testecbs8.c   Sat Aug 27 23:28:28 2022
MAXINP      =  65536  ( Input buffer size)
MAXOUT      =  65536  (Output buffer size)
MAXLEN - 1  =     31  (Maximum LFSR size)
MINLEN + 1  =      9  (Minimum LFSR size)
constantflag = 0   constant1 = 170   constant2 = 85
totalseedchars = 0   cripple = 0   tapswap = 0
adders = 0 0 0 0   resetpower = 0
lfsralen = 9    shift = 3
tapa1 = 0
tapa2 = 1
tapa3 = 2
tapa4 = 3
tapa5 = 4
tapa6 = 5
tapa7 = 6
tapa8 = 7
lfsra[0] = 0
lfsra[1] = 0
lfsra[2] = 0
lfsra[3] = 0
lfsra[4] = 0
lfsra[5] = 0
lfsra[6] = 0
lfsra[7] = 0
lfsra[8] = 0
lfsra[9] = 0
lfsra[10] = 0
lfsra[11] = 0
lfsra[12] = 0
lfsra[13] = 0
lfsra[14] = 0
lfsra[15] = 0
lfsra[16] = 0
lfsra[17] = 0
lfsra[18] = 0
lfsra[19] = 0
lfsra[20] = 0
lfsra[21] = 0
lfsra[22] = 0
lfsra[23] = 0
lfsra[24] = 0
lfsra[25] = 0
lfsra[26] = 0
lfsra[27] = 0
lfsra[28] = 0
lfsra[29] = 0
lfsra[30] = 0
[me@mymachine ecbs8test]$ 

That particular configuration fails practrand immediately. And adding 1 bits
to the lfsra values doesn't help. But, make it a larger size with setsize
allowed like this. 

  ./testecbs8 -t  -z -S15 -dd > out

  ./testecbs8 -f out | RNG_test stdin64

Then it passes practrand for quite a while. This is the new out file.

  ./testecbs8 -t  -z -S15 -dd > out

constantflag = 0   constant1 = 170   constant2 = 85
totalseedchars = 0   cripple = 0   tapswap = 0
adders = 0 0 0 0   resetpower = 24
lfsralen = 15    shift = 5
tapa1 = 0
tapa2 = 1
tapa3 = 2
tapa4 = 3
tapa5 = 4
tapa6 = 5
tapa7 = 6
tapa8 = 7


This is the old out file that fails practrand immediately no matter how
many 1 bits you put in the lfsra elements. The difference is size of the
LFSR in the new that passes practrand is 15 and the old one is 9. Also,
the new one allows resizeing by calling ecbs8setsize once every 2^24
calls, wherease resetpower is set to 0 in the failed out file below.

  ./testecbs8 -t  -z -dd > out

constantflag = 0   constant1 = 170   constant2 = 85
totalseedchars = 0   cripple = 0   tapswap = 0
adders = 0 0 0 0   resetpower = 0
lfsralen = 9    shift = 1
tapa1 = 0
tapa2 = 1
tapa3 = 2
tapa4 = 3
tapa5 = 4
tapa6 = 5
tapa7 = 6
tapa8 = 7

But, if I turn on ecbs8setsize by setting resetpower to 24 or some value
that will be used, then it starts to pass practrand even with all 0 lfsra
elements? On the other hand, if I disable ecbs8setsize in the new size
15 out file that passed practrand before, then the larger one still passed
practrand to 1 GB. Now these are just quick less than a few minutes tests,
but this is how I figure out whats important and whats not.

Now the smaller 8bit version, testecbs8 will always fail practrand with
enough stuff disabled. But it still passed to a surprising degree, even
with more than one component disabled or crippled. I have run many tests
of crippled versions to 32TB in practrand, but this one is the finalized
version with the randomized shift variable and this one has not been fully
tested yet. But from past experience, I am 99.99999 percent confident.

Note that testing also means a lot of coding and recompiling to find out
things you can't do with command line switches, like outputing intermediate
values instead of the normal ecbs8() return value.

Also, there are two Constant values to replace the formula lines with, but
you can't just change one to have lfsra[tapa1] use the constant and
lfsra[tapa5] uses the normal formula or vice versa. I would have to have
three ecbs8constant() functions for that, so if you set one constant,
then the other constant will use its default value.

The same thing applies to turning off rotates with -T CLI option. You can't
specify to turn off one or the other and have the other use the normal
shift update. It's all or nothing. But, I don't think I need that granularity
for testing. Everything would be turned on for any practical use, so I
just need enough stuff to tweak that I can see that the whole is indeed
greater than the sum of its parts. And I think is you try to calculate
the minimum, maximum, and average final output period, it's pretty involved,
but even if you lowball some hard to quantify things, then numbers add
up to very large numbers of possible states, even given the non-bijective
use of MULT (element | 1) which always makes the element value an odd
number. Because there are two multiplies, we always have four possible
preceding states that could have entered any given state. If you try to
check all four previous states, each of those has four previous states
the could have led to that particular state. Therefore you have 4^n
possible previous states that could have lead to the current state, where
n is the number previous states back you want to look at. This is what
prevents cryptographers from determining previous states even given the
entire current state. There no additional entropy required. Of course,
the algorithm is completly deterministic going forward, so if the
cryptographer gets the compete current state, then he has won the battle.
But if he can only get partial state, we might have a chance to keep him
from discovering the number sequence. I think he needs more than half
of the current state to have any chance of predicting future state. In
factg I think he really needs at least statesize-128 or so bits of the
full complete state. I also think the bigger the statesize, the harder
it would be to obtain statesize-128 bits simply from the output of
the PRNG. This is why I think it just might be suitable for cryptographic
use. 

But apart from that, it also produces good numbers almost no matter what.
Thats an important cryptographic property that is underrated. Just being
mathematically proven and sound is not really good enough if you have to 
avoid certain constants and weak keys and so forth. If it's robust, you 
don't have to worry about those things. And ecbs is robust.

Ask yourself, can you take your 64 bit algorithm and scale it down to
8 bits and still pass practrand to 32 terabytes? Can you also disable
or cripple parts of your scaled down algorithm and still pass practrand
to 32 terabytes? Does your algorithm scale down and still have no weak keys?

I'm not saying you can remove everything or just anything, the algorithm
does have to have a definition, but in arriving at the definition, I used
the testing to figure out which parts are most critical and which parts
are just nice to have. Then all we need do is insist on using the full
finalized algorithm and base the critique on the final version used as
designed, especially the 64 bit version. And we can scale things down and
remove some things for speed and still have confidence in a PRNG that will
produce very long period uniformly distributed random numbers.
  

Links to professional PRNG sites and a lot of discussion about ecbs

Explanation and detailed description of the algorithms and why they are as they are

A cryptographic cipher program that leaves no trace of itself after use. How ecbscryptogen works.

View or Download testecbs64.c, testecbs8.c, ecbscrypto.c, ecbscryptogen.c and some minor utilities.

Back to main index page.


Last updated Sun Sep 11 11:43:29 PDT 2022