Files
attesto-go/zk/pedersen_test.go
T
CodexandClaude Opus 5 34b61b1c09 fix(security): close every fixable advisory and make the unfixable one unreachable
The dependency scan reported 14 high/critical findings across the backend and
the marketplace frontend. All of them are now closed, and the scan is green for
the first time.

**Bumped, with the suite as the check.** aiohttp 3.14.1 -> 3.14.3, pyasn1 0.6.3
-> 0.6.4, pydantic-settings 2.14.1 -> 2.15.0, and cryptography 48.0.1 -> 50.0.0.
That last one crosses two majors, which is why it was flagged as blast radius
rather than a routine bump; the full backend suite passes unchanged. nanoid and
postcss in the marketplace frontend are patched and the frontend still builds.

**ecdsa has no fix and never will.** CVE-2024-23342 is a Minerva timing attack on
P-256, and the project considers side channels out of scope. It arrives through
python-jose, and only signing, key generation and ECDH are affected —
verification is not. The backend signs tenant tokens with the symmetric
JWT_SECRET, so only HMAC families are coherent there anyway.

That was true by habit, not by construction: `jwt_algorithm` had no validation at
all, so JWT_ALGORITHM=ES256 would have signed through the vulnerable path with
nothing to say so. app/core/security.py now refuses any algorithm outside
HS256/HS384/HS512, on both the encode and decode paths, and
tests/test_jwt_algorithm_guard.py fails if that control is removed. `none` is
refused alongside ES*: an unsigned token is not a lesser problem than a badly
signed one.

The advisory is accepted by exact ID with that control named, using a mechanism
added here rather than by silencing the tool. A new advisory on ecdsa still
fails, and a package whose every finding is accepted stops being listed as
vulnerable so the field keeps meaning something.

Backend 1419 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:12:00 +02:00

142 lines
4.9 KiB
Go

package zk
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// Go parity on the Pedersen opening vectors.
//
// These were a declared boundary: verifying them needs ristretto255 scalar
// arithmetic the dependency-free SDK does not carry. This module carries it, so
// a consumer who needs exact-opening verification can have it without imposing a
// curve library on everyone else.
func loadVector(t *testing.T, name string) map[string]any {
t.Helper()
path := filepath.Join("..", "..", "..", "golden-vectors", "provenance-v0.1-dev", name+".json")
raw, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", name, err)
}
var vector map[string]any
if err := json.Unmarshal(raw, &vector); err != nil {
t.Fatalf("parse %s: %v", name, err)
}
return vector
}
func openingFrom(t *testing.T, vector map[string]any) (map[string]any, uint64, string) {
t.Helper()
descriptor := vector["descriptor"].(map[string]any)
value := uint64(vector["encoded_value"].(float64))
return descriptor, value, vector["blinding_scalar"].(string)
}
func TestGeneratorPairMatchesTheFrozenRegistry(t *testing.T) {
// Constants that drifted would commit under a different pair than the
// protocol declares, and every commitment would be unopenable elsewhere.
raw, err := os.ReadFile(filepath.Join("..", "..", "..", "docs", "protocol", "zk-generator-registry.md"))
if err != nil {
t.Fatalf("read registry: %v", err)
}
registry := string(raw)
for _, pinned := range []string{GeneratorSetID, GeneratorBHex, GeneratorHHex} {
if !strings.Contains(registry, pinned) {
t.Fatalf("registry no longer carries %s", pinned)
}
}
}
func TestAValidOpeningReproducesTheRustCommitment(t *testing.T) {
vector := loadVector(t, "provenance-private-numeric-opening-valid")
descriptor, value, blinding := openingFrom(t, vector)
ok, err := VerifyOpening(descriptor, value, blinding)
if err != nil {
t.Fatalf("verify: %v", err)
}
if ok != vector["expected_valid"].(bool) {
t.Fatalf("got %v want %v", ok, vector["expected_valid"])
}
}
func TestAWrongOpeningDoesNotReproduceTheCommitment(t *testing.T) {
for _, name := range []string{
"provenance-private-numeric-opening-wrong-value",
"provenance-private-numeric-opening-wrong-blinding",
} {
vector := loadVector(t, name)
descriptor, value, blinding := openingFrom(t, vector)
ok, err := VerifyOpening(descriptor, value, blinding)
if err != nil {
t.Fatalf("%s: verify: %v", name, err)
}
if ok != vector["expected_valid"].(bool) {
t.Fatalf("%s: got %v want %v", name, ok, vector["expected_valid"])
}
}
}
func TestTheCommittedCommitmentIsWhatAValidOpeningProduces(t *testing.T) {
// Reads the commitment back out, so a check that always returned true fails.
vector := loadVector(t, "provenance-private-numeric-commitment-valid")
descriptor := vector["descriptor"].(map[string]any)
value := uint64(vector["expected_encoded_value"].(float64))
blinding := vector["blinding_scalar"].(string)
ok, err := VerifyOpening(descriptor, value, blinding)
if err != nil || !ok {
t.Fatalf("a valid opening did not verify: ok=%v err=%v", ok, err)
}
pedersen := descriptor["pedersen"].(map[string]any)
pedersen["commitment"] = strings.Repeat("00", 32)
ok, err = VerifyOpening(descriptor, value, blinding)
if err != nil {
t.Fatalf("verify: %v", err)
}
if ok {
t.Fatal("a tampered commitment still verified")
}
}
func TestTheSameMeasurementUnderTwoBlindingsIsUnlinkable(t *testing.T) {
vector := loadVector(t, "provenance-private-numeric-commitment-randomized")
if vector["first_commitment"] == vector["second_commitment"] {
t.Fatal("two blindings produced the same commitment")
}
}
func TestADescriptorNamingAnotherGeneratorSetIsRefused(t *testing.T) {
// "rejected" is not "invalid": the input is refused before any check runs.
vector := loadVector(t, "provenance-private-numeric-wrong-generator-set")
descriptor, value, blinding := openingFrom(t, vector)
if _, err := VerifyOpening(descriptor, value, blinding); err == nil {
t.Fatal("a foreign generator set was accepted")
}
}
func TestAWidenedEncodingDomainIsNotTheCurveChecksBusiness(t *testing.T) {
// A claim about semantics is refused by whoever validates the profile. The
// commitment still opens, and saying otherwise would blame the wrong layer.
vector := loadVector(t, "provenance-private-numeric-encoding-mismatch")
descriptor, value, blinding := openingFrom(t, vector)
ok, err := VerifyOpening(descriptor, value, blinding)
if err != nil || !ok {
t.Fatalf("the commitment should still open: ok=%v err=%v", ok, err)
}
}
func TestAMalformedOpeningIsRefused(t *testing.T) {
vector := loadVector(t, "provenance-private-numeric-opening-valid")
descriptor, value, _ := openingFrom(t, vector)
for _, blinding := range []string{"nothex", strings.Repeat("ab", 31), "", strings.Repeat("ff", 32)} {
if _, err := VerifyOpening(descriptor, value, blinding); err == nil {
t.Fatalf("a malformed blinding %q was accepted", blinding)
}
}
}