Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(examples): Add a useful set of high quality pseudo-random number generators #2868

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/gno.land/p/demo/entropy/entropy.gno
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,11 @@ func (i *Instance) Value() uint32 {
i.addEntropy()
return i.value
}

func (i *Instance) Value64() uint64 {
i.addEntropy()
high := i.value
i.addEntropy()

return (uint64(high) << 32) | uint64(i.value)
}
32 changes: 32 additions & 0 deletions examples/gno.land/p/demo/entropy/entropy_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ func TestInstanceValue(t *testing.T) {
}
}

func TestInstanceValue64(t *testing.T) {
baseEntropy := New()
baseResult := computeValue64(t, baseEntropy)

sameHeightEntropy := New()
sameHeightResult := computeValue64(t, sameHeightEntropy)

if baseResult != sameHeightResult {
t.Errorf("should have the same result: new=%s, base=%s", sameHeightResult, baseResult)
}

std.TestSkipHeights(1)
differentHeightEntropy := New()
differentHeightResult := computeValue64(t, differentHeightEntropy)

if baseResult == differentHeightResult {
t.Errorf("should have different result: new=%s, base=%s", differentHeightResult, baseResult)
}
}

func computeValue(t *testing.T, r *Instance) string {
t.Helper()

Expand All @@ -44,3 +64,15 @@ func computeValue(t *testing.T, r *Instance) string {

return out
}

func computeValue64(t *testing.T, r *Instance) string {
t.Helper()

out := ""
for i := 0; i < 10; i++ {
val := int(r.Value64())
out += strconv.Itoa(val) + " "
}

return out
}
6 changes: 6 additions & 0 deletions examples/gno.land/p/demo/entropy/z_filetest.gno
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func main() {
println(r.Value())
println(r.Value())
println(r.Value())
println(r.Value64())

// should be the same
println("---")
Expand All @@ -24,6 +25,7 @@ func main() {
println(r.Value())
println(r.Value())
println(r.Value())
println(r.Value64())

std.TestSkipHeights(1)
println("---")
Expand All @@ -33,6 +35,7 @@ func main() {
println(r.Value())
println(r.Value())
println(r.Value())
println(r.Value64())
}

// Output:
Expand All @@ -42,15 +45,18 @@ func main() {
// 1950222777
// 3348280598
// 438354259
// 6353385488959065197
// ---
// 4129293727
// 2141104956
// 1950222777
// 3348280598
// 438354259
// 6353385488959065197
// ---
// 49506731
// 1539580078
// 2695928529
// 1895482388
// 3462727799
// 16745038698684748445
86 changes: 86 additions & 0 deletions examples/gno.land/p/demo/math/rand/isaac/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# package isaac // import "gno.land/p/demo/math/rand/isaac"

This is a port of the ISAAC cryptographically secure PRNG,
originally based on the reference implementation found at
https://burtleburtle.net/bob/rand/isaacafa.html

ISAAC has excellent statistical properties, with long cycle times, and
uniformly distributed, unbiased, and unpredictable number generation. It can
not be distinguished from real random data, and in three decades of scrutiny,
no practical attacks have been found.

The default random number algorithm in gno was ported from Go's v2 rand
implementatoon, which defaults to the PCG algorithm. This algorithm is
commonly used in language PRNG implementations because it has modest seeding
requirements, and generates statistically strong randomness.

This package provides an implementation of the 32-bit ISAAC PRNG algorithm. This
algorithm provides very strong statistical performance, and is cryptographically
secure, while still being substantially faster than the default PCG
implementation in `math/rand`. Note that this package does implement a `Uint64()`
function in order to generate a 64 bit number out of two 32 bit numbers. Doing this
makes the generator only slightly faster than PCG, however,

Note that the approach to seeing with ISAAC is very important for best results,
and seeding with ISAAC is not as simple as seeding with a single uint64 value.
The ISAAC algorithm requires a 256-element seed. If used for cryptographic
purposes, this will likely require entropy generated off-chain for actual
cryptographically secure seeding. For other purposes, however, one can utilize
the built-in seeding mechanism, which will leverage the xorshiftr128plus PRNG to
generate any missing seeds if fewer than 256 are provided.


```
Benchmark
---------
PCG: 1000000 Uint64 generated in 15.58s
ISAAC: 1000000 Uint64 generated in 13.23s (uint64)
ISAAC: 1000000 Uint32 generated in 6.43s (uint32)
Ratio: x1.18 times faster than PCG (uint64)
Ratio: x2.42 times faster than PCG (uint32)
```

Use it directly:

```
prng = isaac.New() // pass 0 to 256 uint32 seeds; if fewer than 256 are provided, the rest
// will be generated using the xorshiftr128plus PRNG.
```

Or use it as a drop-in replacement for the default PRNT in Rand:

```
source = isaac.New()
prng := rand.New(source)
```

# TYPES

`
type ISAAC struct {
// Has unexported fields.
}
`

`func New(seeds ...uint32) *ISAAC`
ISAAC requires a large, 256-element seed. This implementation will leverage
the entropy package combined with the the xorshiftr128plus PRNG to generate
any missing seeds of fewer than the required number of arguments are
provided.

`func (isaac *ISAAC) MarshalBinary() ([]byte, error)`
MarshalBinary() returns a byte array that encodes the state of the PRNG.
This can later be used with UnmarshalBinary() to restore the state of the
PRNG. MarshalBinary implements the encoding.BinaryMarshaler interface.

`func (isaac *ISAAC) Seed(seed [256]uint32)`

`func (isaac *ISAAC) Uint32() uint32`

`func (isaac *ISAAC) Uint64() uint64`

`func (isaac *ISAAC) UnmarshalBinary(data []byte) error`
UnmarshalBinary() restores the state of the PRNG from a byte array
that was created with MarshalBinary(). UnmarshalBinary implements the
encoding.BinaryUnmarshaler interface.

7 changes: 7 additions & 0 deletions examples/gno.land/p/demo/math/rand/isaac/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module gno.land/p/demo/math/rand/isaac

require (
gno.land/p/demo/entropy v0.0.0-latest
gno.land/p/demo/math/rand/xorshiftr128plus v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
)
Loading
Loading