Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

provenance.gno

6.88 Kb · 217 lines
  1package core
  2
  3import (
  4	"chain"
  5	"chain/runtime"
  6
  7	"gno.land/p/g19dzk54hhsdc3gllpksuj6gu4a3e6fl8cr6rn4w/minhwa/safe/v1"
  8	"gno.land/p/g19dzk54hhsdc3gllpksuj6gu4a3e6fl8cr6rn4w/minhwa/types/v1"
  9)
 10
 11// Commit–reveal v2 (design §4.2–4.3, closes RA H-3, M-5, M-6).
 12//
 13// The map is fixed by three inputs: the creator's secret sc, the beacon's
 14// secret sb and the public mint-entropy accumulator acc. Neither committer
 15// knows the other's secret while minting is open, so neither can time the
 16// last mint for a good map. Order of reveals:
 17//
 18//  1. nothing is accepted before Closed (an early reveal informs the other
 19//     side);
 20//  2. the creator reveals first; the beacon follows, or reveals first
 21//     inside the last BeaconGraceBlocks of the window;
 22//  3. after revealDeadline anyone finalizes with whatever was revealed.
 23//
 24// What stays open, by design: the last revealer can still choose between
 25// revealing and not (one bit). The beacon is last, runs public code, and
 26// the badge shows the outcome. The transfer lock (tokens.gno) keeps anyone
 27// from trading on early knowledge until Final.
 28type provenance struct {
 29	creatorCommit, beaconCommit string
 30	acc                         string
 31	creatorSecret, beaconSecret string
 32	creatorRevealed             bool
 33	beaconRevealed              bool
 34	salt, badge                 string
 35	finalHeight                 int64
 36}
 37
 38const (
 39	roleCreator = "creator"
 40	roleBeacon  = "beacon"
 41)
 42
 43// BeaconCommit is the beacon's commitment, before any token exists.
 44func BeaconCommit(cur realm, cid, commitHex string) {
 45	assertNoDeposit(cur)
 46	c := mustCollection(cid)
 47	if beacon == "" || caller(cur) != beacon {
 48		panic("only the configured beacon can commit")
 49	}
 50	if c.state == types.StateDraft || c.prov == nil || !c.useBeacon {
 51		panic("this collection is not waiting for a beacon")
 52	}
 53	if c.prov.beaconCommit != "" {
 54		panic("the beacon already committed")
 55	}
 56	if c.minted() > 0 {
 57		panic("too late: tokens were minted before the beacon committed")
 58	}
 59	commit := safe.AssertHex("commit", commitHex, 32)
 60	if usedCommits.Has(commit) {
 61		panic("this commitment was already used")
 62	}
 63	usedCommits.Set(commit, cid)
 64	c.prov.beaconCommit = commit
 65	chain.Emit("BeaconCommitted", "cid", cid, "commit", commit)
 66}
 67
 68func RevealCreatorSecret(cur realm, cid, secret string) {
 69	assertNoDeposit(cur)
 70	c := mustCollection(cid)
 71	assertCreator(cur, c)
 72	h := height()
 73	assertRevealOpen(c, h)
 74	if c.prov.creatorRevealed {
 75		panic("the creator secret is already revealed")
 76	}
 77	assertSecret(c, roleCreator, secret, c.prov.creatorCommit)
 78	c.prov.creatorSecret = secret
 79	c.prov.creatorRevealed = true
 80	chain.Emit("CreatorRevealed", "cid", cid, "secret", secret)
 81	if revealsComplete(c) {
 82		finalize(c, h)
 83	}
 84}
 85
 86func RevealBeaconSecret(cur realm, cid, secret string) {
 87	assertNoDeposit(cur)
 88	c := mustCollection(cid)
 89	if beacon == "" || caller(cur) != beacon {
 90		panic("only the configured beacon can reveal")
 91	}
 92	h := height()
 93	assertRevealOpen(c, h)
 94	if !c.useBeacon || c.prov.beaconCommit == "" {
 95		panic("this collection has no beacon commitment")
 96	}
 97	if c.prov.beaconRevealed {
 98		panic("the beacon secret is already revealed")
 99	}
100	if !c.prov.creatorRevealed && h < revealDeadline(c)-BeaconGraceBlocks {
101		panic("the beacon reveals after the creator, or in the window's last " + itoa(BeaconGraceBlocks) + " blocks")
102	}
103	assertSecret(c, roleBeacon, secret, c.prov.beaconCommit)
104	c.prov.beaconSecret = secret
105	c.prov.beaconRevealed = true
106	chain.Emit("BeaconRevealed", "cid", cid, "secret", secret)
107	if revealsComplete(c) {
108		finalize(c, h)
109	}
110}
111
112// Finalize is permissionless and never paused (I-2): immediately once every
113// expected secret is revealed, otherwise after revealDeadline (fallback).
114func Finalize(cur realm, cid string) {
115	assertNoDeposit(cur)
116	c := mustCollection(cid)
117	h := height()
118	refresh(c, h)
119	if c.state != types.StateClosed {
120		panic("only a closed collection can be finalized (state " + c.state.String() + ")")
121	}
122	if !revealsComplete(c) && h <= revealDeadline(c) {
123		panic("the reveal window is open until height " + itoa(revealDeadline(c)))
124	}
125	finalize(c, h)
126}
127
128func assertRevealOpen(c *collection, h int64) {
129	refresh(c, h)
130	if c.state != types.StateClosed {
131		panic("secrets are accepted only after the sale closed (state " + c.state.String() + ")")
132	}
133	if h > revealDeadline(c) {
134		panic("the reveal window closed; call Finalize")
135	}
136}
137
138func assertSecret(c *collection, role, secret, commit string) {
139	if len(secret) < MinSecretLen || len(secret) > MaxSecretLen {
140		panic("secret must be " + itoa(MinSecretLen) + ".." + itoa(MaxSecretLen) + " bytes")
141	}
142	safe.AssertText("secret", secret, MaxSecretLen)
143	if safe.Commit(runtime.ChainID(), c.id, role, secret) != commit {
144		panic("secret does not match the " + role + " commitment")
145	}
146}
147
148func revealsComplete(c *collection) bool {
149	return c.prov.creatorRevealed && (!c.useBeacon || c.prov.beaconRevealed)
150}
151
152// finalize runs at most once per collection (I-10): every caller checks
153// state Closed, and this sets Final.
154func finalize(c *collection, h int64) {
155	p := c.prov
156	p.salt = safe.Salt(c.id, p.creatorSecret, p.beaconSecret, p.acc)
157	switch {
158	case p.creatorRevealed && p.beaconRevealed:
159		p.badge = BadgeBoth
160	case p.creatorRevealed:
161		p.badge = BadgeCreatorOnly
162	case p.beaconRevealed:
163		p.badge = BadgeBeaconOnly
164	default:
165		p.badge = BadgeFallback
166	}
167	p.finalHeight = h
168	c.state = types.StateFinal
169	c.st.final = true
170	chain.Emit("CollectionFinal", "cid", c.id, "salt", p.salt, "badge", p.badge)
171}
172
173// VerifyProvenance recomputes every commitment and the salt from public
174// data. True only for a Final collection whose record is consistent.
175func VerifyProvenance(cid string) bool {
176	c := mustCollection(cid)
177	if c.state != types.StateFinal {
178		return false
179	}
180	p := c.prov
181	chainID := runtime.ChainID()
182	if p.creatorRevealed && safe.Commit(chainID, c.id, roleCreator, p.creatorSecret) != p.creatorCommit {
183		return false
184	}
185	if p.beaconRevealed && safe.Commit(chainID, c.id, roleBeacon, p.beaconSecret) != p.beaconCommit {
186		return false
187	}
188	return p.salt == safe.Salt(c.id, p.creatorSecret, p.beaconSecret, p.acc)
189}
190
191// Provenance is the whole record as JSON; secrets and salt stay "" until
192// revealed / final.
193func Provenance(cid string) string {
194	c := mustCollection(cid)
195	w := &safe.JSONWriter{}
196	w.BeginObject()
197	w.StrField("cid", c.id)
198	w.StrField("chainId", runtime.ChainID())
199	w.StrField("state", stateAt(c, height()).String())
200	w.IntField("minted", c.minted())
201	w.IntField("reserved", c.reserved)
202	if p := c.prov; p != nil {
203		w.BoolField("useBeacon", c.useBeacon)
204		w.StrField("creatorCommit", p.creatorCommit)
205		w.StrField("beaconCommit", p.beaconCommit)
206		w.StrField("entropy", p.acc)
207		w.StrField("creatorSecret", p.creatorSecret)
208		w.StrField("beaconSecret", p.beaconSecret)
209		w.StrField("salt", p.salt)
210		w.StrField("badge", p.badge)
211		w.IntField("revealDeadline", revealDeadline(c))
212		w.IntField("finalHeight", p.finalHeight)
213		w.BoolField("verified", VerifyProvenance(cid))
214	}
215	w.EndObject()
216	return w.String()
217}