Search Apps Documentation Source Content File Folder Download Copy Actions Download

isaac64.gno

11.12 Kb ยท 429 lines
  1// This is a port of the 64-bit version of the ISAAC cryptographically secure PRNG, originally
  2// based on the reference implementation found at https://burtleburtle.net/bob/rand/isaacafa.html
  3//
  4// ISAAC has excellent statistical properties, with long cycle times, and uniformly distributed,
  5// unbiased, and unpredictable number generation. It can not be distinguished from real random
  6// data, and in three decades of scrutiny, no practical attacks have been found.
  7//
  8// The default random number algorithm in gno was ported from Go's v2 rand implementatoon, which
  9// defaults to the PCG algorithm. This algorithm is commonly used in language PRNG implementations
 10// because it has modest seeding requirements, and generates statistically strong randomness.
 11//
 12// This package provides an implementation of the 64-bit ISAAC PRNG algorithm. This algorithm
 13// provides very strong statistical performance, and is cryptographically secure, while still
 14// being substantially faster than the default PCG implementation in `math/rand`.
 15//
 16// Note that the approach to seeing with ISAAC is very important for best results, and seeding with
 17// ISAAC is not as simple as seeding with a single uint64 value. The ISAAC algorithm requires a
 18// 256-element seed. If used for cryptographic purposes, this will likely require entropy generated
 19// off-chain for actual cryptographically secure seeding. For other purposes, however, one can
 20// utilize the built-in seeding mechanism, which will leverage the xorshiftr128plus PRNG to generate
 21// any missing seeds if fewer than 256 are provided.
 22//
 23//		Benchmark
 24//		---------
 25//		PCG:         1000000 Uint64 generated in 15.58s
 26//		ISAAC:       1000000 Uint64 generated in 8.95s
 27//	        ISAAC:       1000000 Uint32 generated in 7.66s
 28//		Ratio:       x1.74 times faster than PCG (uint64)
 29//	        Ratio:       x2.03 times faster than PCG (uint32)
 30//
 31// Use it directly:
 32//
 33//		prng = isaac.New() // pass 0 to 256 uint64 seeds; if fewer than 256 are provided, the rest
 34//	                    // will be generated using the xorshiftr128plus PRNG.
 35//
 36// Or use it as a drop-in replacement for the default PRNT in Rand:
 37//
 38//	source = isaac64.New()
 39//	prng := rand.New(source)
 40package isaac64
 41
 42import (
 43	"errors"
 44	"math"
 45
 46	"gno.land/p/demo/entropy"
 47	"gno.land/p/nt/ufmt"
 48	"gno.land/p/wyhaines/rand/xorshiftr128plus"
 49)
 50
 51const (
 52	RANDSIZL = 8
 53	RANDSIZ  = 1 << RANDSIZL // 256
 54)
 55
 56type ISAAC struct {
 57	randrsl    [256]uint64
 58	randcnt    uint64
 59	mm         [256]uint64
 60	aa, bb, cc uint64
 61	seed       [256]uint64
 62}
 63
 64// ISAAC requires a large, 256-element seed. This implementation will leverage the entropy
 65// package combined with the xorshiftr128plus PRNG to generate any missing seeds if fewer than
 66// the required number of arguments are provided.
 67func New(seeds ...uint64) *ISAAC {
 68	isaac := &ISAAC{}
 69	seed := [256]uint64{}
 70
 71	index := 0
 72	for index = 0; index < len(seeds) && index < 256; index++ {
 73		seed[index] = seeds[index]
 74	}
 75
 76	if index < 2 {
 77		e := entropy.New()
 78		for ; index < 2; index++ {
 79			seed[index] = e.Value64()
 80		}
 81	}
 82
 83	// Use the first two seeds as seeding inputs for xorshiftr128plus, in order to
 84	// use it to provide any remaining missing seeds.
 85	prng := xorshiftr128plus.New(
 86		seed[0],
 87		seed[1],
 88	)
 89	for ; index < 256; index++ {
 90		seed[index] = prng.Uint64()
 91	}
 92	isaac.Seed(seed)
 93	return isaac
 94}
 95
 96// Reinitialize the generator with a new seed. A seed must be composed of 256 uint64.
 97func (isaac *ISAAC) Seed(seed [256]uint64) {
 98	isaac.randrsl = seed
 99	isaac.seed = seed
100	isaac.randinit(true)
101}
102
103// beUint64() decodes a uint64 from a set of eight bytes, assuming big endian encoding.
104func beUint64(b []byte) uint64 {
105	_ = b[7] // bounds check hint to compiler
106	return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
107		uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
108}
109
110// bePutUint64() encodes a uint64 into a buffer of eight bytes.
111func bePutUint64(b []byte, v uint64) {
112	_ = b[7] // early bounds check to guarantee safety of writes below
113	b[0] = byte(v >> 56)
114	b[1] = byte(v >> 48)
115	b[2] = byte(v >> 40)
116	b[3] = byte(v >> 32)
117	b[4] = byte(v >> 24)
118	b[5] = byte(v >> 16)
119	b[6] = byte(v >> 8)
120	b[7] = byte(v)
121}
122
123// A label to identify the marshalled data.
124var marshalISAACLabel = []byte("isaac:")
125
126// MarshalBinary() returns a byte array that encodes the state of the PRNG. This can later be used
127// with UnmarshalBinary() to restore the state of the PRNG.
128// MarshalBinary implements the encoding.BinaryMarshaler interface.
129func (isaac *ISAAC) MarshalBinary() ([]byte, error) {
130	b := make([]byte, 6+2048*3+8*3+8) // 6 + 2048*3 + 8*3 + 8 == 6182
131	copy(b, marshalISAACLabel)
132	offset := 6
133	for i := 0; i < 256; i++ {
134		bePutUint64(b[offset:], isaac.seed[i])
135		offset += 8
136	}
137	for i := 0; i < 256; i++ {
138		bePutUint64(b[offset:], isaac.randrsl[i])
139		offset += 8
140	}
141	for i := 0; i < 256; i++ {
142		bePutUint64(b[offset:], isaac.mm[i])
143		offset += 8
144	}
145	bePutUint64(b[offset:], isaac.aa)
146	offset += 8
147	bePutUint64(b[offset:], isaac.bb)
148	offset += 8
149	bePutUint64(b[offset:], isaac.cc)
150	offset += 8
151	bePutUint64(b[offset:], isaac.randcnt)
152	return b, nil
153}
154
155// errUnmarshalISAAC is returned when unmarshalling fails.
156var errUnmarshalISAAC = errors.New("invalid ISAAC encoding")
157
158// UnmarshalBinary() restores the state of the PRNG from a byte array that was created with MarshalBinary().
159// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
160func (isaac *ISAAC) UnmarshalBinary(data []byte) error {
161	if len(data) != 6182 || string(data[:6]) != string(marshalISAACLabel) {
162		return errUnmarshalISAAC
163	}
164	offset := 6
165	for i := 0; i < 256; i++ {
166		isaac.seed[i] = beUint64(data[offset:])
167		offset += 8
168	}
169	for i := 0; i < 256; i++ {
170		isaac.randrsl[i] = beUint64(data[offset:])
171		offset += 8
172	}
173	for i := 0; i < 256; i++ {
174		isaac.mm[i] = beUint64(data[offset:])
175		offset += 8
176	}
177	isaac.aa = beUint64(data[offset:])
178	offset += 8
179	isaac.bb = beUint64(data[offset:])
180	offset += 8
181	isaac.cc = beUint64(data[offset:])
182	offset += 8
183	isaac.randcnt = beUint64(data[offset:])
184	return nil
185}
186
187func (isaac *ISAAC) randinit(flag bool) {
188	var a, b, c, d, e, f, g, h uint64
189	isaac.aa = 0
190	isaac.bb = 0
191	isaac.cc = 0
192
193	a = 0x9e3779b97f4a7c13
194	b = 0x9e3779b97f4a7c13
195	c = 0x9e3779b97f4a7c13
196	d = 0x9e3779b97f4a7c13
197	e = 0x9e3779b97f4a7c13
198	f = 0x9e3779b97f4a7c13
199	g = 0x9e3779b97f4a7c13
200	h = 0x9e3779b97f4a7c13
201
202	// scramble it
203	for i := 0; i < 4; i++ {
204		mix(&a, &b, &c, &d, &e, &f, &g, &h)
205	}
206
207	// fill in mm[] with messy stuff
208	for i := 0; i < RANDSIZ; i += 8 {
209		if flag {
210			a += isaac.randrsl[i]
211			b += isaac.randrsl[i+1]
212			c += isaac.randrsl[i+2]
213			d += isaac.randrsl[i+3]
214			e += isaac.randrsl[i+4]
215			f += isaac.randrsl[i+5]
216			g += isaac.randrsl[i+6]
217			h += isaac.randrsl[i+7]
218		}
219		mix(&a, &b, &c, &d, &e, &f, &g, &h)
220		isaac.mm[i] = a
221		isaac.mm[i+1] = b
222		isaac.mm[i+2] = c
223		isaac.mm[i+3] = d
224		isaac.mm[i+4] = e
225		isaac.mm[i+5] = f
226		isaac.mm[i+6] = g
227		isaac.mm[i+7] = h
228	}
229
230	if flag {
231		// do a second pass to make all of the seed affect all of mm
232		for i := 0; i < RANDSIZ; i += 8 {
233			a += isaac.mm[i]
234			b += isaac.mm[i+1]
235			c += isaac.mm[i+2]
236			d += isaac.mm[i+3]
237			e += isaac.mm[i+4]
238			f += isaac.mm[i+5]
239			g += isaac.mm[i+6]
240			h += isaac.mm[i+7]
241			mix(&a, &b, &c, &d, &e, &f, &g, &h)
242			isaac.mm[i] = a
243			isaac.mm[i+1] = b
244			isaac.mm[i+2] = c
245			isaac.mm[i+3] = d
246			isaac.mm[i+4] = e
247			isaac.mm[i+5] = f
248			isaac.mm[i+6] = g
249			isaac.mm[i+7] = h
250		}
251	}
252
253	isaac.isaac()
254	isaac.randcnt = RANDSIZ
255}
256
257func mix(a, b, c, d, e, f, g, h *uint64) {
258	*a -= *e
259	*f ^= *h >> 9
260	*h += *a
261
262	*b -= *f
263	*g ^= *a << 9
264	*a += *b
265
266	*c -= *g
267	*h ^= *b >> 23
268	*b += *c
269
270	*d -= *h
271	*a ^= *c << 15
272	*c += *d
273
274	*e -= *a
275	*b ^= *d >> 14
276	*d += *e
277
278	*f -= *b
279	*c ^= *e << 20
280	*e += *f
281
282	*g -= *c
283	*d ^= *f >> 17
284	*f += *g
285
286	*h -= *d
287	*e ^= *g << 14
288	*g += *h
289}
290
291func ind(mm []uint64, x uint64) uint64 {
292	return mm[(x>>3)&(RANDSIZ-1)]
293}
294
295func (isaac *ISAAC) isaac() {
296	var a, b, x, y uint64
297	a = isaac.aa
298	b = isaac.bb + isaac.cc + 1
299	isaac.cc++
300
301	m := isaac.mm[:]
302	r := isaac.randrsl[:]
303
304	var i, m2Index int
305
306	// First half
307	for i = 0; i < RANDSIZ/2; i++ {
308		m2Index = i + RANDSIZ/2
309		switch i % 4 {
310		case 0:
311			a = ^(a ^ (a << 21)) + m[m2Index]
312		case 1:
313			a = (a ^ (a >> 5)) + m[m2Index]
314		case 2:
315			a = (a ^ (a << 12)) + m[m2Index]
316		case 3:
317			a = (a ^ (a >> 33)) + m[m2Index]
318		}
319		x = m[i]
320		y = ind(m, x) + a + b
321		m[i] = y
322		b = ind(m, y>>RANDSIZL) + x
323		r[i] = b
324	}
325
326	// Second half
327	for i = RANDSIZ / 2; i < RANDSIZ; i++ {
328		m2Index = i - RANDSIZ/2
329		switch i % 4 {
330		case 0:
331			a = ^(a ^ (a << 21)) + m[m2Index]
332		case 1:
333			a = (a ^ (a >> 5)) + m[m2Index]
334		case 2:
335			a = (a ^ (a << 12)) + m[m2Index]
336		case 3:
337			a = (a ^ (a >> 33)) + m[m2Index]
338		}
339		x = m[i]
340		y = ind(m, x) + a + b
341		m[i] = y
342		b = ind(m, y>>RANDSIZL) + x
343		r[i] = b
344	}
345
346	isaac.bb = b
347	isaac.aa = a
348}
349
350// Return a 64 bit random integer.
351func (isaac *ISAAC) Uint64() uint64 {
352	if isaac.randcnt == 0 {
353		isaac.isaac()
354		isaac.randcnt = RANDSIZ
355	}
356	isaac.randcnt--
357	return isaac.randrsl[isaac.randcnt]
358}
359
360var gencycle int = 0
361var bufferFor32 uint64 = uint64(0)
362
363// Return a 32 bit random integer, composed of the high 32 bits of the generated 32 bit result.
364func (isaac *ISAAC) Uint32() uint32 {
365	if gencycle == 0 {
366		bufferFor32 = isaac.Uint64()
367		gencycle = 1
368		return uint32(bufferFor32 >> 32)
369	}
370
371	gencycle = 0
372	return uint32(bufferFor32 & 0xffffffff)
373}
374
375// Until there is better benchmarking support in gno, you can test the performance of this PRNG with this function.
376// This isn't perfect, since it will include the startup time of gno in the results, but this will give you a timing
377// for generating a million random uint64 numbers on any unix based system:
378//
379// `time gno run -expr 'benchmarkISAAC()' isaac64.gno
380func benchmarkISAAC(_iterations ...int) {
381	iterations := 1000000
382	if len(_iterations) > 0 {
383		iterations = _iterations[0]
384	}
385	isaac := New()
386
387	for i := 0; i < iterations; i++ {
388		_ = isaac.Uint64()
389	}
390	ufmt.Println(ufmt.Sprintf("ISAAC: generated %d uint64\n", iterations))
391}
392
393// The averageISAAC() function is a simple benchmarking helper to demonstrate
394// the most basic statistical property of the ISAAC PRNG.
395func averageISAAC(_iterations ...int) {
396	target := uint64(500000)
397	iterations := 1000000
398
399	ufmt.Println(
400		ufmt.Sprintf(
401			"Averaging %d random numbers. The average should be very close to %d.\n",
402			iterations,
403			target))
404
405	if len(_iterations) > 0 {
406		iterations = _iterations[0]
407	}
408	isaac := New(987654321987654321, 123456789987654321, 1, 997755331886644220)
409
410	var average float64 = 0
411	var squares []uint64 = make([]uint64, iterations)
412	for i := 0; i < iterations; i++ {
413		n := isaac.Uint64()%(target*2) + 1
414		average += (float64(n) - average) / float64(i+1)
415		squares[i] = n
416	}
417
418	sum_of_squares := uint64(0)
419	// transform numbers into their squares of the distance from the average
420	for i := 0; i < iterations; i++ {
421		difference := average - float64(squares[i])
422		square := uint64(difference * difference)
423		sum_of_squares += square
424	}
425
426	ufmt.Println(ufmt.Sprintf("ISAAC average of %d uint64: %f\n", iterations, average))
427	ufmt.Println(ufmt.Sprintf("ISAAC standard deviation  : %f\n", math.Sqrt(float64(sum_of_squares)/float64(iterations))))
428	ufmt.Println(ufmt.Sprintf("ISAAC theoretical perfect deviation: %f\n", (float64(target*2)-1)/math.Sqrt(12)))
429}