hash.gno
3.57 Kb · 111 lines
1package safe
2
3import (
4 "bytes"
5 "crypto/sha256"
6 "encoding/hex"
7 "strconv"
8 "strings"
9)
10
11// MaxMerkleDepth bounds a proof: 2^32 leaves is far beyond any allowlist.
12const MaxMerkleDepth = 32
13
14func Sha256Hex(s string) string {
15 sum := sha256.Sum256([]byte(s))
16 return hex.EncodeToString(sum[:])
17}
18
19// --- commit–reveal v2 (design §4.2) --------------------------------------
20//
21// Every formula is domain-separated by platform, purpose, version, chain and
22// collection, so a secret or commit is useless anywhere else (RA M-5). They
23// live here, not in core, so the frontend's verifier, the beacon service and
24// the tests all port one definition.
25
26// Commit is the value a creator ("creator") or the beacon ("beacon") commits
27// to for collection cid on chain chainID.
28func Commit(chainID, cid, role, secret string) string {
29 return Sha256Hex(Domain + "|commit|v2|" + chainID + "|" + cid + "|" + role + "|" + secret)
30}
31
32// InitialEntropy seeds a collection's mint-entropy accumulator.
33func InitialEntropy(chainID, cid string) string {
34 return Sha256Hex(Domain + "|acc|v2|" + chainID + "|" + cid)
35}
36
37// MixEntropy folds one mint call into the accumulator. The inputs are
38// public, but none existed when the secrets were committed, which is what
39// stops a committer from choosing the map their own mints receive.
40func MixEntropy(acc string, to address, height, first, last int64) string {
41 return Sha256Hex(acc + "|" + to.String() + "|" + strconv.FormatInt(height, 10) + "|" +
42 strconv.FormatInt(first, 10) + "-" + strconv.FormatInt(last, 10))
43}
44
45// Salt binds both secrets ("" when not revealed) to the final accumulator.
46func Salt(cid, creatorSecret, beaconSecret, acc string) string {
47 return Sha256Hex(Domain + "|salt|v2|" + cid + "|" + creatorSecret + "|" + beaconSecret + "|" + acc)
48}
49
50// Seed is the per-token engine seed once the salt is final.
51func Seed(cid, tid, salt string) string {
52 return Sha256Hex(Domain + "|seed|v1|" + cid + "|" + tid + "|" + salt)
53}
54
55// --- allowlist merkle proofs (design §4.6) -------------------------------
56
57// AllowlistLeaf is the leaf for addr in collection cid's allowlist tree.
58func AllowlistLeaf(cid string, addr address) string {
59 return Sha256Hex(Domain + "|al|v1|" + cid + "|" + addr.String())
60}
61
62// MerkleVerify checks a sorted-pair sha256 proof ("hex,hex,…", leaf to
63// root). An empty proof means the tree has a single leaf equal to the root.
64// Malformed input is simply false: proofs come from untrusted callers.
65func MerkleVerify(rootHex, leafHex, proof string) bool {
66 root, err := hex.DecodeString(rootHex)
67 if err != nil || len(root) != 32 {
68 return false
69 }
70 node, err := hex.DecodeString(leafHex)
71 if err != nil || len(node) != 32 {
72 return false
73 }
74 if proof != "" {
75 steps := strings.Split(proof, ",")
76 if len(steps) > MaxMerkleDepth {
77 return false
78 }
79 for _, step := range steps {
80 sibling, err := hex.DecodeString(step)
81 if err != nil || len(sibling) != 32 {
82 return false
83 }
84 node = hashPair(node, sibling)
85 }
86 }
87 return bytes.Equal(node, root)
88}
89
90// hashPair hashes the two children in byte order, so a proof needs no
91// left/right flags.
92func hashPair(a, b []byte) []byte {
93 if bytes.Compare(b, a) < 0 {
94 a, b = b, a
95 }
96 buf := make([]byte, 0, 64)
97 buf = append(buf, a...)
98 buf = append(buf, b...)
99 sum := sha256.Sum256(buf)
100 return sum[:]
101}
102
103// MerkleParent is hashPair on hex nodes, for tests and the off-chain builder.
104func MerkleParent(aHex, bHex string) string {
105 a, errA := hex.DecodeString(aHex)
106 b, errB := hex.DecodeString(bHex)
107 if errA != nil || errB != nil {
108 panic("MerkleParent: invalid hex")
109 }
110 return hex.EncodeToString(hashPair(a, b))
111}