feat: playable arcade — rooms, identity, client, deployment
Round length is now bounded: the multiplier follows a hyperbolic curve diverging at 60s, replacing an exponential one where a 275x crash point produced a two-and-a-half minute round. Fixes seed reveal, which silently failed every round because pgx cannot encode a fixed-size byte array as bytea. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
86
pkg/identity/identity_test.go
Normal file
86
pkg/identity/identity_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package identity_test
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/drjones/quantum-arcade/pkg/identity"
|
||||
)
|
||||
|
||||
func newKey(t *testing.T) (string, ed25519.PrivateKey) {
|
||||
t.Helper()
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return hex.EncodeToString(pub), priv
|
||||
}
|
||||
|
||||
func TestValidSignatureAuthenticates(t *testing.T) {
|
||||
a := identity.NewAuthenticator()
|
||||
pubHex, priv := newKey(t)
|
||||
|
||||
nonceHex, err := a.Challenge(pubHex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nonce, _ := hex.DecodeString(nonceHex)
|
||||
sig := ed25519.Sign(priv, nonce)
|
||||
|
||||
if err := a.Verify(pubHex, hex.EncodeToString(sig)); err != nil {
|
||||
t.Fatalf("valid signature rejected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrongKeyCannotAuthenticate(t *testing.T) {
|
||||
a := identity.NewAuthenticator()
|
||||
pubHex, _ := newKey(t)
|
||||
_, otherPriv := newKey(t)
|
||||
|
||||
nonceHex, _ := a.Challenge(pubHex)
|
||||
nonce, _ := hex.DecodeString(nonceHex)
|
||||
sig := ed25519.Sign(otherPriv, nonce)
|
||||
|
||||
if err := a.Verify(pubHex, hex.EncodeToString(sig)); !errors.Is(err, identity.ErrBadSignature) {
|
||||
t.Fatalf("got %v, want ErrBadSignature", err)
|
||||
}
|
||||
}
|
||||
|
||||
// A captured signature must not work twice.
|
||||
func TestChallengeIsSingleUse(t *testing.T) {
|
||||
a := identity.NewAuthenticator()
|
||||
pubHex, priv := newKey(t)
|
||||
|
||||
nonceHex, _ := a.Challenge(pubHex)
|
||||
nonce, _ := hex.DecodeString(nonceHex)
|
||||
sigHex := hex.EncodeToString(ed25519.Sign(priv, nonce))
|
||||
|
||||
if err := a.Verify(pubHex, sigHex); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := a.Verify(pubHex, sigHex); !errors.Is(err, identity.ErrUnknownChallenge) {
|
||||
t.Fatalf("replay succeeded or gave %v, want ErrUnknownChallenge", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMalformedKeyRejected(t *testing.T) {
|
||||
a := identity.NewAuthenticator()
|
||||
if _, err := a.Challenge("not-hex"); !errors.Is(err, identity.ErrBadPublicKey) {
|
||||
t.Fatalf("got %v, want ErrBadPublicKey", err)
|
||||
}
|
||||
if _, err := a.Challenge("aabb"); !errors.Is(err, identity.ErrBadPublicKey) {
|
||||
t.Fatalf("short key: got %v, want ErrBadPublicKey", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyWithoutChallengeFails(t *testing.T) {
|
||||
a := identity.NewAuthenticator()
|
||||
pubHex, priv := newKey(t)
|
||||
sig := ed25519.Sign(priv, []byte("anything"))
|
||||
if err := a.Verify(pubHex, hex.EncodeToString(sig)); !errors.Is(err, identity.ErrUnknownChallenge) {
|
||||
t.Fatalf("got %v, want ErrUnknownChallenge", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user