Skip to content

Commit bd99fbe

Browse files
moulmvertes
andauthored
feat: add p/demo/entropy (#2487)
- Revert partially "feat(stdlibs): add math/rand (#2455) (f547d7d) - Update p/demo/rand and rename to p/demo/entropy --------- Signed-off-by: moul <[email protected]> Co-authored-by: Marc Vertes <[email protected]>
1 parent 36c8b03 commit bd99fbe

File tree

6 files changed

+195
-2
lines changed

6 files changed

+195
-2
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Entropy generates fully deterministic, cost-effective, and hard to guess
2+
// numbers.
3+
//
4+
// It is designed both for single-usage, like seeding math/rand or for being
5+
// reused which increases the entropy and its cost effectiveness.
6+
//
7+
// Disclaimer: this package is unsafe and won't prevent others to guess values
8+
// in advance.
9+
//
10+
// It uses the Bernstein's hash djb2 to be CPU-cycle efficient.
11+
package entropy
12+
13+
import (
14+
"math"
15+
"std"
16+
"time"
17+
)
18+
19+
type Instance struct {
20+
value uint32
21+
}
22+
23+
func New() *Instance {
24+
r := Instance{value: 5381}
25+
r.addEntropy()
26+
return &r
27+
}
28+
29+
func FromSeed(seed uint32) *Instance {
30+
r := Instance{value: seed}
31+
r.addEntropy()
32+
return &r
33+
}
34+
35+
func (i *Instance) Seed() uint32 {
36+
return i.value
37+
}
38+
39+
func (i *Instance) djb2String(input string) {
40+
for _, c := range input {
41+
i.djb2Uint32(uint32(c))
42+
}
43+
}
44+
45+
// super fast random algorithm.
46+
// http://www.cse.yorku.ca/~oz/hash.html
47+
func (i *Instance) djb2Uint32(input uint32) {
48+
i.value = (i.value << 5) + i.value + input
49+
}
50+
51+
// AddEntropy uses various runtime variables to add entropy to the existing seed.
52+
func (i *Instance) addEntropy() {
53+
// FIXME: reapply the 5381 initial value?
54+
55+
// inherit previous entropy
56+
// nothing to do
57+
58+
// handle callers
59+
{
60+
caller1 := std.GetCallerAt(1).String()
61+
i.djb2String(caller1)
62+
caller2 := std.GetCallerAt(2).String()
63+
i.djb2String(caller2)
64+
}
65+
66+
// height
67+
{
68+
height := std.GetHeight()
69+
if height >= math.MaxUint32 {
70+
height -= math.MaxUint32
71+
}
72+
i.djb2Uint32(uint32(height))
73+
}
74+
75+
// time
76+
{
77+
secs := time.Now().Second()
78+
i.djb2Uint32(uint32(secs))
79+
nsecs := time.Now().Nanosecond()
80+
i.djb2Uint32(uint32(nsecs))
81+
}
82+
83+
// FIXME: compute other hard-to-guess but deterministic variables, like real gas?
84+
}
85+
86+
func (i *Instance) Value() uint32 {
87+
i.addEntropy()
88+
return i.value
89+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package entropy
2+
3+
import (
4+
"std"
5+
"strconv"
6+
"testing"
7+
)
8+
9+
func TestInstance(t *testing.T) {
10+
instance := New()
11+
if instance == nil {
12+
t.Errorf("instance should not be nil")
13+
}
14+
}
15+
16+
func TestInstanceValue(t *testing.T) {
17+
baseEntropy := New()
18+
baseResult := computeValue(t, baseEntropy)
19+
20+
sameHeightEntropy := New()
21+
sameHeightResult := computeValue(t, sameHeightEntropy)
22+
23+
if baseResult != sameHeightResult {
24+
t.Errorf("should have the same result: new=%s, base=%s", sameHeightResult, baseResult)
25+
}
26+
27+
std.TestSkipHeights(1)
28+
differentHeightEntropy := New()
29+
differentHeightResult := computeValue(t, differentHeightEntropy)
30+
31+
if baseResult == differentHeightResult {
32+
t.Errorf("should have different result: new=%s, base=%s", differentHeightResult, baseResult)
33+
}
34+
}
35+
36+
func computeValue(t *testing.T, r *Instance) string {
37+
t.Helper()
38+
39+
out := ""
40+
for i := 0; i < 10; i++ {
41+
val := int(r.Value())
42+
out += strconv.Itoa(val) + " "
43+
}
44+
45+
return out
46+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module gno.land/p/demo/entropy
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package main
2+
3+
import (
4+
"std"
5+
6+
"gno.land/p/demo/entropy"
7+
)
8+
9+
func main() {
10+
// initial
11+
println("---")
12+
r := entropy.New()
13+
println(r.Value())
14+
println(r.Value())
15+
println(r.Value())
16+
println(r.Value())
17+
println(r.Value())
18+
19+
// should be the same
20+
println("---")
21+
r = entropy.New()
22+
println(r.Value())
23+
println(r.Value())
24+
println(r.Value())
25+
println(r.Value())
26+
println(r.Value())
27+
28+
std.TestSkipHeights(1)
29+
println("---")
30+
r = entropy.New()
31+
println(r.Value())
32+
println(r.Value())
33+
println(r.Value())
34+
println(r.Value())
35+
println(r.Value())
36+
}
37+
38+
// Output:
39+
// ---
40+
// 4129293727
41+
// 2141104956
42+
// 1950222777
43+
// 3348280598
44+
// 438354259
45+
// ---
46+
// 4129293727
47+
// 2141104956
48+
// 1950222777
49+
// 3348280598
50+
// 438354259
51+
// ---
52+
// 49506731
53+
// 1539580078
54+
// 2695928529
55+
// 1895482388
56+
// 3462727799
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module gno.land/r/demo/art/gnoface
22

33
require (
4+
gno.land/p/demo/entropy v0.0.0-latest
45
gno.land/p/demo/uassert v0.0.0-latest
56
gno.land/p/demo/ufmt v0.0.0-latest
67
)

examples/gno.land/r/demo/art/gnoface/gnoface.gno

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ package gnoface
22

33
import (
44
"math/rand"
5-
"std"
65
"strconv"
76
"strings"
87

8+
"gno.land/p/demo/entropy"
99
"gno.land/p/demo/ufmt"
1010
)
1111

1212
func Render(path string) string {
13-
seed := uint64(std.GetHeight())
13+
seed := uint64(entropy.New().Value())
1414

1515
path = strings.TrimSpace(path)
1616
if path != "" {

0 commit comments

Comments
 (0)