package content import ( "bytes" "crypto/sha256" "encoding/hex" "testing" ) func TestBindStableHash(t *testing.T) { payload := Phase1Rendered{ "title": "Approve policy change v3", "action": "sign", "detail": map[string]any{"policy_id": "pol-42"}, } a, err := Bind(payload) if err != nil { t.Fatalf("Bind: %v", err) } b, err := Bind(payload) if err != nil { t.Fatalf("Bind again: %v", err) } if a.ContentHash != b.ContentHash { t.Errorf("content_hash not stable: %q vs %q", a.ContentHash, b.ContentHash) } if !bytes.Equal(a.ContentCanonical, b.ContentCanonical) { t.Errorf("canonical bytes not stable") } if bytes.Equal(a.Salt, b.Salt) { t.Errorf("salt should be random (equal across two calls)") } if bytes.Equal(a.Nonce, b.Nonce) { t.Errorf("nonce should be random (equal across two calls)") } } func TestBindRejectsNil(t *testing.T) { if _, err := Bind(nil); err == nil { t.Fatalf("Bind(nil) must error") } } func TestVerificationDigitsFormat(t *testing.T) { b, err := Bind(Phase1Rendered{"k": "v"}) if err != nil { t.Fatal(err) } if l := len(b.VerificationDigits); l != 6 { t.Errorf("want 6 digits, got %d (%q)", l, b.VerificationDigits) } for _, r := range b.VerificationDigits { if r < '0' || r > '9' { t.Errorf("non-digit %q in verification digits %q", r, b.VerificationDigits) } } } func TestChallengeFor_Deterministic(t *testing.T) { b, err := Bind(Phase1Rendered{"k": "v"}) if err != nil { t.Fatal(err) } got := ChallengeFor(b.Nonce, b.ContentCanonical) if !bytes.Equal(got, b.Challenge) { t.Errorf("ChallengeFor must reproduce the challenge") } // Flip a byte in canonical content — challenge must change. alt := append([]byte(nil), b.ContentCanonical...) alt[0] ^= 0xff if bytes.Equal(ChallengeFor(b.Nonce, alt), b.Challenge) { t.Errorf("challenge did not change when canonical content was tampered") } } func TestRehashCanonicalMatchesBind(t *testing.T) { b, err := Bind(Phase1Rendered{"foo": "bar"}) if err != nil { t.Fatal(err) } if got := RehashCanonical(b.ContentCanonical); got != b.ContentHash { t.Errorf("RehashCanonical=%q want %q", got, b.ContentHash) } // Sanity: computed independently sum := sha256.Sum256(b.ContentCanonical) if "sha256:"+hex.EncodeToString(sum[:]) != b.ContentHash { t.Errorf("ContentHash does not match computed sha256") } }