Skip to content

Commit c16f2ed

Browse files
authored
feat: add "gno.land/p/rand" package, hard-to-guess deterministic random library (#153)
* chore: add new 'std.TestSkipHeights' testing vm helper * chore: add gno.land/p/rand package Signed-off-by: Manfred Touron <[email protected]>
1 parent ab5131a commit c16f2ed

File tree

4 files changed

+192
-0
lines changed

4 files changed

+192
-0
lines changed

examples/gno.land/p/rand/rand.gno

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package rand
2+
3+
// the goal of this package is to implement a random library that
4+
// is fully deterministic for validators while being hard to
5+
// determine by the callers.
6+
7+
import (
8+
"crypto/md5"
9+
"encoding/binary"
10+
"math/rand"
11+
"std"
12+
)
13+
14+
type instance struct {
15+
seed int64
16+
}
17+
18+
func New() *instance {
19+
r := instance{}
20+
r.addEntropy()
21+
return &r
22+
}
23+
24+
// AddEntropy uses various runtime variables to add entropy to the existing seed.
25+
func (i *instance) addEntropy() {
26+
h := md5.New()
27+
28+
// why not?
29+
h.Write([]byte("now, you gno"))
30+
31+
// inherit previous entropy
32+
{
33+
buf := make([]byte, 16)
34+
_, _ = rand.New(rand.NewSource(i.seed)).Read(buf)
35+
h.Write(buf)
36+
}
37+
38+
// callers
39+
h.Write([]byte(std.GetCallerAt(1)))
40+
h.Write([]byte(std.GetCallerAt(2)))
41+
42+
// height
43+
{
44+
height := std.GetHeight()
45+
buf := make([]byte, 8)
46+
binary.BigEndian.PutUint64(buf, uint64(height))
47+
h.Write(buf)
48+
}
49+
50+
// time
51+
{
52+
now := std.GetTimestamp()
53+
buf := make([]byte, 8)
54+
binary.BigEndian.PutUint64(buf, uint64(now))
55+
h.Write(buf)
56+
}
57+
58+
// set seed
59+
i.seed = int64(binary.BigEndian.Uint64(h.Sum(nil)))
60+
}
61+
62+
func (i *instance) Float32() float32 {
63+
i.addEntropy()
64+
return rand.New(rand.NewSource(i.seed)).Float32()
65+
}
66+
67+
func (i *instance) Float64() float64 {
68+
i.addEntropy()
69+
return rand.New(rand.NewSource(i.seed)).Float64()
70+
}
71+
72+
func (i *instance) Int() int {
73+
i.addEntropy()
74+
return rand.New(rand.NewSource(i.seed)).Int()
75+
}
76+
77+
func (i *instance) Intn(n int) int {
78+
i.addEntropy()
79+
return rand.New(rand.NewSource(i.seed)).Intn(n)
80+
}
81+
82+
func (i *instance) Int63() int64 {
83+
i.addEntropy()
84+
return rand.New(rand.NewSource(i.seed)).Int63()
85+
}
86+
87+
func (i *instance) Int63n(n int64) int64 {
88+
i.addEntropy()
89+
return rand.New(rand.NewSource(i.seed)).Int63n(n)
90+
}
91+
92+
func (i *instance) Int31() int32 {
93+
i.addEntropy()
94+
return rand.New(rand.NewSource(i.seed)).Int31()
95+
}
96+
97+
func (i *instance) Int31n(n int32) int32 {
98+
i.addEntropy()
99+
return rand.New(rand.NewSource(i.seed)).Int31n(n)
100+
}
101+
102+
func (i *instance) Uint32() uint32 {
103+
i.addEntropy()
104+
return rand.New(rand.NewSource(i.seed)).Uint32()
105+
}
106+
107+
func (i *instance) Uint64() uint64 {
108+
i.addEntropy()
109+
return rand.New(rand.NewSource(i.seed)).Uint64()
110+
}
111+
112+
func (i *instance) Read(p []byte) (n int, err error) {
113+
i.addEntropy()
114+
return rand.New(rand.NewSource(i.seed)).Read(p)
115+
}
116+
117+
func (i *instance) Shuffle(n int, swap func(i, j int)) {
118+
i.addEntropy()
119+
rand.New(rand.NewSource(i.seed)).Shuffle(n, swap)
120+
}

precompile.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ var stdlibWhitelist = []string{
4545
"io/util",
4646
"math",
4747
"math/big",
48+
"math/rand",
4849
"regexp",
4950
"sort",
5051
"strconv",

tests/files2/zrealm_rand0.gno

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/rand"
7+
)
8+
9+
func main() {
10+
// initial
11+
println("---")
12+
r := rand.New()
13+
println(r.Intn(1000))
14+
println(r.Intn(1000))
15+
println(r.Intn(1000))
16+
println(r.Intn(1000))
17+
println(r.Intn(1000))
18+
19+
// should be the same
20+
println("---")
21+
r = rand.New()
22+
println(r.Intn(1000))
23+
println(r.Intn(1000))
24+
println(r.Intn(1000))
25+
println(r.Intn(1000))
26+
println(r.Intn(1000))
27+
28+
std.TestSkipHeights(1)
29+
println("---")
30+
r = rand.New()
31+
println(r.Intn(1000))
32+
println(r.Intn(1000))
33+
println(r.Intn(1000))
34+
println(r.Intn(1000))
35+
println(r.Intn(1000))
36+
}
37+
38+
// Output:
39+
// ---
40+
// 71
41+
// 908
42+
// 897
43+
// 955
44+
// 61
45+
// ---
46+
// 71
47+
// 908
48+
// 897
49+
// 955
50+
// 61
51+
// ---
52+
// 410
53+
// 919
54+
// 51
55+
// 342
56+
// 683

tests/imports_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,21 @@ func testPackageInjector(store gno.Store, pn *gno.PackageNode) {
557557
m.PushValue(typedString(rlmpath))
558558
},
559559
)
560+
pn.DefineNative("TestSkipHeights",
561+
gno.Flds( // params
562+
"count", "int64",
563+
),
564+
gno.Flds( // results
565+
),
566+
func(m *gno.Machine) {
567+
arg0 := m.LastBlock().GetParams1().TV
568+
count := arg0.GetInt64()
569+
570+
ctx := m.Context.(stdlibs.ExecContext)
571+
ctx.Height += count
572+
m.Context = ctx
573+
},
574+
)
560575
pn.DefineNative("TestDerivePkgAddr",
561576
gno.Flds( // params
562577
"pkgPath", "string",

0 commit comments

Comments
 (0)