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

9.08 Kb · 267 lines
  1package core
  2
  3import (
  4	"chain"
  5	"chain/runtime"
  6
  7	"gno.land/p/g1pq6pxhgxk2uw5shg5le6glfydpxcu74k0zh069/sinopia/safe/v1"
  8	"gno.land/p/g1pq6pxhgxk2uw5shg5le6glfydpxcu74k0zh069/sinopia/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 may reveal until revealDeadline - BeaconGraceBlocks; the
 21//     beacon follows at any time before the deadline;
 22//  3. inside the last BeaconGraceBlocks only the beacon may reveal. If the
 23//     creator stayed silent, the beacon's reveal finalizes at once
 24//     ("beacon-only"), so the creator never sees sb while it can still
 25//     choose (SA-M1; this also closes the mempool race of a creator
 26//     front-running the beacon's grace reveal);
 27//  4. after revealDeadline anyone finalizes with whatever was revealed.
 28//
 29// What stays open, by design: the beacon, as last revealer after the
 30// creator, can still choose between revealing and not (one bit). It runs
 31// public code and the badge shows the outcome. The transfer lock
 32// (tokens.gno) keeps anyone from trading on early knowledge until Final.
 33//
 34// A collection launched without the beacon (only possible while no beacon
 35// is configured, SA-M2) gives the creator the whole choice of the map; it
 36// can have no creator reserve and is never reported as a fair launch
 37// (FairLaunch, Provenance "fair").
 38type provenance struct {
 39	creatorCommit, beaconCommit string
 40	acc                         string
 41	creatorSecret, beaconSecret string
 42	creatorRevealed             bool
 43	beaconRevealed              bool
 44	salt, badge                 string
 45	finalHeight                 int64
 46}
 47
 48const (
 49	roleCreator = "creator"
 50	roleBeacon  = "beacon"
 51)
 52
 53// BeaconCommit is the beacon's commitment, before any token exists.
 54func BeaconCommit(cur realm, cid, commitHex string) {
 55	assertNoDeposit(cur)
 56	c := mustCollection(cid)
 57	if beacon == "" || caller(cur) != beacon {
 58		panic("only the configured beacon can commit")
 59	}
 60	if c.state == types.StateDraft || c.prov == nil || !c.useBeacon {
 61		panic("this collection is not waiting for a beacon")
 62	}
 63	if c.prov.beaconCommit != "" {
 64		panic("the beacon already committed")
 65	}
 66	if c.minted() > 0 {
 67		panic("too late: tokens were minted before the beacon committed")
 68	}
 69	commit := safe.AssertHex("commit", commitHex, 32)
 70	if isBurned(c, commit) {
 71		panic("this commitment's secret was already revealed for this collection")
 72	}
 73	c.prov.beaconCommit = commit
 74	chain.Emit("BeaconCommitted", "cid", cid, "commit", commit)
 75}
 76
 77func RevealCreatorSecret(cur realm, cid, secret string) {
 78	assertNoDeposit(cur)
 79	c := mustCollection(cid)
 80	assertCreator(cur, c)
 81	h := height()
 82	assertRevealOpen(c, h)
 83	if c.prov.creatorRevealed {
 84		panic("the creator secret is already revealed")
 85	}
 86	if c.useBeacon {
 87		// SA-M1: once the grace period starts only the beacon may reveal, so
 88		// the creator can never reveal after seeing sb.
 89		if c.prov.beaconRevealed {
 90			panic("the beacon revealed first; the outcome is fixed")
 91		}
 92		if h >= revealDeadline(c)-BeaconGraceBlocks {
 93			panic("the creator's reveal window ended at height " + itoa(revealDeadline(c)-BeaconGraceBlocks-1) +
 94				"; only the beacon may reveal now")
 95		}
 96	}
 97	assertSecret(c, roleCreator, secret, c.prov.creatorCommit)
 98	c.prov.creatorSecret = secret
 99	c.prov.creatorRevealed = true
100	chain.Emit("CreatorRevealed", "cid", cid, "secret", secret)
101	if revealsComplete(c) {
102		finalize(c, h)
103	}
104}
105
106func RevealBeaconSecret(cur realm, cid, secret string) {
107	assertNoDeposit(cur)
108	c := mustCollection(cid)
109	if beacon == "" || caller(cur) != beacon {
110		panic("only the configured beacon can reveal")
111	}
112	h := height()
113	assertRevealOpen(c, h)
114	if !c.useBeacon || c.prov.beaconCommit == "" {
115		panic("this collection has no beacon commitment")
116	}
117	if c.prov.beaconRevealed {
118		panic("the beacon secret is already revealed")
119	}
120	if !c.prov.creatorRevealed && h < revealDeadline(c)-BeaconGraceBlocks {
121		panic("the beacon reveals after the creator, or in the window's last " + itoa(BeaconGraceBlocks) + " blocks")
122	}
123	assertSecret(c, roleBeacon, secret, c.prov.beaconCommit)
124	c.prov.beaconSecret = secret
125	c.prov.beaconRevealed = true
126	chain.Emit("BeaconRevealed", "cid", cid, "secret", secret)
127	// After the creator: both secrets are in (2/2). First, in the grace
128	// period: the creator can no longer reveal, so the map is fixed now
129	// (beacon-only) — nobody is left holding a choice (SA-M1).
130	finalize(c, h)
131}
132
133// Finalize is permissionless and never paused (I-2): immediately once every
134// expected secret is revealed, otherwise after revealDeadline (fallback).
135func Finalize(cur realm, cid string) {
136	assertNoDeposit(cur)
137	c := mustCollection(cid)
138	h := height()
139	refresh(c, h)
140	if c.state != types.StateClosed {
141		panic("only a closed collection can be finalized (state " + c.state.String() + ")")
142	}
143	if !revealsComplete(c) && h <= revealDeadline(c) {
144		panic("the reveal window is open until height " + itoa(revealDeadline(c)))
145	}
146	// SA-I9: a sale that minted nothing stays Closed, so its creator can
147	// still abort and relaunch; nobody else can lock it into Final.
148	if c.minted() == 0 {
149		panic("nothing was minted; the creator may abort and relaunch instead")
150	}
151	finalize(c, h)
152}
153
154func assertRevealOpen(c *collection, h int64) {
155	refresh(c, h)
156	if c.state != types.StateClosed {
157		panic("secrets are accepted only after the sale closed (state " + c.state.String() + ")")
158	}
159	if h > revealDeadline(c) {
160		panic("the reveal window closed; call Finalize")
161	}
162}
163
164func assertSecret(c *collection, role, secret, commit string) {
165	if len(secret) < MinSecretLen || len(secret) > MaxSecretLen {
166		panic("secret must be " + itoa(MinSecretLen) + ".." + itoa(MaxSecretLen) + " bytes")
167	}
168	safe.AssertText("secret", secret, MaxSecretLen)
169	if safe.Commit(runtime.ChainID(), c.id, role, secret) != commit {
170		panic("secret does not match the " + role + " commitment")
171	}
172}
173
174func revealsComplete(c *collection) bool {
175	return c.prov.creatorRevealed && (!c.useBeacon || c.prov.beaconRevealed)
176}
177
178// finalize runs at most once per collection (I-10): every caller checks
179// state Closed, and this sets Final.
180func finalize(c *collection, h int64) {
181	p := c.prov
182	p.salt = safe.Salt(c.id, p.creatorSecret, p.beaconSecret, p.acc)
183	switch {
184	case p.creatorRevealed && p.beaconRevealed:
185		p.badge = BadgeBoth
186	case p.creatorRevealed:
187		p.badge = BadgeCreatorOnly
188	case p.beaconRevealed:
189		p.badge = BadgeBeaconOnly
190	default:
191		p.badge = BadgeFallback
192	}
193	p.finalHeight = h
194	c.state = types.StateFinal
195	c.st.final = true
196	chain.Emit("CollectionFinal", "cid", c.id, "salt", p.salt, "badge", p.badge)
197}
198
199// VerifyProvenance recomputes every commitment and the salt from public
200// data. True only for a Final collection whose record is consistent.
201func VerifyProvenance(cid string) bool {
202	c := mustCollection(cid)
203	if c.state != types.StateFinal {
204		return false
205	}
206	p := c.prov
207	chainID := runtime.ChainID()
208	if p.creatorRevealed && safe.Commit(chainID, c.id, roleCreator, p.creatorSecret) != p.creatorCommit {
209		return false
210	}
211	if p.beaconRevealed && safe.Commit(chainID, c.id, roleBeacon, p.beaconSecret) != p.beaconCommit {
212		return false
213	}
214	return p.salt == safe.Salt(c.id, p.creatorSecret, p.beaconSecret, p.acc)
215}
216
217// FairLaunch is true only for a Final, consistent collection whose map no
218// single party could choose: launched with the beacon and finalized with
219// both secrets or with the beacon's alone (after the creator stayed
220// silent). Creator-only collections (no beacon: the creator alone knew the
221// only secret and could steer the last mint, SA-M2) and fallbacks are not
222// fair launches, even when VerifyProvenance is true.
223func FairLaunch(cid string) bool {
224	c := mustCollection(cid)
225	if !VerifyProvenance(cid) || !c.useBeacon {
226		return false
227	}
228	return c.prov.badge == BadgeBoth || c.prov.badge == BadgeBeaconOnly
229}
230
231func isBurned(c *collection, commit string) bool {
232	for _, b := range c.burnedCommits {
233		if b == commit {
234			return true
235		}
236	}
237	return false
238}
239
240// Provenance is the whole record as JSON; secrets and salt stay "" until
241// revealed / final.
242func Provenance(cid string) string {
243	c := mustCollection(cid)
244	w := &safe.JSONWriter{}
245	w.BeginObject()
246	w.StrField("cid", c.id)
247	w.StrField("chainId", runtime.ChainID())
248	w.StrField("state", stateAt(c, height()).String())
249	w.IntField("minted", c.minted())
250	w.IntField("reserved", c.reserved)
251	if p := c.prov; p != nil {
252		w.BoolField("useBeacon", c.useBeacon)
253		w.StrField("creatorCommit", p.creatorCommit)
254		w.StrField("beaconCommit", p.beaconCommit)
255		w.StrField("entropy", p.acc)
256		w.StrField("creatorSecret", p.creatorSecret)
257		w.StrField("beaconSecret", p.beaconSecret)
258		w.StrField("salt", p.salt)
259		w.StrField("badge", p.badge)
260		w.IntField("revealDeadline", revealDeadline(c))
261		w.IntField("finalHeight", p.finalHeight)
262		w.BoolField("verified", VerifyProvenance(cid))
263		w.BoolField("fair", FairLaunch(cid))
264	}
265	w.EndObject()
266	return w.String()
267}