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.

commitreveal_test.gno

5.53 Kb · 151 lines
  1package commitreveal
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/uassert/v0"
  8)
  9
 10const goodSalt = "0123456789abcdef" // exactly MinSaltLen
 11
 12func TestCommitAndVerify(t *testing.T) {
 13	c, err := Commit("rock", goodSalt)
 14	uassert.NoError(t, err)
 15	uassert.True(t, ValidCommitment(c), "a commitment is a 32-byte hex digest")
 16	uassert.Equal(t, 64, len(c), "32 bytes hex-encoded")
 17
 18	uassert.True(t, Verify(c, "rock", goodSalt), "the correct opening verifies")
 19}
 20
 21func TestCommitIsDeterministic(t *testing.T) {
 22	a, _ := Commit("paper", goodSalt)
 23	b, _ := Commit("paper", goodSalt)
 24	uassert.Equal(t, a, b, "same input, same commitment")
 25}
 26
 27func TestDifferentValuesDiffer(t *testing.T) {
 28	a, _ := Commit("rock", goodSalt)
 29	b, _ := Commit("paper", goodSalt)
 30	uassert.False(t, a == b)
 31}
 32
 33// TestSameValueDifferentSaltDiffers is why the salt exists: two players
 34// choosing the same move must not publish the same commitment.
 35func TestSameValueDifferentSaltDiffers(t *testing.T) {
 36	a, _ := Commit("rock", "salt-one-0123456789")
 37	b, _ := Commit("rock", "salt-two-0123456789")
 38	uassert.False(t, a == b, "the same move must not be recognisable across players")
 39}
 40
 41func TestWrongOpeningFails(t *testing.T) {
 42	c, _ := Commit("rock", goodSalt)
 43
 44	uassert.False(t, Verify(c, "paper", goodSalt), "wrong value")
 45	uassert.False(t, Verify(c, "rock", "a-different-salt-value"), "wrong salt")
 46	uassert.False(t, Verify(c, "", goodSalt), "empty value")
 47}
 48
 49// TestShortSaltRefused is the enforced-not-advised part: a three-move game with
 50// a short salt is brute-forceable, so the package will not produce one.
 51func TestShortSaltRefused(t *testing.T) {
 52	_, err := Commit("rock", "tooshort")
 53	uassert.ErrorIs(t, err, ErrShortSalt)
 54
 55	_, err = Commit("rock", strings.Repeat("x", MinSaltLen-1))
 56	uassert.ErrorIs(t, err, ErrShortSalt, "one byte under the minimum is still refused")
 57
 58	_, err = Commit("rock", strings.Repeat("x", MinSaltLen))
 59	uassert.NoError(t, err, "exactly MinSaltLen is accepted")
 60}
 61
 62func TestShortSaltNeverVerifies(t *testing.T) {
 63	// Even a digest that would match cannot be opened with a short salt.
 64	uassert.False(t, Verify(MustCommit("rock", goodSalt), "rock", "short"),
 65		"Verify enforces the salt floor too")
 66}
 67
 68func TestLongValueRefused(t *testing.T) {
 69	_, err := Commit(strings.Repeat("x", MaxValueLen+1), goodSalt)
 70	uassert.ErrorIs(t, err, ErrLongValue)
 71
 72	_, err = Commit(strings.Repeat("x", MaxValueLen), goodSalt)
 73	uassert.NoError(t, err, "exactly MaxValueLen is accepted")
 74}
 75
 76// TestLengthPrefixPreventsAmbiguity is the subtle one: with plain
 77// concatenation, ("ab","cdefghijklmnopq") and ("abc","defghijklmnopq") produce
 78// the same bytes, so one commitment could be opened two ways.
 79func TestLengthPrefixPreventsAmbiguity(t *testing.T) {
 80	a, err := Commit("ab", "cdefghijklmnopqr")
 81	uassert.NoError(t, err)
 82	b, err := Commit("abc", "defghijklmnopqr0")
 83	uassert.NoError(t, err)
 84
 85	uassert.False(t, a == b, "a split ambiguity must not collide")
 86	uassert.False(t, Verify(a, "abc", "defghijklmnopqr0"), "and must not cross-verify")
 87}
 88
 89func TestOpenReportsReasons(t *testing.T) {
 90	c := MustCommit("rock", goodSalt)
 91
 92	uassert.NoError(t, Open(c, "rock", goodSalt))
 93	uassert.ErrorIs(t, Open(c, "paper", goodSalt), ErrMismatch)
 94	uassert.ErrorIs(t, Open(c, "rock", "short"), ErrShortSalt)
 95	uassert.ErrorIs(t, Open(c, strings.Repeat("x", MaxValueLen+1), goodSalt), ErrLongValue)
 96	uassert.ErrorIs(t, Open("not-hex!!", "rock", goodSalt), ErrBadHexDigest)
 97	uassert.ErrorIs(t, Open("abcd", "rock", goodSalt), ErrBadHexDigest, "right charset, wrong length")
 98}
 99
100func TestMalformedCommitments(t *testing.T) {
101	uassert.False(t, Verify("", "rock", goodSalt))
102	uassert.False(t, Verify("zz", "rock", goodSalt), "not hex")
103	uassert.False(t, Verify(strings.Repeat("ab", 16), "rock", goodSalt), "16 bytes, not 32")
104}
105
106func TestValidCommitment(t *testing.T) {
107	uassert.True(t, ValidCommitment(MustCommit("x", goodSalt)))
108	uassert.False(t, ValidCommitment(""))
109	uassert.False(t, ValidCommitment("nothex"))
110	uassert.False(t, ValidCommitment(strings.Repeat("a", 63)), "odd length")
111	uassert.False(t, ValidCommitment(strings.Repeat("a", 66)), "too long")
112	uassert.True(t, ValidCommitment(strings.Repeat("A", 64)), "uppercase hex is fine")
113}
114
115func TestMustCommitPanicsOnBadInput(t *testing.T) {
116	defer func() {
117		r := recover()
118		uassert.True(t, r != nil, "MustCommit must panic on a short salt")
119	}()
120	MustCommit("rock", "short")
121}
122
123func TestEqualConstantTime(t *testing.T) {
124	a := []byte{1, 2, 3}
125	uassert.True(t, equalConstantTime(a, []byte{1, 2, 3}))
126	uassert.False(t, equalConstantTime(a, []byte{1, 2, 4}), "differs in the last byte")
127	uassert.False(t, equalConstantTime(a, []byte{9, 2, 3}), "differs in the first byte")
128	uassert.False(t, equalConstantTime(a, []byte{1, 2}), "different lengths")
129	uassert.True(t, equalConstantTime(nil, nil))
130}
131
132// TestFullRoundTrip walks the two phases the scheme is made of.
133func TestFullRoundTrip(t *testing.T) {
134	// Phase 1: both players publish only a commitment.
135	aliceSalt := "alice-secret-salt-1"
136	bobSalt := "bob-secret-salt-0001"
137	aliceCommit := MustCommit("rock", aliceSalt)
138	bobCommit := MustCommit("paper", bobSalt)
139
140	uassert.False(t, aliceCommit == bobCommit)
141	uassert.True(t, ValidCommitment(aliceCommit))
142
143	// Phase 2: reveals are checked against the earlier commitments.
144	uassert.NoError(t, Open(aliceCommit, "rock", aliceSalt))
145	uassert.NoError(t, Open(bobCommit, "paper", bobSalt))
146
147	// Bob cannot change his move after seeing Alice's.
148	uassert.ErrorIs(t, Open(bobCommit, "scissors", bobSalt), ErrMismatch)
149	// Nor can he claim Alice's commitment as his own.
150	uassert.ErrorIs(t, Open(aliceCommit, "rock", bobSalt), ErrMismatch)
151}