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 TestAValueOutsideTheDeclaredDomainCannotOpen(t *testing.T) { // "invalid", not "rejected": the check ran and answered no. The commitment // would fail to match anyway, but for the wrong reason. vector := loadVector(t, "provenance-private-numeric-semantic-bound-invalid") 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 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) } } } // TestAMeasurementOfZeroOpensItsCommitment pins the case that split the SDKs. // // Zero is legal wherever semantic_min_encoded is 0: a detector reporting exactly // 0.0 produces one, and C = 0*B + r*H is an ordinary commitment to it. Two of // the three clients got it wrong in the same way -- libsodium and noble both // refuse a scalar multiplication whose result is the identity, which is right // for a key exchange and wrong here, and both SDKs read the refusal as an // invalid opening. This library accepts the zero scalar and was correct. // // That is the argument for a shared corpus rather than per-language tests: two // implementations agreeing is not evidence, and the one that matched the Rust // core was the odd one out. func TestAMeasurementOfZeroOpensItsCommitment(t *testing.T) { vector := loadVector(t, "provenance-private-numeric-opening-zero-value") descriptor, value, blinding := openingFrom(t, vector) if value != 0 { t.Fatalf("vector is not the zero case: got %d", value) } ok, err := VerifyOpening(descriptor, value, blinding) if err != nil { t.Fatalf("verify: %v", err) } if !ok { t.Fatal("a measurement of zero must open its commitment") } } // TestZeroIsNotASkeletonKey guards the shape of the fix the other SDKs needed. func TestZeroIsNotASkeletonKey(t *testing.T) { zero := loadVector(t, "provenance-private-numeric-opening-zero-value") other := loadVector(t, "provenance-private-numeric-opening-valid") zeroDescriptor, _, zeroBlinding := openingFrom(t, zero) otherDescriptor, _, otherBlinding := openingFrom(t, other) for _, probe := range []struct { name string descriptor map[string]any value uint64 blinding string }{ {"zero against another commitment", otherDescriptor, 0, otherBlinding}, {"non-zero against the zero commitment", zeroDescriptor, 1, zeroBlinding}, {"zero blinding against a real commitment", zeroDescriptor, 0, strings.Repeat("00", 32)}, } { ok, err := VerifyOpening(probe.descriptor, probe.value, probe.blinding) if err == nil && ok { t.Fatalf("%s: opened a commitment it must not", probe.name) } } }