diff --git a/examples/gno.land/p/demo/entropy/entropy.gno b/examples/gno.land/p/demo/entropy/entropy.gno new file mode 100644 index 00000000000..5e35b8c7227 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/entropy.gno @@ -0,0 +1,89 @@ +// Entropy generates fully deterministic, cost-effective, and hard to guess +// numbers. +// +// It is designed both for single-usage, like seeding math/rand or for being +// reused which increases the entropy and its cost effectiveness. +// +// Disclaimer: this package is unsafe and won't prevent others to guess values +// in advance. +// +// It uses the Bernstein's hash djb2 to be CPU-cycle efficient. +package entropy + +import ( + "math" + "std" + "time" +) + +type Instance struct { + value uint32 +} + +func New() *Instance { + r := Instance{value: 5381} + r.addEntropy() + return &r +} + +func FromSeed(seed uint32) *Instance { + r := Instance{value: seed} + r.addEntropy() + return &r +} + +func (i *Instance) Seed() uint32 { + return i.value +} + +func (i *Instance) djb2String(input string) { + for _, c := range input { + i.djb2Uint32(uint32(c)) + } +} + +// super fast random algorithm. +// http://www.cse.yorku.ca/~oz/hash.html +func (i *Instance) djb2Uint32(input uint32) { + i.value = (i.value << 5) + i.value + input +} + +// AddEntropy uses various runtime variables to add entropy to the existing seed. +func (i *Instance) addEntropy() { + // FIXME: reapply the 5381 initial value? + + // inherit previous entropy + // nothing to do + + // handle callers + { + caller1 := std.GetCallerAt(1).String() + i.djb2String(caller1) + caller2 := std.GetCallerAt(2).String() + i.djb2String(caller2) + } + + // height + { + height := std.GetHeight() + if height >= math.MaxUint32 { + height -= math.MaxUint32 + } + i.djb2Uint32(uint32(height)) + } + + // time + { + secs := time.Now().Second() + i.djb2Uint32(uint32(secs)) + nsecs := time.Now().Nanosecond() + i.djb2Uint32(uint32(nsecs)) + } + + // FIXME: compute other hard-to-guess but deterministic variables, like real gas? +} + +func (i *Instance) Value() uint32 { + i.addEntropy() + return i.value +} diff --git a/examples/gno.land/p/demo/entropy/entropy_test.gno b/examples/gno.land/p/demo/entropy/entropy_test.gno new file mode 100644 index 00000000000..0deb3ab9aa2 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/entropy_test.gno @@ -0,0 +1,46 @@ +package entropy + +import ( + "std" + "strconv" + "testing" +) + +func TestInstance(t *testing.T) { + instance := New() + if instance == nil { + t.Errorf("instance should not be nil") + } +} + +func TestInstanceValue(t *testing.T) { + baseEntropy := New() + baseResult := computeValue(t, baseEntropy) + + sameHeightEntropy := New() + sameHeightResult := computeValue(t, sameHeightEntropy) + + if baseResult != sameHeightResult { + t.Errorf("should have the same result: new=%s, base=%s", sameHeightResult, baseResult) + } + + std.TestSkipHeights(1) + differentHeightEntropy := New() + differentHeightResult := computeValue(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() + + out := "" + for i := 0; i < 10; i++ { + val := int(r.Value()) + out += strconv.Itoa(val) + " " + } + + return out +} diff --git a/examples/gno.land/p/demo/entropy/gno.mod b/examples/gno.land/p/demo/entropy/gno.mod new file mode 100644 index 00000000000..9a6db8f5b61 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/entropy diff --git a/examples/gno.land/p/demo/entropy/z_filetest.gno b/examples/gno.land/p/demo/entropy/z_filetest.gno new file mode 100644 index 00000000000..85ed1b10a3d --- /dev/null +++ b/examples/gno.land/p/demo/entropy/z_filetest.gno @@ -0,0 +1,56 @@ +package main + +import ( + "std" + + "gno.land/p/demo/entropy" +) + +func main() { + // initial + println("---") + r := entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + + // should be the same + println("---") + r = entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + + std.TestSkipHeights(1) + println("---") + r = entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) +} + +// Output: +// --- +// 4129293727 +// 2141104956 +// 1950222777 +// 3348280598 +// 438354259 +// --- +// 4129293727 +// 2141104956 +// 1950222777 +// 3348280598 +// 438354259 +// --- +// 49506731 +// 1539580078 +// 2695928529 +// 1895482388 +// 3462727799 diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index eb6f44d4026..072c98f3bd6 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,6 +1,7 @@ module gno.land/r/demo/art/gnoface require ( + gno.land/p/demo/entropy v0.0.0-latest gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface.gno b/examples/gno.land/r/demo/art/gnoface/gnoface.gno index 9e85c5c7387..b4bc8e222e5 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface.gno @@ -2,15 +2,15 @@ package gnoface import ( "math/rand" - "std" "strconv" "strings" + "gno.land/p/demo/entropy" "gno.land/p/demo/ufmt" ) func Render(path string) string { - seed := uint64(std.GetHeight()) + seed := uint64(entropy.New().Value()) path = strings.TrimSpace(path) if path != "" {