diff --git a/Makefile b/Makefile index 2a6b380..0c9183f 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +GO_MODULES := . driver/gurvy/compat + .PHONY: all all: checks unit-tests unit-tests-race @@ -9,11 +11,19 @@ checks: check-deps .PHONY: unit-tests unit-tests: - @go test -timeout 480s -cover $(shell go list ./...) + @echo "Unit-testing Go modules..." + @for dir in $(GO_MODULES); do \ + echo " Unit-testing module: $$dir"; \ + (cd $$dir && go test -cover ./...); \ + done .PHONY: unit-tests-race unit-tests-race: - @export GORACE=history_size=7; go test -timeout 960s -race -cover $(shell go list ./...) + @echo "Unit-testing with race Go modules..." + @for dir in $(GO_MODULES); do \ + echo " Unit-testing with race module: $$dir"; \ + (export GORACE=history_size=7 && cd $$dir && go test -race -cover ./...); \ + done .PHONY: perf perf: @@ -27,22 +37,40 @@ check-deps: .PHONY: lint # run various linters lint: - @echo "Running Go Linters..." - golangci-lint run --color=always --timeout=4m + @echo "Running Go linters..." + @for dir in $(GO_MODULES); do \ + echo " Running Go linters on module: $$dir"; \ + (cd $$dir && golangci-lint run --color=always --timeout=4m); \ + done .PHONY: lint-auto-fix # run linters with auto-fix lint-auto-fix: - @echo "Running Go Linters with auto-fix..." - golangci-lint run --color=always --timeout=4m --fix + @echo "Running Go linters with auto-fix..." + @for dir in $(GO_MODULES); do \ + echo " Running Go linters with auto-fix on module: $$dir"; \ + (cd $$dir && golangci-lint run --color=always --timeout=4m --fix); \ + done .PHONY: install-linter-tool # install golangci-lint install-linter-tool: @echo "Installing golangci Linter" - @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(HOME)/go/bin v2.10.1 + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(HOME)/go/bin v2.12.2 .PHONY: fmt fmt: ## Run gofmt on the entire project @echo "Running gofmt..." - @gofmt -l -s -w . + @for dir in $(GO_MODULES); do \ + echo " Formatting module: $$dir"; \ + (cd $$dir && find . -path './.git' -prune -o -name '*.go' -print | xargs gofmt -l -s -w); \ + done + +.PHONY: tidy +# tidy up go modules +tidy: + @echo "Tidying Go modules..." + @for dir in $(GO_MODULES); do \ + echo " Tidying module: $$dir"; \ + (cd $$dir && go mod tidy); \ + done diff --git a/README.md b/README.md index d4cd8a2..6d330f4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The library abstracts the complexity of pairing operations while providing flexi ## Features - **Multiple Curve Support**: FP256BN, BN254, BLS12-381, BLS12-377, and BBS+ variants -- **Pluggable Backends**: Support for AMCL, Gurvy, and Kilic implementations +- **Pluggable Backends**: Support for AMCL and Gurvy implementations - **Type-Safe API**: Strongly-typed group elements (G1, G2, Gt, Zr) - **Efficient Operations**: Optimized pairing computations and multi-scalar multiplications - **Serialization**: Support for both compressed and uncompressed point representations @@ -76,10 +76,10 @@ func main() { | `FP256BN_AMCL` | 256-bit Barreto-Naehrig curve | AMCL | General-purpose pairing operations | | `FP256BN_AMCL_MIRACL` | 256-bit BN curve (MIRACL variant) | AMCL | Legacy compatibility | | `BN254` | 254-bit Barreto-Naehrig curve | Gurvy | High-performance applications | -| `BLS12_381` | BLS12-381 curve | Kilic | Modern protocols, BLS signatures | +| `BLS12_381` | BLS12-381 curve (deprecated) | None | Legacy slot | | `BLS12_381_GURVY` | BLS12-381 curve | Gurvy | Performance-optimized BLS12-381 | | `BLS12_377_GURVY` | BLS12-377 curve | Gurvy | Recursive proof systems | -| `BLS12_381_BBS` | BLS12-381 for BBS+ signatures | Kilic | Anonymous credentials | +| `BLS12_381_BBS` | BLS12-381 for BBS+ signatures | Gurvy | Anonymous credentials | | `BLS12_381_BBS_GURVY` | BLS12-381 for BBS+ signatures | Gurvy | High-performance BBS+ | ### Choosing a Curve @@ -204,7 +204,7 @@ fmt.Printf("Signature: %x\n", signature.Compressed()) ### Example 4: Multi-Scalar Multiplication ```go -curve := math.Curves[math.BLS12_381] +curve := math.Curves[math.BLS12_381_GURVY] // Create multiple points and scalars points := []*math.G1{ @@ -243,11 +243,11 @@ fmt.Printf("Multi-scalar multiplication result: %s\n", result.String()) │ Curve, G1, G2, Gt, Zr interfaces │ └─────────────────┬───────────────────┘ │ - ┌─────────┼─────────┐ - ▼ ▼ ▼ - ┌──────┐ ┌──────┐ ┌──────┐ - │ AMCL │ │Gurvy │ │Kilic │ - └──────┘ └──────┘ └──────┘ + ┌─────────┴─────────┐ + ▼ ▼ + ┌──────┐ ┌──────┐ + │ AMCL │ │Gurvy │ + └──────┘ └──────┘ ``` This design allows: @@ -258,8 +258,35 @@ This design allows: ### Backend Implementations - **AMCL (Apache Milagro Crypto Library)**: Mature, well-tested implementation -- **Gurvy**: High-performance Go-native implementation with assembly optimizations -- **Kilic**: Optimized BLS12-381 implementation with focus on BLS signatures +- **Gurvy**: High-performance Go-native implementation using [gnark-crypto](https://github.com/ConsenSys/gnark-crypto) + +### Gurvy BLS12-381 hash-to-curve + +The `driver/gurvy` package contains a self-contained BLS12-381 hash-to-curve +pipeline (`Hash`, `SwuMapG1BE`, `HashToG1GenericBESwu`) that is byte-for-byte +compatible with the algorithm originally implemented in `kilic/bls12-381`. + +**Dependencies**: the main module has no runtime dependency on +`kilic/bls12-381`. The kilic library was removed from `go.mod` once the +reimplementation was validated. + +**Test vectors**: `driver/gurvy/compat_test.go` uses self-contained +known-answer vectors and property tests (gnark-crypto as the sole oracle). +Vectors can be regenerated by temporarily removing the `//go:build ignore` +constraint from `driver/gurvy/gen_vectors_test.go` and running: + +```bash +cd driver/gurvy && go test -run TestGenVectors -v +``` + +**Oracle tests**: `driver/gurvy/compat/` is a standalone Go module +(`github.com/IBM/mathlib/driver/gurvy/compat`) that retains the original +kilic-vs-gurvy oracle tests. These are deliberately isolated so that the kilic +dependency does not appear in the main module graph. Run them with: + +```bash +cd driver/gurvy/compat && go test ./... +``` ## Performance Considerations @@ -324,5 +351,4 @@ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENS This library builds upon the excellent work of: - Apache Milagro Crypto Library (AMCL) -- ConsenSys Gurvy library -- Kilic BLS12-381 implementation \ No newline at end of file +- ConsenSys Gurvy library \ No newline at end of file diff --git a/driver/common/common_test.go b/driver/common/common_test.go new file mode 100644 index 0000000..1de8ed4 --- /dev/null +++ b/driver/common/common_test.go @@ -0,0 +1,392 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package common + +import ( + "math/big" + "testing" + + "github.com/IBM/mathlib/driver" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// --------------------------------------------------------------------------- +// BigToBytes +// --------------------------------------------------------------------------- + +func TestBigToBytesPositive(t *testing.T) { + // A small positive number should be right-padded with zeroes to 32 bytes. + n := big.NewInt(42) + b := BigToBytes(n) + assert.Len(t, b, ScalarByteSize) + // Last byte must be 42. + assert.Equal(t, byte(42), b[ScalarByteSize-1]) + // Leading bytes must be 0. + for _, byt := range b[:ScalarByteSize-1] { + assert.Equal(t, byte(0), byt) + } +} + +func TestBigToBytesZero(t *testing.T) { + b := BigToBytes(big.NewInt(0)) + assert.Len(t, b, ScalarByteSize) + for _, byt := range b { + assert.Equal(t, byte(0), byt) + } +} + +func TestBigToBytesNegative(t *testing.T) { + // -1 should produce the two's complement (0xFF…FF). + b := BigToBytes(big.NewInt(-1)) + assert.Len(t, b, ScalarByteSize) + for _, byt := range b { + assert.Equal(t, byte(0xff), byt) + } +} + +func TestBigToBytesRoundTrip(t *testing.T) { + // Encode then decode via SetBytes should reproduce the original value for + // positive numbers. + values := []*big.Int{ + big.NewInt(0), + big.NewInt(1), + big.NewInt(255), + big.NewInt(256), + new(big.Int).Lsh(big.NewInt(1), 200), + } + for _, v := range values { + b := BigToBytes(v) + assert.Len(t, b, ScalarByteSize) + back := new(big.Int).SetBytes(b) + assert.Zero(t, back.Cmp(v), "round-trip failed for %s", v) + } +} + +// --------------------------------------------------------------------------- +// BaseZr +// --------------------------------------------------------------------------- + +// modulus used across all BaseZr tests. +var testModulus = func() big.Int { + m, _ := new(big.Int).SetString( + "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", + 16, + ) + + return *m +}() + +func newZr(i int64) *BaseZr { + return &BaseZr{Int: *big.NewInt(i), Modulus: testModulus} +} + +func TestBaseZrIsZero(t *testing.T) { + assert.True(t, newZr(0).IsZero()) + assert.False(t, newZr(1).IsZero()) + assert.False(t, newZr(-1).IsZero()) +} + +func TestBaseZrIsOne(t *testing.T) { + assert.True(t, newZr(1).IsOne()) + assert.False(t, newZr(0).IsOne()) + assert.False(t, newZr(2).IsOne()) + assert.False(t, newZr(-1).IsOne()) +} + +func TestBaseZrBigInt(t *testing.T) { + z := newZr(99) + assert.Zero(t, z.BigInt().Cmp(big.NewInt(99))) +} + +func TestBaseZrPlus(t *testing.T) { + a := newZr(5) + b := newZr(7) + c := a.Plus(b).(*BaseZr) + assert.Zero(t, c.Cmp(big.NewInt(12))) +} + +func TestBaseZrMinus(t *testing.T) { + a := newZr(10) + b := newZr(3) + c := a.Minus(b).(*BaseZr) + assert.Zero(t, c.Cmp(big.NewInt(7))) +} + +func TestBaseZrMul(t *testing.T) { + a := newZr(6) + b := newZr(7) + c := a.Mul(b).(*BaseZr) + assert.Zero(t, c.Cmp(big.NewInt(42))) +} + +func TestBaseZrPowMod(t *testing.T) { + // 2^10 mod p == 1024 + a := newZr(2) + exp := newZr(10) + r := a.PowMod(exp).(*BaseZr) + assert.Zero(t, r.Cmp(big.NewInt(1024))) +} + +func TestBaseZrMod(t *testing.T) { + a := newZr(7) + m := newZr(3) + a.Mod(m) + assert.Zero(t, a.Cmp(big.NewInt(1))) +} + +func TestBaseZrInvModP(t *testing.T) { + // 3 * 4 ≡ 1 (mod 11) + a := newZr(3) + p := newZr(11) + a.InvModP(p) + assert.Zero(t, a.Cmp(big.NewInt(4))) +} + +func TestBaseZrInvModOrder(t *testing.T) { + a := newZr(7) + orig := new(big.Int).Set(&a.Int) + a.InvModOrder() + // a * a^-1 ≡ 1 (mod modulus) + prod := new(big.Int).Mul(orig, &a.Int) + prod.Mod(prod, &testModulus) + assert.Zero(t, prod.Cmp(big.NewInt(1))) +} + +func TestBaseZrBytesPositive(t *testing.T) { + a := newZr(123) + b := a.Bytes() + assert.Len(t, b, ScalarByteSize) + // Decode and compare + back := new(big.Int).SetBytes(b) + assert.Zero(t, back.Cmp(big.NewInt(123))) +} + +func TestBaseZrBytesNegative(t *testing.T) { + // A negative number is reduced mod p before encoding. + a := &BaseZr{Int: *big.NewInt(-1), Modulus: testModulus} + b := a.Bytes() + assert.Len(t, b, ScalarByteSize) + // The result should equal (p - 1). + expected := new(big.Int).Sub(&testModulus, big.NewInt(1)) + back := new(big.Int).SetBytes(b) + assert.Zero(t, back.Cmp(expected)) +} + +func TestBaseZrEquals(t *testing.T) { + a := newZr(42) + b := newZr(42) + c := newZr(43) + assert.True(t, a.Equals(b)) + assert.False(t, a.Equals(c)) +} + +func TestBaseZrCopy(t *testing.T) { + a := newZr(55) + b := a.Copy().(*BaseZr) + assert.True(t, a.Equals(b)) + // Mutating the copy must not affect the original. + b.Add(&b.Int, big.NewInt(1)) + assert.False(t, a.Equals(b)) +} + +func TestBaseZrClone(t *testing.T) { + a := newZr(77) + b := newZr(0) + b.Clone(a) + assert.True(t, a.Equals(b)) +} + +func TestBaseZrString(t *testing.T) { + a := newZr(255) + assert.Equal(t, "ff", a.String()) +} + +func TestBaseZrNeg(t *testing.T) { + a := newZr(10) + a.Neg() + assert.Zero(t, a.Cmp(big.NewInt(-10))) +} + +// --------------------------------------------------------------------------- +// CurveBase +// --------------------------------------------------------------------------- + +func newCurveBase() *CurveBase { + return &CurveBase{Modulus: testModulus} +} + +func newCBZr(i int64) *BaseZr { + cb := newCurveBase() + + return cb.NewZrFromInt64(i).(*BaseZr) +} + +func TestCurveBaseGroupOrder(t *testing.T) { + cb := newCurveBase() + order := cb.GroupOrder().(*BaseZr) + assert.Zero(t, order.Cmp(&testModulus)) +} + +func TestCurveBaseNewZrFromBytes(t *testing.T) { + cb := newCurveBase() + b := BigToBytes(big.NewInt(9999)) + z := cb.NewZrFromBytes(b).(*BaseZr) + assert.Zero(t, z.Cmp(big.NewInt(9999))) +} + +func TestCurveBaseNewZrFromInt64(t *testing.T) { + cb := newCurveBase() + z := cb.NewZrFromInt64(12345).(*BaseZr) + assert.Zero(t, z.Cmp(big.NewInt(12345))) +} + +func TestCurveBaseNewZrFromUint64(t *testing.T) { + cb := newCurveBase() + z := cb.NewZrFromUint64(99999).(*BaseZr) + assert.Zero(t, z.Cmp(new(big.Int).SetUint64(99999))) +} + +func TestCurveBaseNewZrFromBigInt(t *testing.T) { + cb := newCurveBase() + n := big.NewInt(77777) + z := cb.NewZrFromBigInt(n).(*BaseZr) + assert.Zero(t, z.Cmp(n)) +} + +func TestCurveBaseNewRandomZr(t *testing.T) { + cb := newCurveBase() + rng, err := cb.Rand() + require.NoError(t, err) + r := cb.NewRandomZr(rng).(*BaseZr) + // Must be in [0, modulus) + assert.GreaterOrEqual(t, r.Sign(), 0) + assert.Negative(t, r.Cmp(&testModulus)) +} + +func TestCurveBaseHashToZr(t *testing.T) { + cb := newCurveBase() + h := cb.HashToZr([]byte("hello")).(*BaseZr) + assert.GreaterOrEqual(t, h.Sign(), 0) + assert.Negative(t, h.Cmp(&testModulus)) +} + +func TestCurveBaseModNeg(t *testing.T) { + cb := newCurveBase() + m := newCBZr(11) + a := newCBZr(3) + neg := cb.ModNeg(a, m).(*BaseZr) + // a + (-a) ≡ 0 (mod m) + sum := new(big.Int).Add(&a.Int, &neg.Int) + sum.Mod(sum, big.NewInt(11)) + assert.Zero(t, sum.Cmp(big.NewInt(0))) +} + +func TestCurveBaseModSub(t *testing.T) { + cb := newCurveBase() + m := newCBZr(11) + a := newCBZr(8) + b := newCBZr(3) + r := cb.ModSub(a, b, m).(*BaseZr) + assert.Zero(t, r.Cmp(big.NewInt(5))) +} + +func TestCurveBaseModAdd(t *testing.T) { + cb := newCurveBase() + m := newCBZr(11) + a := newCBZr(8) + b := newCBZr(6) + r := cb.ModAdd(a, b, m).(*BaseZr) + assert.Zero(t, r.Cmp(big.NewInt(3))) // (8+6) mod 11 = 3 +} + +func TestCurveBaseModMul(t *testing.T) { + cb := newCurveBase() + m := newCBZr(11) + a := newCBZr(4) + b := newCBZr(5) + r := cb.ModMul(a, b, m).(*BaseZr) + assert.Zero(t, r.Cmp(big.NewInt(9))) // 20 mod 11 = 9 +} + +func TestCurveBaseModAddMul(t *testing.T) { + cb := newCurveBase() + m := newCBZr(100) + a1 := newCBZr(3) + b1 := newCBZr(4) + a2 := newCBZr(5) + b2 := newCBZr(6) + // (3*4) + (5*6) = 12 + 30 = 42 + r := cb.ModAddMul( + []driver.Zr{a1, a2}, + []driver.Zr{b1, b2}, + m, + ) + assert.Zero(t, r.(*BaseZr).Cmp(big.NewInt(42))) +} + +func TestCurveBaseModAddMul2(t *testing.T) { + cb := newCurveBase() + m := newCBZr(100) + a1 := newCBZr(3) + c1 := newCBZr(4) + b1 := newCBZr(5) + c2 := newCBZr(6) + // (3*4) + (5*6) = 42 + r := cb.ModAddMul2(a1, c1, b1, c2, m).(*BaseZr) + assert.Zero(t, r.Cmp(big.NewInt(42))) +} + +func TestCurveBaseModAddMul3(t *testing.T) { + cb := newCurveBase() + m := newCBZr(1000) + a1 := newCBZr(2) + a2 := newCBZr(3) + b1 := newCBZr(4) + b2 := newCBZr(5) + c1 := newCBZr(6) + c2 := newCBZr(7) + // (2*3) + (4*5) + (6*7) = 6 + 20 + 42 = 68 + r := cb.ModAddMul3(a1, a2, b1, b2, c1, c2, m).(*BaseZr) + assert.Zero(t, r.Cmp(big.NewInt(68))) +} + +func TestCurveBaseModMulInPlace(t *testing.T) { + cb := newCurveBase() + m := newCBZr(100) + result := newCBZr(0) + a := newCBZr(6) + b := newCBZr(7) + cb.ModMulInPlace(result, a, b, m) + assert.Zero(t, result.Cmp(big.NewInt(42))) +} + +func TestCurveBaseModAddMul2InPlace(t *testing.T) { + cb := newCurveBase() + m := newCBZr(100) + result := newCBZr(0) + a1 := newCBZr(3) + c1 := newCBZr(4) + b1 := newCBZr(5) + c2 := newCBZr(6) + cb.ModAddMul2InPlace(result, a1, c1, b1, c2, m) + assert.Zero(t, result.Cmp(big.NewInt(42))) +} + +func TestCurveBaseModAddMul3InPlace(t *testing.T) { + cb := newCurveBase() + m := newCBZr(1000) + result := newCBZr(0) + a1 := newCBZr(2) + a2 := newCBZr(3) + b1 := newCBZr(4) + b2 := newCBZr(5) + c1 := newCBZr(6) + c2 := newCBZr(7) + cb.ModAddMul3InPlace(result, a1, a2, b1, b2, c1, c2, m) + assert.Zero(t, result.Cmp(big.NewInt(68))) +} diff --git a/driver/gurvy/bls12381/bls12381_test.go b/driver/gurvy/bls12381/bls12381_test.go new file mode 100644 index 0000000..78f0b71 --- /dev/null +++ b/driver/gurvy/bls12381/bls12381_test.go @@ -0,0 +1,518 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Direct tests for the bls12381 driver package. +// These exercises every exported type and method directly so that the +// per-package coverage tool can count them. + +package bls12381 + +import ( + "math/big" + "testing" + + "github.com/IBM/mathlib/driver" + gnarkbls "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// --------------------------------------------------------------------------- +// helpers +// --------------------------------------------------------------------------- + +func newCurve() *Curve { return NewCurve() } + +func randZr(t *testing.T, c *Curve) driver.Zr { + t.Helper() + r, err := c.Rand() + require.NoError(t, err) + + return c.NewRandomZr(r) +} + +// --------------------------------------------------------------------------- +// Zr +// --------------------------------------------------------------------------- + +func TestZrIsZeroIsOne(t *testing.T) { + c := newCurve() + assert.True(t, c.NewZrFromInt64(0).IsZero()) + assert.False(t, c.NewZrFromInt64(1).IsZero()) + assert.True(t, c.NewZrFromInt64(1).IsOne()) + assert.False(t, c.NewZrFromInt64(0).IsOne()) + assert.False(t, c.NewZrFromInt64(2).IsOne()) +} + +func TestZrBigInt(t *testing.T) { + c := newCurve() + z := c.NewZrFromInt64(777) + bi := z.BigInt() + assert.Zero(t, bi.Cmp(big.NewInt(777))) +} + +func TestZrPlus(t *testing.T) { + c := newCurve() + a := c.NewZrFromInt64(5) + b := c.NewZrFromInt64(7) + s := a.Plus(b) + assert.True(t, s.Equals(c.NewZrFromInt64(12))) +} + +func TestZrMinus(t *testing.T) { + c := newCurve() + a := c.NewZrFromInt64(10) + b := c.NewZrFromInt64(3) + d := a.Minus(b) + assert.True(t, d.Equals(c.NewZrFromInt64(7))) +} + +func TestZrMul(t *testing.T) { + c := newCurve() + a := c.NewZrFromInt64(6) + b := c.NewZrFromInt64(7) + p := a.Mul(b) + assert.True(t, p.Equals(c.NewZrFromInt64(42))) +} + +func TestZrPowMod(t *testing.T) { + c := newCurve() + base := c.NewZrFromInt64(2) + exp := c.NewZrFromInt64(10) + r := base.PowMod(exp) + assert.True(t, r.Equals(c.NewZrFromInt64(1024))) +} + +func TestZrMod(t *testing.T) { + c := newCurve() + a := c.NewZrFromInt64(7) + m := c.NewZrFromInt64(3) + a.Mod(m) + assert.True(t, a.Equals(c.NewZrFromInt64(1))) +} + +func TestZrInvModP(t *testing.T) { + c := newCurve() + // 3 * 4 ≡ 1 (mod 11) + a := c.NewZrFromInt64(3) + a.InvModP(c.NewZrFromInt64(11)) + assert.True(t, a.Equals(c.NewZrFromInt64(4))) +} + +func TestZrInvModOrder(t *testing.T) { + c := newCurve() + a := c.NewZrFromInt64(7) + orig := a.Copy() + a.InvModOrder() + assert.True(t, orig.Mul(a).Equals(c.NewZrFromInt64(1))) +} + +func TestZrBytes(t *testing.T) { + c := newCurve() + a := c.NewZrFromInt64(12345) + b := a.Bytes() + back := c.NewZrFromBytes(b) + assert.True(t, a.Equals(back)) +} + +func TestZrCopyClone(t *testing.T) { + c := newCurve() + a := c.NewZrFromInt64(99) + cp := a.Copy() + assert.True(t, a.Equals(cp)) + + cl := c.NewZrFromInt64(0) + cl.Clone(a) + assert.True(t, a.Equals(cl)) +} + +func TestZrString(t *testing.T) { + c := newCurve() + s := c.NewZrFromInt64(255).String() + assert.Equal(t, "ff", s) +} + +func TestZrNeg(t *testing.T) { + c := newCurve() + a := c.NewZrFromInt64(10) + m := c.GroupOrder() + a.Neg() + sum := a.Plus(c.NewZrFromInt64(10)) + sum.Mod(m) + assert.True(t, sum.IsZero()) +} + +func TestZrFromUint64(t *testing.T) { + c := newCurve() + u := c.NewZrFromUint64(999) + assert.True(t, u.Equals(c.NewZrFromInt64(999))) +} + +func TestZrFromBigInt(t *testing.T) { + c := newCurve() + n := big.NewInt(54321) + z := c.NewZrFromBigInt(n) + assert.True(t, z.Equals(c.NewZrFromInt64(54321))) +} + +func TestGroupOrder(t *testing.T) { + c := newCurve() + order := c.GroupOrder() + assert.False(t, order.IsZero()) +} + +// --------------------------------------------------------------------------- +// G1 +// --------------------------------------------------------------------------- + +func TestG1Ops(t *testing.T) { + c := newCurve() + r := randZr(t, c) + + g := c.GenG1() + assert.False(t, g.IsInfinity()) + + g35 := g.Mul(c.NewZrFromInt64(35)) + g23 := g.Mul(c.NewZrFromInt64(23)) + g58 := g.Mul(c.NewZrFromInt64(58)) + + sum := g35.Copy() + sum.Add(g23) + assert.True(t, sum.Equals(g58)) + + sub := g58.Copy() + sub.Sub(g23) + assert.True(t, sub.Equals(g35)) + + // IsInfinity after self-subtraction + inf := g35.Copy() + inf.Sub(g35.Copy()) + assert.True(t, inf.IsInfinity()) + + // Neg + neg := g35.Copy() + neg.Neg() + neg.Add(g35.Copy()) + assert.True(t, neg.IsInfinity()) + + // Mul2 + m2 := g.Mul2(c.NewZrFromInt64(35), g, c.NewZrFromInt64(23)) + assert.True(t, m2.Equals(g58)) + + // Mul2InPlace + m3 := g.Copy() + m3.Mul2InPlace(c.NewZrFromInt64(35), g, c.NewZrFromInt64(23)) + assert.True(t, m3.Equals(g58)) + + // Clone + clone := c.NewG1() + clone.Clone(g35) + assert.True(t, clone.Equals(g35)) + + // Bytes round-trip + pt := g.Mul(r) + raw := pt.Bytes() + back := c.NewG1FromBytes(raw) + assert.True(t, pt.Equals(back)) + + // Compressed round-trip + comp := pt.Compressed() + backComp := c.NewG1FromCompressed(comp) + assert.True(t, pt.Equals(backComp)) + + // String (must not panic) + _ = g.String() +} + +func TestG1InvalidBytes(t *testing.T) { + c := newCurve() + assert.Panics(t, func() { c.NewG1FromBytes(nil) }) + assert.Panics(t, func() { c.NewG1FromCompressed(nil) }) +} + +// --------------------------------------------------------------------------- +// G2 +// --------------------------------------------------------------------------- + +func TestG2Ops(t *testing.T) { + c := newCurve() + r := randZr(t, c) + + g := c.GenG2() + + g35 := g.Mul(c.NewZrFromInt64(35)) + g23 := g.Mul(c.NewZrFromInt64(23)) + g58 := g.Mul(c.NewZrFromInt64(58)) + + sum := g35.Copy() + sum.Add(g23) + assert.True(t, sum.Equals(g58)) + + sub := g58.Copy() + sub.Sub(g23) + assert.True(t, sub.Equals(g35)) + + // Affine (no-op, must not panic) + g35.Affine() + + // Clone + clone := c.NewG2() + clone.Clone(g35) + assert.True(t, clone.Equals(g35)) + + // Bytes / Compressed round-trip + pt := g.Mul(r) + raw := pt.Bytes() + back := c.NewG2FromBytes(raw) + assert.True(t, pt.Equals(back)) + + comp := pt.Compressed() + backComp := c.NewG2FromCompressed(comp) + assert.True(t, pt.Equals(backComp)) + + // String + _ = g.String() +} + +func TestG2InvalidBytes(t *testing.T) { + c := newCurve() + assert.Panics(t, func() { c.NewG2FromBytes(nil) }) + assert.Panics(t, func() { c.NewG2FromCompressed(nil) }) +} + +// --------------------------------------------------------------------------- +// Gt +// --------------------------------------------------------------------------- + +func TestGtOps(t *testing.T) { + c := newCurve() + r := randZr(t, c) + + g1 := c.GenG1() + g2 := c.GenG2() + + gt := c.FExp(c.Pairing(g2, g1)) + + // Exp(1) == gt + assert.True(t, gt.Equals(gt.Exp(c.NewZrFromInt64(1)))) + + // gt * gt^-1 == unity + inv := c.NewGtFromBytes(gt.Bytes()) + inv.Inverse() + gt.Mul(inv) + assert.True(t, gt.IsUnity()) + + // Bytes round-trip + gtr := c.FExp(c.Pairing(g2.Mul(r), g1)) + b := gtr.Bytes() + back := c.NewGtFromBytes(b) + assert.True(t, gtr.Equals(back)) + + // ToString + assert.NotEmpty(t, gtr.ToString()) +} + +func TestGtInvalidBytes(t *testing.T) { + c := newCurve() + assert.Panics(t, func() { c.NewGtFromBytes(nil) }) +} + +// --------------------------------------------------------------------------- +// Pairing +// --------------------------------------------------------------------------- + +func TestPairing(t *testing.T) { + c := newCurve() + r, err := c.Rand() + require.NoError(t, err) + + s := c.NewRandomZr(r) + g1 := c.GenG1() + g2 := c.GenG2() + + a := c.FExp(c.Pairing(g2.Mul(s), g1)) + b := c.FExp(c.Pairing(g2, g1.Mul(s))) + assert.True(t, a.Equals(b)) + + // Pairing2 + r1 := c.NewRandomZr(r) + r2 := c.NewRandomZr(r) + r3 := c.NewRandomZr(r) + r4 := c.NewRandomZr(r) + + p := g2.Mul(r1) + q := g1.Mul(r2) + rg2 := g2.Mul(r3) + sg1 := g1.Mul(r4) + + tt1 := c.FExp(c.Pairing2(p, rg2, q, sg1)) + tt2 := c.FExp(c.Pairing(g2.Mul(r1).Mul(r2), g1)) + tt3 := c.FExp(c.Pairing(g2, g1.Mul(r3).Mul(r4))) + tt2.Mul(tt3) + assert.True(t, tt1.Equals(tt2)) +} + +// --------------------------------------------------------------------------- +// Hash functions +// --------------------------------------------------------------------------- + +func TestHashToG1(t *testing.T) { + c := newCurve() + msg := []byte("hello bls12-381") + + h := c.HashToG1(msg) + assert.False(t, h.IsInfinity()) + h2 := c.HashToG1(msg) + assert.True(t, h.Equals(h2)) + + hd := c.HashToG1WithDomain(msg, []byte("domain")) + assert.False(t, hd.IsInfinity()) +} + +func TestHashToG2(t *testing.T) { + c := newCurve() + msg := []byte("hello g2") + h := c.HashToG2(msg) + assert.NotEmpty(t, h.Bytes()) + hd := c.HashToG2WithDomain(msg, []byte("dom")) + assert.NotEmpty(t, hd.Bytes()) +} + +func TestHashToZr(t *testing.T) { + c := newCurve() + h := c.HashToZr([]byte("some data")) + assert.False(t, h.IsZero()) +} + +// --------------------------------------------------------------------------- +// BBSCurve +// --------------------------------------------------------------------------- + +func TestBBSCurveHashToG1(t *testing.T) { + bbs := NewBBSCurve() + msg := []byte("bbs test") + h := bbs.HashToG1(msg) + assert.False(t, h.IsInfinity()) + + hd := bbs.HashToG1WithDomain(msg, []byte("dom")) + assert.False(t, hd.IsInfinity()) +} + +func TestBBSCurveHashToG2(t *testing.T) { + bbs := NewBBSCurve() + msg := []byte("bbs g2") + h := bbs.HashToG2(msg) + assert.NotEmpty(t, h.Bytes()) + + hd := bbs.HashToG2WithDomain(msg, []byte("dom")) + assert.NotEmpty(t, hd.Bytes()) +} + +// --------------------------------------------------------------------------- +// Mod arithmetic +// --------------------------------------------------------------------------- + +func TestModArith(t *testing.T) { + c := newCurve() + m := c.GroupOrder() + a := c.NewZrFromInt64(8) + b := c.NewZrFromInt64(3) + + assert.True(t, c.ModAdd(a, b, m).Equals(c.NewZrFromInt64(11))) + assert.True(t, c.ModSub(a, b, m).Equals(c.NewZrFromInt64(5))) + assert.True(t, c.ModMul(a, b, m).Equals(c.NewZrFromInt64(24))) + + neg := c.ModNeg(b, m) + sum := c.ModAdd(b, neg, m) + assert.True(t, sum.IsZero()) + + // ModAddMul2 + r := c.ModAddMul2(a, b, b, a, m) + assert.True(t, r.Equals(c.NewZrFromInt64(48))) // (8*3)+(3*8)=48 + + // ModAddMul3 + c1 := c.NewZrFromInt64(2) + r3 := c.ModAddMul3(a, b, b, a, c1, c1, m) + assert.True(t, r3.Equals(c.NewZrFromInt64(52))) // 24+24+4=52 + + // ModAddMul (slice form) + sl := c.ModAddMul([]driver.Zr{a, b}, []driver.Zr{b, a}, m) + assert.True(t, sl.Equals(c.NewZrFromInt64(48))) + + // InPlace forms + res := c.NewZrFromInt64(0) + c.ModMulInPlace(res, a, b, m) + assert.True(t, res.Equals(c.NewZrFromInt64(24))) + + res2 := c.NewZrFromInt64(0) + c.ModAddMul2InPlace(res2, a, b, b, a, m) + assert.True(t, res2.Equals(c.NewZrFromInt64(48))) + + res3 := c.NewZrFromInt64(0) + c.ModAddMul3InPlace(res3, a, b, b, a, c1, c1, m) + assert.True(t, res3.Equals(c.NewZrFromInt64(52))) +} + +// --------------------------------------------------------------------------- +// MultiScalarMul +// --------------------------------------------------------------------------- + +func TestMultiScalarMul(t *testing.T) { + c := newCurve() + r, err := c.Rand() + require.NoError(t, err) + + n := 5 + pts := make([]driver.G1, n) + scalars := make([]driver.Zr, n) + for i := range n { + scalars[i] = c.NewRandomZr(r) + pts[i] = c.GenG1().Mul(c.NewRandomZr(r)) + } + + naive := c.NewG1() + for i := range n { + naive.Add(pts[i].Mul(scalars[i])) + } + msm := c.MultiScalarMul(pts, scalars) + assert.True(t, naive.Equals(msm)) +} + +// --------------------------------------------------------------------------- +// Size accessors +// --------------------------------------------------------------------------- + +func TestCurveSizes(t *testing.T) { + c := newCurve() + assert.Positive(t, c.CoordinateByteSize()) + assert.Positive(t, c.G1ByteSize()) + assert.Positive(t, c.CompressedG1ByteSize()) + assert.Positive(t, c.G2ByteSize()) + assert.Positive(t, c.CompressedG2ByteSize()) + assert.Positive(t, c.ScalarByteSize()) +} + +// --------------------------------------------------------------------------- +// JointScalarMultiplication +// --------------------------------------------------------------------------- + +func TestJointScalarMultiplication(t *testing.T) { + c := newCurve() + r, err := c.Rand() + require.NoError(t, err) + + s1 := c.NewRandomZr(r).BigInt() + s2 := c.NewRandomZr(r).BigInt() + + g1a := c.GenG1().(*G1) + g1b := c.GenG1().Mul(c.NewZrFromInt64(3)).(*G1) + + p := &bls12381G1Jac{} + JointScalarMultiplication(p, &g1a.G1Affine, &g1b.G1Affine, s1, s2) + assert.NotNil(t, p) +} + +type bls12381G1Jac = gnarkbls.G1Jac diff --git a/driver/gurvy/compat/compat_kilic_test.go b/driver/gurvy/compat/compat_kilic_test.go new file mode 100644 index 0000000..1730068 --- /dev/null +++ b/driver/gurvy/compat/compat_kilic_test.go @@ -0,0 +1,240 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package compat provides oracle-based compatibility tests that verify +// the driver/gurvy implementations match kilic/bls12-381 exactly. +// +// This package lives in its own Go module so that the kilic dependency is +// isolated here and does not appear in the main module's dependency graph. +// Run with: +// +// cd driver/gurvy/compat && go test ./... +package compat_test + +import ( + "crypto/sha256" + "testing" + "unsafe" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" + kilic "github.com/kilic/bls12-381" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/IBM/mathlib/driver/gurvy" +) + +// --------------------------------------------------------------------------- +// Kilic internal-function bindings via go:linkname +// --------------------------------------------------------------------------- + +type kilicFe [6]uint64 + +//go:linkname kilicAdd github.com/kilic/bls12-381.add +func kilicAdd(c, a, b *kilicFe) + +//go:linkname kilicToMont github.com/kilic/bls12-381.toMont +func kilicToMont(a, b *kilicFe) + +//go:linkname kilicFromMont github.com/kilic/bls12-381.fromMont +func kilicFromMont(c, a *kilicFe) + +//go:linkname kilicInverse github.com/kilic/bls12-381.inverse +func kilicInverse(inv, e *kilicFe) + +//go:linkname kilicIsQuadraticNonResidue github.com/kilic/bls12-381.isQuadraticNonResidue +func kilicIsQuadraticNonResidue(a *kilicFe) bool + +//go:linkname kilicSqrt github.com/kilic/bls12-381.sqrt +func kilicSqrt(c, a *kilicFe) bool + +//go:linkname kilicSquare github.com/kilic/bls12-381.square +func kilicSquare(c, a *kilicFe) + +//go:linkname kilicNeg github.com/kilic/bls12-381.neg +func kilicNeg(c, a *kilicFe) + +//go:linkname kilicIsogenyMapG1 github.com/kilic/bls12-381.isogenyMapG1 +func kilicIsogenyMapG1(x, y *kilicFe) + +//go:linkname kilicSwuMapG1 github.com/kilic/bls12-381.swuMapG1 +func kilicSwuMapG1(u *kilicFe) (*kilicFe, *kilicFe) + +//go:linkname kilicHashToFpXMDSHA256 github.com/kilic/bls12-381.hashToFpXMDSHA256 +func kilicHashToFpXMDSHA256(msg []byte, domain []byte, count int) ([]*kilicFe, error) + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +func toKilicFe(p *fp.Element) *kilicFe { + return (*kilicFe)(unsafe.Pointer(p)) +} + +func toFpElement(p *kilicFe) *fp.Element { + return (*fp.Element)(unsafe.Pointer(p)) +} + +// --------------------------------------------------------------------------- +// Oracle tests +// --------------------------------------------------------------------------- + +func TestCompatIsQuadraticResidue(t *testing.T) { + for range 1000 { + var x fp.Element + _, err := x.SetRandom() + require.NoError(t, err) + + kX := toKilicFe(&x) + kilicNonResidue := kilicIsQuadraticNonResidue(kX) + gurvyResidue := gurvy.IsQuadraticResidue(&x) + + if kilicNonResidue { + assert.Equal(t, uint64(0), gurvyResidue, "expected non-residue") + } else { + assert.Equal(t, uint64(1), gurvyResidue, "expected residue") + } + } +} + +func TestCompatSqrt(t *testing.T) { + for range 1000 { + var x fp.Element + _, err := x.SetRandom() + require.NoError(t, err) + + var x2 fp.Element + x2.Square(&x) + + kX2 := toKilicFe(&x2) + var kSqrt kilicFe + ok := kilicSqrt(&kSqrt, kX2) + require.True(t, ok, "kilic sqrt failed") + + var gSqrt fp.Element + gurvy.Sqrt(&gSqrt, &x2) + + gSqrtNeg := gSqrt + gSqrtNeg.Neg(&gSqrtNeg) + + kSqrtFp := toFpElement(&kSqrt) + assert.True(t, gSqrt.Equal(kSqrtFp) || gSqrtNeg.Equal(kSqrtFp), "sqrt mismatch") + } +} + +func TestCompatSgn0(t *testing.T) { + // signBE is defined in kilic as checking if negZ.cmp(z) > -1 + // where negZ = -z, z = fromMont(e). + // So signBE(e) is true if -z >= z, which means z > (p-1)/2 or z == 0. + for range 1000 { + var x fp.Element + _, err := x.SetRandom() + require.NoError(t, err) + + kX := toKilicFe(&x) + + var z kilicFe + kilicFromMont(&z, kX) + var negZ kilicFe + kilicNeg(&negZ, &z) + + expectedSignBE := true + for j := 5; j >= 0; j-- { + if negZ[j] < z[j] { + expectedSignBE = false + + break + } else if negZ[j] > z[j] { + break + } + } + + gurvySign := gurvy.Sgn0(&x) + if expectedSignBE { + assert.Equal(t, uint64(1), gurvySign, "expected signBE match") + } else { + assert.Equal(t, uint64(0), gurvySign, "expected signBE match") + } + } +} + +func TestCompatSwuMapG1BE(t *testing.T) { + for range 1000 { + var u fp.Element + _, err := u.SetRandom() + require.NoError(t, err) + + kU := toKilicFe(&u) + kX, kY := kilicSwuMapG1(kU) + + gX, gY := gurvy.SwuMapG1BE(&u) + + assert.Equal(t, toFpElement(kX).Bytes(), gX.Bytes(), "X coordinate mismatch") + assert.Equal(t, toFpElement(kY).Bytes(), gY.Bytes(), "Y coordinate mismatch") + } +} + +func TestCompatIsogenyMapG1(t *testing.T) { + // The two isogeny implementations (kilic vs gnark) use different polynomial + // representations and only agree on valid points of the isogenous curve E'. + // Generate valid E' points via kilicSwuMapG1 before comparing. + for i := range 1000 { + var u fp.Element + _, err := u.SetRandom() + require.NoError(t, err) + + kU := toKilicFe(&u) + kX, kY := kilicSwuMapG1(kU) + + kilicIsogenyMapG1(kX, kY) + + gX, gY := gurvy.SwuMapG1BE(&u) + importX := *gX + importY := *gY + hash_to_curve.G1Isogeny(&importX, &importY) + + assert.Equal(t, toFpElement(kX).Bytes(), importX.Bytes(), "Isogeny X mismatch at i=%d", i) + assert.Equal(t, toFpElement(kY).Bytes(), importY.Bytes(), "Isogeny Y mismatch at i=%d", i) + } +} + +func TestCompatHash(t *testing.T) { + for i := range 100 { + msg := []byte(string(rune(i)) + "test_hash_msg") + domain := []byte("test_hash_domain") + + kU, err := kilicHashToFpXMDSHA256(msg, domain, 2) + require.NoError(t, err) + + gU, err := gurvy.Hash(msg, domain, 2, sha256.New) + require.NoError(t, err) + + assert.Equal(t, toFpElement(kU[0]).Bytes(), gU[0].Bytes(), "u0 mismatch for input %d", i) + assert.Equal(t, toFpElement(kU[1]).Bytes(), gU[1].Bytes(), "u1 mismatch for input %d", i) + } +} + +func TestCompatHashToG1GenericBESwu(t *testing.T) { + g1Kilic := kilic.NewG1() + + for i := range 100 { + msg := []byte(string(rune(i)) + "test_message_compat") + domain := []byte("test_domain_compat") + + kPoint, err := g1Kilic.HashToCurve(msg, domain) + require.NoError(t, err) + + kRaw := g1Kilic.ToUncompressed(kPoint) + + gPoint, err := gurvy.HashToG1GenericBESwu(msg, domain, sha256.New) + require.NoError(t, err) + + gRaw := gPoint.RawBytes() + + assert.Equal(t, kRaw[:], gRaw[:], "HashToG1GenericBESwu mismatch for input %d", i) + } +} diff --git a/driver/gurvy/compat/go.mod b/driver/gurvy/compat/go.mod new file mode 100644 index 0000000..ba1c960 --- /dev/null +++ b/driver/gurvy/compat/go.mod @@ -0,0 +1,20 @@ +module github.com/IBM/mathlib/driver/gurvy/compat + +go 1.26.3 + +replace github.com/IBM/mathlib => ../../.. + +require ( + github.com/IBM/mathlib v0.0.0-00010101000000-000000000000 + github.com/consensys/gnark-crypto v0.20.1 + github.com/kilic/bls12-381 v0.1.0 + github.com/stretchr/testify v1.11.1 +) + +require ( + github.com/bits-and-blooms/bitset v1.24.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.46.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/driver/gurvy/compat/go.sum b/driver/gurvy/compat/go.sum new file mode 100644 index 0000000..a09f388 --- /dev/null +++ b/driver/gurvy/compat/go.sum @@ -0,0 +1,30 @@ +github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= +github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/consensys/gnark-crypto v0.20.1 h1:PXDUBvk8AzhvWowHLWBEAfUQcV1/aZgWIqD6eMpXmDg= +github.com/consensys/gnark-crypto v0.20.1/go.mod h1:RBWrSgy+IDbGR69RRV313th3M/aZU1ubk2om+qHuTSc= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= +github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/driver/gurvy/compat_test.go b/driver/gurvy/compat_test.go new file mode 100644 index 0000000..2b39e47 --- /dev/null +++ b/driver/gurvy/compat_test.go @@ -0,0 +1,299 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package gurvy + +import ( + "crypto/sha256" + "encoding/hex" + "math/big" + "testing" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// --------------------------------------------------------------------------- +// TestCompatIsQuadraticResidue +// Property test: IsQuadraticResidue agrees with gnark-crypto fp.Sqrt. +// A value x is a residue iff Sqrt(x)² == x (and Sqrt is non-zero). +// Additionally, x² is always a residue. +// --------------------------------------------------------------------------- + +func TestCompatIsQuadraticResidue(t *testing.T) { + for i := range 1000 { + var x fp.Element + _, err := x.SetRandom() + require.NoError(t, err) + + // x² is always a quadratic residue + var x2 fp.Element + x2.Square(&x) + assert.Equal(t, uint64(1), IsQuadraticResidue(&x2), "x² must always be a residue") + + // For a random x, compare our result with gnark's Sqrt + var s fp.Element + s.Sqrt(&x) + var s2 fp.Element + s2.Square(&s) + gnarkResidue := s2.Equal(&x) // gnark sqrt: s²==x iff x is a residue (or zero) + if !x.IsZero() { + gurvyResidue := IsQuadraticResidue(&x) + if gnarkResidue { + assert.Equal(t, uint64(1), gurvyResidue, "gnark says residue; gurvy must agree (i=%d)", i) + } else { + assert.Equal(t, uint64(0), gurvyResidue, "gnark says non-residue; gurvy must agree (i=%d)", i) + } + } + } +} + +// --------------------------------------------------------------------------- +// TestCompatSqrt +// Property test: Sqrt(x²)² == x² (result is correct square root). +// --------------------------------------------------------------------------- + +func TestCompatSqrt(t *testing.T) { + for i := range 1000 { + var x fp.Element + _, err := x.SetRandom() + require.NoError(t, err) + + var x2 fp.Element + x2.Square(&x) + + var gSqrt fp.Element + Sqrt(&gSqrt, &x2) + + // gSqrt² must equal x² + var check fp.Element + check.Square(&gSqrt) + assert.True(t, check.Equal(&x2), "Sqrt(x²)² must equal x² (i=%d)", i) + } +} + +// --------------------------------------------------------------------------- +// TestCompatSgn0 +// Property test: Sgn0(x) == 1 iff x (in canonical form) ≤ (p-1)/2. +// The comparison is done using big.Int arithmetic. +// --------------------------------------------------------------------------- + +var pMinus1Over2BigInt *big.Int + +func init() { + // p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab + p, _ := new(big.Int).SetString( + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", + 10, + ) + pMinus1Over2BigInt = new(big.Int).Rsh(new(big.Int).Sub(p, big.NewInt(1)), 1) +} + +func TestCompatSgn0(t *testing.T) { + for i := range 1000 { + var x fp.Element + _, err := x.SetRandom() + require.NoError(t, err) + + // Canonical (non-Montgomery) representation as big.Int + bits := x.Bits() // little-endian limbs, canonical form + xBig := new(big.Int) + for j := 5; j >= 0; j-- { + xBig.Lsh(xBig, 64) + xBig.Or(xBig, new(big.Int).SetUint64(bits[j])) + } + + expectedSign := uint64(1) // 1 if x ≤ (p-1)/2 + if xBig.Cmp(pMinus1Over2BigInt) > 0 { + expectedSign = 0 + } + + assert.Equal(t, expectedSign, Sgn0(&x), "Sgn0 mismatch at i=%d", i) + } +} + +// --------------------------------------------------------------------------- +// TestCompatSwuMapG1BE +// Known-answer + property test. +// Vectors were generated by running the current SwuMapG1BE implementation. +// Property: the output lies on the isogenous curve E' (y² == x³ + a·x + b). +// --------------------------------------------------------------------------- + +var swuMapG1BEVectors = []struct{ uHex, xHex, yHex string }{ + {"065ff6427b0643bbd5de136d880db9a0cea91fda568cc5592e383453d770d6c975e3384aa5e74a2dfa17f29196cc128c", "0acc776da750b5095d0d8aab42ff4c77681d8738b5a86cd41837b671be16d3e0449e32684bca3170cfece28a05e1eb96", "0c6856cea1bc86340610f4ed95e3c845f19c03a748902472cf56d0af4dec2153e22e1a07fa11eb64787009a6017a6d50"}, + {"06f0d603b811e70310217be7917dc630cd5a6e7f7ffd6e76aca16152ee0054590a6a03f3089cd9958bb634fc3871a24a", "04fd00f11700da162b53cb61070da7b7eb5fdcf3abef8989da12c70568251678877d9bb4279c0ecbad529a669e67878d", "110f7bc6cc39c84757ac021cd6cb92a8cd59acc5f48ff759c7f13bb7b40eadd7cfbcae580e05cc9dd6eda371878aeee0"}, + {"0d5051d51146d2a7d730379cc156d4d1a687e9ec9401d65ff6c14cf3337419c398605accdc5979ba34b40ad7f9ab9229", "0d4fb0ec33958bed5b968c500065a662c6d7ea8f293f8d192ab59c24275392fa92a0da3d13fb5144667046668a55baea", "03e853037b5a8885889237c2b92e7169ef0fcc121e4d4412290112d2b5a414605de37a907d5d3329e80ac447f149cd9d"}, + {"04da65e7090cc541d6d917b9574901850758f25e8ec70f37172bf7d0b730ecaca7ca0902cbf10eeb6e0b16f5efc41a20", "0db07104cabc7d672b9730eb54cbffb5d73d94a02764a2ddcdb6e4c9ca0afbed8cabf640e02f0b900a931710806ba965", "0f4884ee64f61fd7cde720d603fef344a2e0276d2fc2bc2113bc79c7ec4240423d45aca459b817ed63b76b23ac39ead8"}, + {"0a5e748ae5d85cd7563e477894c2250248d797f050b3f9bde8d3ac2f55a19d481de6aabf0f688749d5ca478fecc7ecf9", "0de441585cdc30741bef0c5d37d8ada19474489602ff58e34136ce9cb87df4919986e18671a1f41af7e9c833cd1683fc", "00220991203ab05b933281fd1468ce0cee49f80202cb26d3a8938beef2f67604942edeb056afff914cea09d5c43b5a1d"}, + {"06e69f597f8274821a6fd2cec04c8f4cfdecda884e76f1963b5112c06c6b8774a37eda3b409fb752ad9645c293d579bc", "0bd4e508b27ce11f5039e4818f712a29ddeb0ecf6dd587a3443ad27a788c3ac7c346d78e250406d87b00dd81b20aaea8", "116f508aadae96815d1cc790802b05c4d6a152e2afc39ee16c05ba3bae96fd21830056e022d14a017027bd5a2c782834"}, + {"0d2be6c188e53ed32970645f4e1c0d3a2b5cb41cd077c856929d7ddb08f35772af65b25058172cee2cbb641019010a2d", "01a838b2bdff67ce0a25acd7b246ce53e08f2d418a3de6a170b9c6f333086e604702dd560173afcf7ec26f7998a55407", "0b188cc4796f8cfde3127c2e84de14d0664be6861d58c15c4c423cf48bb42910f8ecfeb523d9561ca0876db286d6e5dd"}, + {"11a2ccf0b7899073b838834ed8c5d223bc52d4e6dfbc87b900d68f7f4c3a8186ac801e4226d872c478bef39dad337cd0", "076605616e069d6bcabf48f8455194423edf658fca24fe1e1bcbf3f2252ef709b9108de039dc7f612bc3969c8b376563", "012710cd695cad2817812efe559099bcda6390f089481c804093c5878df72d319f9571e960b7dd6d51a7d59d197536ce"}, +} + +func mustDecodeHex48(t *testing.T, s string) [48]byte { + t.Helper() + b, err := hex.DecodeString(s) + require.NoError(t, err) + require.Len(t, b, 48) + var out [48]byte + copy(out[:], b) + + return out +} + +func mustFpFromHex(t *testing.T, s string) fp.Element { + t.Helper() + raw := mustDecodeHex48(t, s) + var e fp.Element + e.SetBytes(raw[:]) + + return e +} + +func TestCompatSwuMapG1BE(t *testing.T) { + for idx, v := range swuMapG1BEVectors { + u := mustFpFromHex(t, v.uHex) + wantX := mustDecodeHex48(t, v.xHex) + wantY := mustDecodeHex48(t, v.yHex) + + gotX, gotY := SwuMapG1BE(&u) + assert.Equal(t, wantX, gotX.Bytes(), "X mismatch at vector %d", idx) + assert.Equal(t, wantY, gotY.Bytes(), "Y mismatch at vector %d", idx) + } + + // Property: random inputs produce points on E' (y² == x³ + a·x + b) + for i := range 200 { + var u fp.Element + _, err := u.SetRandom() + require.NoError(t, err) + + gX, gY := SwuMapG1BE(&u) + + // Verify y² == x³ + swuA·x + swuB + var lhs, rhs, tmp fp.Element + lhs.Square(gY) // y² + rhs.Square(gX) + rhs.Add(&rhs, &swuA) + rhs.Mul(&rhs, gX) + tmp.Set(&swuB) + rhs.Add(&rhs, &tmp) + assert.True(t, lhs.Equal(&rhs), "SwuMapG1BE output not on E' at i=%d", i) + } +} + +// --------------------------------------------------------------------------- +// TestCompatIsogenyMapG1 +// Smoke test: gnark's G1Isogeny does not panic and produces a consistent +// result on valid E' points. gnark IS the implementation — no oracle needed. +// --------------------------------------------------------------------------- + +var isogenyMapG1SmokeVectors = []struct{ uHex, xHex, yHex string }{ + {"08fac73d5499381b6218f8a26acb705b5715447d9490c79832a04b5734faf2f59b14cc6c4c76336b110dea500eb989a6", "05db95201cfcc1b1670fdc158730523a4f5f269dbd0c83af6c10c6bbb4004ee5387ddd25c15bcc1011d728c883cce294", "1928754227e1b4386d7865795b13763529793175787338667154ec5e15ec2add9693a601bd2c987d5d2bbfa8c1f08a23"}, + {"082e3a50aeb5160697c38a88db433170e6535700073d97cb93ab01bf3e012c10534d98d4c5d721e9dc3a930e2bb80ded", "094dae268dd4f7517252f56537ed50f731a9977b4f9580306466afa8e79b59d14d330b5134c0f04b3646bf3687c68aac", "10691446df3e3427751be90c7232cf4c943e1084e6b39f352a4925f1029ddeb997eca8b5d4998e7d93498d6748dcad9e"}, + {"0cdfdab0c97105968793898681d710f8c13daaee0f17f7e5db88ae0cdb42eafa6d2ad39786dcdf0e9d599e9675bc8ab7", "00e86534a476dac7cb55167eb8c161029a1e8bf6fe73c96b0dde99a689e2e771116139466e81677aacae0486c9200cf6", "169b8b04eca6d391fd1d3292d812876aba14bb70063783192d1fc1290c6cb5199af28e85eb16a5172bc8c02a287bfad3"}, + {"165d36fd07187ec1400032446926c0934946f2585e58119e474e3428b1feba22f79a365e97e1515690afdfc72fecbd95", "0a1404eb9c26423e5d941a76df76cdead858627c26695fa86182e3d9d0439489a3d8867a2542e016a38fdac929e511e1", "069f4b0c3cc230f43264977ce61deb879b1246cfe5d2f2bbcfecbb1ad8a81e6d9d6e01f918f60130eb7c7b09eedd16dc"}, +} + +func TestCompatIsogenyMapG1(t *testing.T) { + // Known-answer: apply gnark's isogeny to our SwuMapG1BE output and verify + // the result matches pre-computed vectors. + for idx, v := range isogenyMapG1SmokeVectors { + u := mustFpFromHex(t, v.uHex) + wantX := mustDecodeHex48(t, v.xHex) + wantY := mustDecodeHex48(t, v.yHex) + + gX, gY := SwuMapG1BE(&u) + isoX := *gX + isoY := *gY + hash_to_curve.G1Isogeny(&isoX, &isoY) + + assert.Equal(t, wantX, isoX.Bytes(), "Isogeny X mismatch at vector %d", idx) + assert.Equal(t, wantY, isoY.Bytes(), "Isogeny Y mismatch at vector %d", idx) + } +} + +// --------------------------------------------------------------------------- +// TestCompatHash +// Known-answer test: Hash output matches pre-computed vectors. +// Vectors were generated by the current Hash implementation. +// --------------------------------------------------------------------------- + +var hashVectors = []struct{ msg, domain, u0Hex, u1Hex string }{ + {"\x00test_hash_msg", "test_hash_domain", "06fbab49b24f7e632408368f75b0571bec3791c8c55503b26af1f843aaaabeffbcfd43bf77c83fe9daefb379600ca082", "171afedb2ea0bd0ba2608d3c39b0f94be133d7d644d01d501b7c945236ad712fa58be02916d344e75f437aae98f503fb"}, + {"\x01test_hash_msg", "test_hash_domain", "14c6581ce7724881c0a56b9eeddc34be8e696bcc1c34f9c313a011c762a3f53f5623dab53357f16ec83c595c199b36b6", "19bfbb4021d442232cbc4db58b19e4833371f4b0080d89e40443a9c23c1e3c6292e290b4b70ca20cd0c7c6306ba3fe61"}, + {"\x02test_hash_msg", "test_hash_domain", "0d2cbd74a6b58ea9deb25a09fd9a200dd606bac11dc684162cb9271d8878d85d4e8ff94024f74d9d1fe77050ea760e16", "0b51d6149e185fd3015555797deb007d28165c23782ff481f359556a1173e06b633744750080e213ac7a190931e9675e"}, + {"\x03test_hash_msg", "test_hash_domain", "1379b7ebcc5d418cdd93b2460b2652c5cc40ce971cbd942926bd78998341b586c8a41432d7693007b02bcccbd1dcbf7a", "01518d82c4d341a2353b1ed6c9db980e7d942114873f0ab3d7374142849fe42b7bb39b0866e19af2b69471c5fba5b05d"}, + {"\x04test_hash_msg", "test_hash_domain", "166e78868b0f89f4df78e5abc8c1c167de37b10f2857e3e629a6ffea75318c934aeb03665aed608d66a2206052dbcceb", "0893ca79f875429c3e552d022adaf32944b60bebea89c951a82c6dc8006087dcab17159b42a6c5d3dd0656950f92a80e"}, + {"\x05test_hash_msg", "test_hash_domain", "012916b602a1daa746946aabfdd33b67ef0efc6120207483c4aefead368a8de478bf41f4699ef495726fdcea5b940c66", "0b109e9e255a036b81330bb0e292811a9d7d9b899fed486b12a7fd4d9cb43636088f50cbbb196e2281ac1ef712a6d8f6"}, + {"\x06test_hash_msg", "test_hash_domain", "07f452ab51fd0f6d94f923c3c3062e4555212525ff075ef504a3bd775cff6c6bd8bbb104e0b4cacc347819396037a8fd", "0b357647045e4d076bd67b3049254b1bfef65e312d4c32b44e5b094ab5e0af5c1d7f407e58a05977b8aa4be5bf2fbefc"}, + {"\atest_hash_msg", "test_hash_domain", "0535dbfca8567e51f2bcebc5868114cdd53ba016cf7dbfb3075ac15f2bb7fa59bfb91514bdc6bc1cba802873c4e2692f", "125fcc2dfec5da9e51e639f81a78681b191b05795156f0a7f076058214dcbc5d3bb23aa7e9c213f149319a3492af8ca4"}, +} + +func TestCompatHash(t *testing.T) { + for idx, v := range hashVectors { + wantU0 := mustDecodeHex48(t, v.u0Hex) + wantU1 := mustDecodeHex48(t, v.u1Hex) + + gU, err := Hash([]byte(v.msg), []byte(v.domain), 2, sha256.New) + require.NoError(t, err) + + assert.Equal(t, wantU0, gU[0].Bytes(), "u0 mismatch at vector %d", idx) + assert.Equal(t, wantU1, gU[1].Bytes(), "u1 mismatch at vector %d", idx) + } +} + +// --------------------------------------------------------------------------- +// TestCompatHashToG1GenericBESwu +// Known-answer + property test. +// Vectors were generated by the current HashToG1GenericBESwu implementation. +// Property: all outputs are valid BLS12-381 G1 points (on curve + in subgroup). +// --------------------------------------------------------------------------- + +var hashToG1Vectors = []struct{ msg, domain, pointHex string }{ + {"\x00test_message_compat", "test_domain_compat", "0f1b515bcdf56b17354a57a65654d96b13e85963150e3a0e82befa1c0f534cc9a1c9eacb079a26944a1ac68046ad4f9a06021b3be13b29c2dadd9991958178df24886384e8b7cf6fee095bda3ff3f1277ecc1f1f3d75e9f05ce2d48a731c9498"}, + {"\x01test_message_compat", "test_domain_compat", "15fd546a343ad1bcad7744b92938f093f9b147fc68df48cd1d30dc83fc4384ed2ccb6be5c8638f1adf2a87f93c76b1c50efec8a48882d6a6f1227bec816aaaf3e248fa4bafdfbc35279a70ec0b806898fb4944286b86e1ca91c605a3ef828e65"}, + {"\x02test_message_compat", "test_domain_compat", "0d863b66cdb37246617faf00749e690c69241488d3d68bde10ed5ad7edf7973bf3546a9e6e6a772c4cc5fab0884e980816f9898387e818c3a8a4de68356aa1201f63fbcc7a15ec00bff86b9a46c9b693dfd3f6b249daef0450093b98c374c04d"}, + {"\x03test_message_compat", "test_domain_compat", "15422eb28e1e8181b3f5e835afd568ea158247013f71c39966972a9c77968fdf30bd9a10acd77d547295ec99af9e39810e48848502a79553c1b4c0e5ea98b2c0cd4a1fb7dea85508f741aaaa2272eb75e6cfb841dc61b2de965283202c721112"}, + {"\x04test_message_compat", "test_domain_compat", "0cd46a05b862ccd88319db39ffd1bd6e38c008488bf067d7db93e1b53557813def39b2694259c495a9f8f56fc79b316d14425f26dca82267163b3fc5da7ee1ea1e17cfec2bed69107bdc0e428882c72b41c950a53e07005e49943109e3bfdd9c"}, + {"\x05test_message_compat", "test_domain_compat", "10b3d47a246fe7e99f8cf12eb8eca29ae9ac257055dcf3ad18af0bdf8f9079630faf235ab2bface7b9ac05fd6c0f867118e83661d7be15ec5b341f7b4c775785a392125118c5eaed43895c507c378ce1ff01f808ea0986efad4f22926d2aa0a1"}, + {"\x06test_message_compat", "test_domain_compat", "1294f57a65af33b06d3ba7daf592e81e347f525a78aa81eb39a9eae99fc80d080bae0857ad7d8568c838cd1a27d3d61e0c8fe9ae1ffbf2de1a9ad74d0804ca723f7644d0cd33857b2a33dc25c889b20a68f25d9061e17323a51d896e0bfb53fc"}, + {"\atest_message_compat", "test_domain_compat", "0afbef0e9e9d9cc75a6cf2bf3dec0a0e79c91d20df2357cd91d975c680504d87dffee6f2d7643608ba8cd251ebaa7cbf12588250f3aa5e8ee4f3f16984f358042c2836f3b5de2723e3d35a4f85452545e9302749a21c001782c0d5d8bf2bf7e6"}, +} + +func TestCompatHashToG1GenericBESwu(t *testing.T) { + // Known-answer check + for idx, v := range hashToG1Vectors { + wantRaw, err := hex.DecodeString(v.pointHex) + require.NoError(t, err) + + gPoint, err := HashToG1GenericBESwu([]byte(v.msg), []byte(v.domain), sha256.New) + require.NoError(t, err) + + gotRaw := gPoint.RawBytes() + assert.Equal(t, wantRaw, gotRaw[:], "point mismatch at vector %d", idx) + } + + // Property: random inputs produce valid BLS12-381 G1 points + for i := range 50 { + msg := []byte(string(rune(i)) + "prop_test_msg") + domain := []byte("prop_test_domain") + + gPoint, err := HashToG1GenericBESwu(msg, domain, sha256.New) + require.NoError(t, err) + + assert.True(t, gPoint.IsOnCurve(), "output not on curve at i=%d", i) + assert.True(t, gPoint.IsInSubGroup(), "output not in subgroup at i=%d", i) + } + + // Zero-value check: the point must not be the identity (infinity) + gPoint, err := HashToG1GenericBESwu([]byte("non-empty"), []byte("domain"), sha256.New) + require.NoError(t, err) + var infinity bls12381.G1Affine + assert.False(t, gPoint.Equal(&infinity), "hash-to-curve must not return the identity") +} diff --git a/driver/gurvy/custom.go b/driver/gurvy/custom.go index b72e011..1306d19 100644 --- a/driver/gurvy/custom.go +++ b/driver/gurvy/custom.go @@ -10,13 +10,10 @@ package gurvy import ( "errors" "hash" - "unsafe" - "github.com/IBM/mathlib/driver/kilic" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" - "github.com/consensys/gnark-crypto/field/pool" ) const Bits = 381 // number of bits needed to represent a Element @@ -27,19 +24,8 @@ type G1Affine struct { X, Y fp.Element } -func toKilicElement(p *fp.Element) *kilic.Fe { - return (*kilic.Fe)(unsafe.Pointer(p)) -} - -func toGurvyElement(p *kilic.Fe) *fp.Element { - return (*fp.Element)(unsafe.Pointer(p)) -} - -func toGurvyAffine(p *G1Affine) *bls12381.G1Affine { - return (*bls12381.G1Affine)(unsafe.Pointer(p)) -} - // ExpandMsgXmd expands msg to a slice of lenInBytes bytes. +// Matches kilic's implementation for BESwu compatibility // https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-5 // https://tools.ietf.org/html/rfc8017#section-4.1 (I2OSP/O2ISP) func ExpandMsgXmd(msg, dst []byte, lenInBytes int, hashFunc func() hash.Hash) ([]byte, error) { @@ -55,10 +41,7 @@ func ExpandMsgXmd(msg, dst []byte, lenInBytes int, hashFunc func() hash.Hash) ([ } sizeDomain := uint8(dstLen) - // Z_pad = I2OSP(0, r_in_bytes) - // l_i_b_str = I2OSP(len_in_bytes, 2) - // DST_prime = I2OSP(len(DST), 1) ∥ DST - // b₀ = H(Z_pad ∥ msg ∥ l_i_b_str ∥ I2OSP(0, 1) ∥ DST_prime) + // b₀ = H(Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST || sizeDomain) h.Reset() if _, err := h.Write(make([]byte, h.BlockSize())); err != nil { return nil, err @@ -77,7 +60,7 @@ func ExpandMsgXmd(msg, dst []byte, lenInBytes int, hashFunc func() hash.Hash) ([ } b0 := h.Sum(nil) - // b₁ = H(b₀ ∥ I2OSP(1, 1) ∥ DST_prime) + // b₁ = H(b₀ || I2OSP(1, 1) || DST || sizeDomain) h.Reset() if _, err := h.Write(b0); err != nil { return nil, err @@ -97,7 +80,7 @@ func ExpandMsgXmd(msg, dst []byte, lenInBytes int, hashFunc func() hash.Hash) ([ copy(res[:h.Size()], b1) for i := 2; i <= ell; i++ { - // b_i = H(strxor(b₀, b_(i - 1)) ∥ I2OSP(i, 1) ∥ DST_prime) + // b_i = H(strxor(b₀, b_(i - 1)) || I2OSP(i, 1) || DST || sizeDomain) h.Reset() strxor := make([]byte, h.Size()) for j := range h.Size() { @@ -123,12 +106,11 @@ func ExpandMsgXmd(msg, dst []byte, lenInBytes int, hashFunc func() hash.Hash) ([ } // Hash msg to count prime field elements. -// https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-5.2 +// Uses the same method as kilic for BESwu compatibility: +// splits 64 bytes into two 32-byte chunks, converts each to field element, +// then combines as e1 + e0 * F where F = 2^256 * R (mod p) func Hash(msg, dst []byte, count int, hashFunc func() hash.Hash) ([]fp.Element, error) { - // 128 bits of security - // L = ceil((ceil(log2(p)) + k) / 8), where k is the security parameter = 128 - const Bytes = 1 + (Bits-1)/8 - const L = 16 + Bytes + const L = 64 // bytes per field element (per kilic's hashToFpXMD) lenInBytes := count * L pseudoRandomBytes, err := ExpandMsgXmd(msg, dst, lenInBytes, hashFunc) @@ -136,18 +118,35 @@ func Hash(msg, dst []byte, count int, hashFunc func() hash.Hash) ([]fp.Element, return nil, err } - // get temporary big int from the pool - vv := pool.BigInt.Get() + // F = 2^256 * R (Montgomery form) + // From kilic: F = 2^256 * R mod p + var F fp.Element + F[0] = 0x75b3cd7c5ce820f + F[1] = 0x3ec6ba621c3edb0b + F[2] = 0x168a13d82bff6bce + F[3] = 0x87663c4bf8c449d2 + F[4] = 0x15f34c83ddc8d830 + F[5] = 0xf9628b49caa2e85 res := make([]fp.Element, count) for i := range count { - vv.SetBytes(pseudoRandomBytes[i*L : (i+1)*L]) - res[i].SetBigInt(vv) + chunk := pseudoRandomBytes[i*L : (i+1)*L] + // Split into two 32-byte chunks, right-align in 48-byte arrays (like kilic) + a0 := make([]byte, 48) + copy(a0[16:], chunk[:32]) // copy to bytes 16-47 (right-aligned) + a1 := make([]byte, 48) + copy(a1[16:], chunk[32:]) // copy to bytes 16-47 (right-aligned) + + var e0, e1 fp.Element + e0.SetBytes(a0) + e1.SetBytes(a1) + + // e1 + e0 * F + var tmp fp.Element + tmp.Mul(&e0, &F) + res[i].Add(&e1, &tmp) } - // release object into pool - pool.BigInt.Put(vv) - return res, nil } @@ -157,29 +156,28 @@ func HashToG1GenericBESwu(msg, dst []byte, hashFunc func() hash.Hash) (bls12381. return bls12381.G1Affine{}, err } - xQ0, yQ0 := kilic.SwuMapG1BE(toKilicElement(&u[0])) - xQ1, yQ1 := kilic.SwuMapG1BE(toKilicElement(&u[1])) + xQ0, yQ0 := SwuMapG1BE(&u[0]) + xQ1, yQ1 := SwuMapG1BE(&u[1]) - _xq0 := toGurvyElement(xQ0) - _yq0 := toGurvyElement(yQ0) - _xq1 := toGurvyElement(xQ1) - _yq1 := toGurvyElement(yQ1) + Q0 := G1Affine{*xQ0, *yQ0} + Q1 := G1Affine{*xQ1, *yQ1} - Q0 := G1Affine{*_xq0, *_yq0} - Q1 := G1Affine{*_xq1, *_yq1} + // Add the two E' points first, then apply isogeny — matches kilic's HashToCurve order. + var _Q0, _Q1 bls12381.G1Jac + _Q0.FromAffine((*bls12381.G1Affine)(&Q0)) + _Q1.FromAffine((*bls12381.G1Affine)(&Q1)).AddAssign(&_Q0) - // TODO (perf): Add in E' first, then apply isogeny - hash_to_curve.G1Isogeny(&Q0.X, &Q0.Y) - hash_to_curve.G1Isogeny(&Q1.X, &Q1.Y) + var sum bls12381.G1Affine + sum.FromJacobian(&_Q1) - var _Q0, _Q1 bls12381.G1Jac - _Q0.FromAffine(toGurvyAffine(&Q0)) - _Q1.FromAffine(toGurvyAffine(&Q1)).AddAssign(&_Q0) + hash_to_curve.G1Isogeny(&sum.X, &sum.Y) - _Q1.ClearCofactor(&_Q1) + var sumJac bls12381.G1Jac + sumJac.FromAffine(&sum) + sumJac.ClearCofactor(&sumJac) - toGurvyAffine(&Q1).FromJacobian(&_Q1) - res := toGurvyAffine(&Q1) + var res bls12381.G1Affine + res.FromJacobian(&sumJac) - return *res, nil + return res, nil } diff --git a/driver/gurvy/gen_vectors_test.go b/driver/gurvy/gen_vectors_test.go new file mode 100644 index 0000000..b7596ff --- /dev/null +++ b/driver/gurvy/gen_vectors_test.go @@ -0,0 +1,94 @@ +// Copyright IBM Corp. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +//go:build ignore + +// gen_vectors_test.go regenerates the known-answer vectors embedded in +// compat_test.go. It is excluded from the normal test build by the +// //go:build ignore constraint above. +// +// To regenerate vectors, temporarily remove (or comment out) the +// //go:build ignore line, then run: +// +// cd driver/gurvy && go test -run TestGenVectors -v +// +// Restore the build constraint afterwards so this file is not compiled +// during normal test runs. +// +// The output is valid Go struct-literal rows that can be pasted directly +// into the vector tables in compat_test.go. + +package gurvy + +import ( + "crypto/sha256" + "fmt" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" +) + +func TestGenVectors(t *testing.T) { + fmt.Println("// ---- Hash vectors -------------------------------------------------------") + fmt.Println("// Paste into hashVectors in compat_test.go") + for i := 0; i < 8; i++ { + msg := []byte(string(rune(i)) + "test_hash_msg") + domain := []byte("test_hash_domain") + gU, err := Hash(msg, domain, 2, sha256.New) + if err != nil { + t.Fatal(err) + } + fmt.Printf("{%q, %q, \"%x\", \"%x\"},\n", + msg, domain, gU[0].Bytes(), gU[1].Bytes()) + } + + fmt.Println() + fmt.Println("// ---- SwuMapG1BE vectors -------------------------------------------------") + fmt.Println("// Paste into swuMapG1BEVectors in compat_test.go") + for i := 0; i < 8; i++ { + msg := []byte(fmt.Sprintf("swu_input_%d", i)) + domain := []byte("swu_test_domain") + gU, err := Hash(msg, domain, 1, sha256.New) + if err != nil { + t.Fatal(err) + } + u := &gU[0] + gX, gY := SwuMapG1BE(u) + fmt.Printf("{\"%x\", \"%x\", \"%x\"},\n", + u.Bytes(), gX.Bytes(), gY.Bytes()) + } + + fmt.Println() + fmt.Println("// ---- IsogenyMapG1 smoke vectors ----------------------------------------") + fmt.Println("// Paste into isogenyMapG1SmokeVectors in compat_test.go") + for i := 0; i < 4; i++ { + msg := []byte(fmt.Sprintf("iso_input_%d", i)) + domain := []byte("iso_test_domain") + gU, err := Hash(msg, domain, 1, sha256.New) + if err != nil { + t.Fatal(err) + } + u := &gU[0] + gX, gY := SwuMapG1BE(u) + isoX := *gX + isoY := *gY + hash_to_curve.G1Isogeny(&isoX, &isoY) + fmt.Printf("{\"%x\", \"%x\", \"%x\"},\n", + u.Bytes(), isoX.Bytes(), isoY.Bytes()) + } + + fmt.Println() + fmt.Println("// ---- HashToG1GenericBESwu vectors ---------------------------------------") + fmt.Println("// Paste into hashToG1Vectors in compat_test.go") + for i := 0; i < 8; i++ { + msg := []byte(string(rune(i)) + "test_message_compat") + domain := []byte("test_domain_compat") + gPoint, err := HashToG1GenericBESwu(msg, domain, sha256.New) + if err != nil { + t.Fatal(err) + } + raw := gPoint.RawBytes() + fmt.Printf("{%q, %q, \"%x\"},\n", msg, domain, raw[:]) + } +} diff --git a/driver/gurvy/gurvy_test.go b/driver/gurvy/gurvy_test.go new file mode 100644 index 0000000..aa7061b --- /dev/null +++ b/driver/gurvy/gurvy_test.go @@ -0,0 +1,547 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package-level tests for the gurvy driver: bn254 and bls12-377 curves. +// These tests exercise the driver types directly (G1, G2, Gt, Zr, Curve) +// without going through the top-level math package. + +package gurvy + +import ( + "crypto/rand" + "crypto/sha256" + "hash" + "io" + "testing" + + "github.com/IBM/mathlib/driver" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +type curveDriver interface { + driver.Curve + GenG1() driver.G1 + GenG2() driver.G2 + GenGt() driver.Gt +} + +// --------------------------------------------------------------------------- +// Shared curve test suite — run once for each driver. +// --------------------------------------------------------------------------- + +func runDriverSuite(t *testing.T, c curveDriver) { + t.Helper() + t.Run("Rand", func(t *testing.T) { + rng, err := c.Rand() + require.NoError(t, err) + assert.NotNil(t, rng) + }) + t.Run("GroupOrder", func(t *testing.T) { + order := c.GroupOrder() + assert.NotNil(t, order) + assert.False(t, order.IsZero()) + }) + t.Run("Zr_ops", func(t *testing.T) { testZrOps(t, c) }) + t.Run("G1_ops", func(t *testing.T) { testG1Ops(t, c) }) + t.Run("G2_ops", func(t *testing.T) { testG2Ops(t, c) }) + t.Run("Gt_ops", func(t *testing.T) { testGtOps(t, c) }) + t.Run("Pairing", func(t *testing.T) { testPairing(t, c) }) + t.Run("HashToG1", func(t *testing.T) { testHashToG1(t, c) }) + t.Run("Serialisation", func(t *testing.T) { testSerialisation(t, c) }) + t.Run("ModArith", func(t *testing.T) { testModArith(t, c) }) + t.Run("MultiScalarMul", func(t *testing.T) { testMultiScalarMul(t, c) }) +} + +func testZrOps(t *testing.T, c curveDriver) { + t.Helper() + rng, err := c.Rand() + require.NoError(t, err) + + // NewZrFromInt64 / NewZrFromUint64 / NewZrFromBytes + a := c.NewZrFromInt64(7) + b := c.NewZrFromUint64(3) + assert.False(t, a.IsZero()) + assert.False(t, b.IsZero()) + assert.True(t, c.NewZrFromInt64(0).IsZero()) + assert.True(t, c.NewZrFromInt64(1).IsOne()) + assert.False(t, c.NewZrFromInt64(2).IsOne()) + + // Plus / Minus / Mul / PowMod + sum := a.Plus(b) + assert.True(t, sum.Equals(c.NewZrFromInt64(10))) + diff := a.Minus(b) + assert.True(t, diff.Equals(c.NewZrFromInt64(4))) + prod := a.Mul(b) + assert.True(t, prod.Equals(c.NewZrFromInt64(21))) + + // Mod + m := c.NewZrFromInt64(5) + am := a.Copy() + am.Mod(m) + assert.True(t, am.Equals(c.NewZrFromInt64(2))) + + // InvModP: 3 * 4 == 1 (mod 11) + three := c.NewZrFromInt64(3) + three.InvModP(c.NewZrFromInt64(11)) + assert.True(t, three.Equals(c.NewZrFromInt64(4))) + + // InvModOrder + r := c.NewRandomZr(rng) + rInv := r.Copy() + rInv.InvModOrder() + assert.True(t, r.Mul(rInv).Equals(c.NewZrFromInt64(1))) + + // PowMod: 2^10 = 1024 + base := c.NewZrFromInt64(2) + pow := base.PowMod(c.NewZrFromInt64(10)) + assert.True(t, pow.Equals(c.NewZrFromInt64(1024))) + + // Bytes round-trip + rr := c.NewRandomZr(rng) + back := c.NewZrFromBytes(rr.Bytes()) + assert.True(t, rr.Equals(back)) + + // BigInt + bi := c.NewZrFromInt64(999).BigInt() + assert.NotNil(t, bi) + + // Clone / Copy + x := c.NewZrFromInt64(55) + y := x.Copy() + assert.True(t, x.Equals(y)) + z := c.NewZrFromInt64(0) + z.Clone(x) + assert.True(t, x.Equals(z)) + + // String + s := c.NewZrFromInt64(255).String() + assert.Equal(t, "ff", s) + + // Neg: x + (-x) mod p == 0 + n := c.NewRandomZr(rng) + neg := n.Copy() + neg.Neg() + sum2 := n.Plus(neg) + sum2.Mod(c.GroupOrder()) + assert.True(t, sum2.IsZero()) +} + +func testG1Ops(t *testing.T, c curveDriver) { + t.Helper() + rng, err := c.Rand() + require.NoError(t, err) + r := c.NewRandomZr(rng) + + g := c.GenG1() + assert.False(t, g.IsInfinity()) + + // Mul: [35]g + g35 := g.Mul(c.NewZrFromInt64(35)) + g23 := g.Mul(c.NewZrFromInt64(23)) + g58 := g.Mul(c.NewZrFromInt64(58)) + + sum := g35.Copy() + sum.Add(g23) + assert.True(t, sum.Equals(g58)) + + // Sub + sub := g58.Copy() + sub.Sub(g23) + assert.True(t, sub.Equals(g35)) + + // IsInfinity after self-subtraction + self := g35.Copy() + self.Sub(g35.Copy()) + assert.True(t, self.IsInfinity()) + + // Neg + neg := g35.Copy() + neg.Neg() + neg.Add(g35.Copy()) + assert.True(t, neg.IsInfinity()) + + // Mul2: [35]g + [23]g == [58]g + m2 := g.Mul2(c.NewZrFromInt64(35), g, c.NewZrFromInt64(23)) + assert.True(t, m2.Equals(g58)) + + // Mul2InPlace + m3 := g.Copy() + m3.Mul2InPlace(c.NewZrFromInt64(35), g, c.NewZrFromInt64(23)) + assert.True(t, m3.Equals(g58)) + + // Clone + clone := c.NewG1() + clone.Clone(g35) + assert.True(t, clone.Equals(g35)) + + // Bytes round-trip + raw := g.Mul(r).Bytes() + back := c.NewG1FromBytes(raw) + assert.Equal(t, raw, back.Bytes()) + + // Compressed round-trip + comp := g.Mul(r).Compressed() + backComp := c.NewG1FromCompressed(comp) + assert.Equal(t, comp, backComp.Compressed()) + + // String (just must not panic) + _ = g.String() +} + +func testG2Ops(t *testing.T, c curveDriver) { + t.Helper() + rng, err := c.Rand() + require.NoError(t, err) + r := c.NewRandomZr(rng) + + g := c.GenG2() + + g35 := g.Mul(c.NewZrFromInt64(35)) + g23 := g.Mul(c.NewZrFromInt64(23)) + g58 := g.Mul(c.NewZrFromInt64(58)) + + sum := g35.Copy() + sum.Add(g23) + assert.True(t, sum.Equals(g58)) + + sub := g58.Copy() + sub.Sub(g23) + assert.True(t, sub.Equals(g35)) + + // Affine (no-op for gnark-crypto, just must not panic) + g35.Affine() + + // Clone + clone := c.NewG2() + clone.Clone(g35) + assert.True(t, clone.Equals(g35)) + + // Bytes round-trip + raw := g.Mul(r).Bytes() + back := c.NewG2FromBytes(raw) + assert.Equal(t, raw, back.Bytes()) + + // Compressed round-trip + comp := g.Mul(r).Compressed() + backComp := c.NewG2FromCompressed(comp) + assert.Equal(t, comp, backComp.Compressed()) + + // String + _ = g.String() +} + +func testGtOps(t *testing.T, c curveDriver) { + t.Helper() + rng, err := c.Rand() + require.NoError(t, err) + + g1 := c.GenG1() + g2 := c.GenG2() + + gt := c.Pairing(g2, g1) + gt = c.FExp(gt) + + // Exp(1) == gt + gt1 := gt.Exp(c.NewZrFromInt64(1)) + assert.True(t, gt.Equals(gt1)) + + // Gt * Gt^-1 == unity + // Clone via NewGtFromBytes since driver.Gt has no Copy() + inv := c.NewGtFromBytes(gt.Bytes()) + inv.Inverse() + gt.Mul(inv) + assert.True(t, gt.IsUnity()) + + // Bytes round-trip + r := c.NewRandomZr(rng) + gtr := c.Pairing(g2.Mul(r), g1) + b := gtr.Bytes() + back := c.NewGtFromBytes(b) + assert.True(t, gtr.Equals(back)) + + // ToString (driver.Gt has ToString, not String) + s := gtr.ToString() + assert.NotEmpty(t, s) +} + +func testPairing(t *testing.T, c curveDriver) { + t.Helper() + rng, err := c.Rand() + require.NoError(t, err) + r := c.NewRandomZr(rng) + + g1 := c.GenG1() + g2 := c.GenG2() + + // e([r]g2, g1) == e(g2, [r]g1) + a := c.FExp(c.Pairing(g2.Mul(r), g1)) + b := c.FExp(c.Pairing(g2, g1.Mul(r))) + assert.True(t, a.Equals(b)) + + // Pairing2: Pairing2(p2a, p2b G2, p1a, p1b G1) + r1 := c.NewRandomZr(rng) + r2 := c.NewRandomZr(rng) + r3 := c.NewRandomZr(rng) + r4 := c.NewRandomZr(rng) + + p := g2.Mul(r1) + q := g1.Mul(r2) + rg2 := g2.Mul(r3) + s := g1.Mul(r4) + + tt1 := c.FExp(c.Pairing2(p, rg2, q, s)) + tt2 := c.FExp(c.Pairing(g2.Mul(r1).Mul(r2), g1)) + tt3 := c.FExp(c.Pairing(g2, g1.Mul(r3).Mul(r4))) + tt2.Mul(tt3) + assert.True(t, tt1.Equals(tt2)) +} + +func testHashToG1(t *testing.T, c curveDriver) { + t.Helper() + msg := []byte("hash to g1 test") + domain := []byte("domain") + + h1 := c.HashToG1(msg) + assert.False(t, h1.IsInfinity()) + + h2 := c.HashToG1WithDomain(msg, domain) + assert.False(t, h2.IsInfinity()) + + // deterministic + h1b := c.HashToG1(msg) + assert.True(t, h1.Equals(h1b)) +} + +func testSerialisation(t *testing.T, c curveDriver) { + t.Helper() + rng, err := c.Rand() + require.NoError(t, err) + r := c.NewRandomZr(rng) + + // G1 panic recovery — invalid bytes must panic (driver contract). + assert.Panics(t, func() { c.NewG1FromBytes(nil) }) + assert.Panics(t, func() { c.NewG2FromBytes(nil) }) + assert.Panics(t, func() { c.NewG1FromCompressed(nil) }) + assert.Panics(t, func() { c.NewG2FromCompressed(nil) }) + assert.Panics(t, func() { c.NewGtFromBytes(nil) }) + + // Valid round trips + g1p := c.GenG1().Mul(r) + g2p := c.GenG2().Mul(r) + + g1b := g1p.Bytes() + assert.True(t, g1p.Equals(c.NewG1FromBytes(g1b))) + g1c := g1p.Compressed() + assert.True(t, g1p.Equals(c.NewG1FromCompressed(g1c))) + + g2b := g2p.Bytes() + assert.True(t, g2p.Equals(c.NewG2FromBytes(g2b))) + g2c := g2p.Compressed() + assert.True(t, g2p.Equals(c.NewG2FromCompressed(g2c))) +} + +func testModArith(t *testing.T, c curveDriver) { + t.Helper() + m := c.GroupOrder() + + a := c.NewZrFromInt64(8) + b := c.NewZrFromInt64(3) + + // ModAdd + sum := c.ModAdd(a, b, m) + assert.True(t, sum.Equals(c.NewZrFromInt64(11))) + + // ModSub + diff := c.ModSub(a, b, m) + assert.True(t, diff.Equals(c.NewZrFromInt64(5))) + + // ModMul + prod := c.ModMul(a, b, m) + assert.True(t, prod.Equals(c.NewZrFromInt64(24))) + + // ModNeg + neg := c.ModNeg(b, m) + sum2 := c.ModAdd(b, neg, m) + assert.True(t, sum2.IsZero()) + + // ModAddMul2 + r := c.ModAddMul2(a, b, b, a, m) + // (8*3) + (3*8) = 48 + assert.True(t, r.Equals(c.NewZrFromInt64(48))) + + // ModAddMul3 + c1 := c.NewZrFromInt64(2) + r3 := c.ModAddMul3(a, b, b, a, c1, c1, m) + // (8*3) + (3*8) + (2*2) = 52 + assert.True(t, r3.Equals(c.NewZrFromInt64(52))) + + // ModMulInPlace + res := c.NewZrFromInt64(0) + c.ModMulInPlace(res, a, b, m) + assert.True(t, res.Equals(c.NewZrFromInt64(24))) + + // ModAddMul2InPlace + res2 := c.NewZrFromInt64(0) + c.ModAddMul2InPlace(res2, a, b, b, a, m) + assert.True(t, res2.Equals(c.NewZrFromInt64(48))) + + // ModAddMul3InPlace + res3 := c.NewZrFromInt64(0) + c.ModAddMul3InPlace(res3, a, b, b, a, c1, c1, m) + assert.True(t, res3.Equals(c.NewZrFromInt64(52))) +} + +func testMultiScalarMul(t *testing.T, c curveDriver) { + t.Helper() + rng, err := c.Rand() + require.NoError(t, err) + + n := 5 + pts := make([]driver.G1, n) + scalars := make([]driver.Zr, n) + for i := range n { + scalars[i] = c.NewRandomZr(rng) + pts[i] = c.GenG1().Mul(c.NewRandomZr(rng)) + } + + // Naïve sum + naive := c.NewG1() + for i := range n { + naive.Add(pts[i].Mul(scalars[i])) + } + + // MSM + msm := c.MultiScalarMul(pts, scalars) + assert.True(t, naive.Equals(msm)) +} + +// newG1DriverHelper is used by testSerialisation for HashToG1WithDomain. +// Some drivers (bn254 via driver interface) may not expose HashToG2WithDomain. +// We test what we can through the common interface. + +// --------------------------------------------------------------------------- +// BN254 +// --------------------------------------------------------------------------- + +type bn254Driver struct { + *Bn254 +} + +func (d *bn254Driver) GenG1() driver.G1 { return d.Bn254.GenG1() } +func (d *bn254Driver) GenG2() driver.G2 { return d.Bn254.GenG2() } +func (d *bn254Driver) GenGt() driver.Gt { return d.Bn254.GenGt() } + +// NewG1 / NewG2 disambiguate between driver.Curve method and Bn254 method. +func (d *bn254Driver) NewG1() driver.G1 { return d.Bn254.NewG1() } +func (d *bn254Driver) NewG2() driver.G2 { return d.Bn254.NewG2() } + +// Implement the full driver.Curve interface by delegating to Bn254. +var _ curveDriver = (*bn254Driver)(nil) + +// Wrappers to satisfy the curveDriver interface's embedded methods that are +// not promoted automatically because of name clashes. + +func (d *bn254Driver) Rand() (io.Reader, error) { return rand.Reader, nil } + +func TestBn254Suite(t *testing.T) { + runDriverSuite(t, &bn254Driver{NewBn254()}) +} + +// --------------------------------------------------------------------------- +// BLS12-377 +// --------------------------------------------------------------------------- + +type bls377Driver struct { + *Bls12_377 +} + +func (d *bls377Driver) GenG1() driver.G1 { return d.Bls12_377.GenG1() } +func (d *bls377Driver) GenG2() driver.G2 { return d.Bls12_377.GenG2() } +func (d *bls377Driver) GenGt() driver.Gt { return d.Bls12_377.GenGt() } +func (d *bls377Driver) NewG1() driver.G1 { return d.Bls12_377.NewG1() } +func (d *bls377Driver) NewG2() driver.G2 { return d.Bls12_377.NewG2() } +func (d *bls377Driver) Rand() (io.Reader, error) { return rand.Reader, nil } + +var _ curveDriver = (*bls377Driver)(nil) + +func TestBls12377Suite(t *testing.T) { + runDriverSuite(t, &bls377Driver{NewBls12_377()}) +} + +// --------------------------------------------------------------------------- +// BLS12-377 HashToG2 +// --------------------------------------------------------------------------- + +func TestBls12377HashToG2(t *testing.T) { + c := NewBls12_377() + msg := []byte("g2 hash") + domain := []byte("dom") + + h := c.HashToG2(msg) + // G2 has no IsInfinity; verify non-zero bytes as proxy. + assert.NotEmpty(t, h.Bytes()) + + h2 := c.HashToG2WithDomain(msg, domain) + assert.NotEmpty(t, h2.Bytes()) + + // The two hashes should differ (different domains would be tested elsewhere; + // here just confirm the function runs without panic). + assert.False(t, h.Equals(h2)) +} + +// --------------------------------------------------------------------------- +// Size accessors — smoke test for both drivers. +// --------------------------------------------------------------------------- + +func TestBn254Sizes(t *testing.T) { + c := NewBn254() + assert.Positive(t, c.CoordinateByteSize()) + assert.Positive(t, c.G1ByteSize()) + assert.Positive(t, c.CompressedG1ByteSize()) + assert.Positive(t, c.G2ByteSize()) + assert.Positive(t, c.CompressedG2ByteSize()) + assert.Positive(t, c.ScalarByteSize()) +} + +func TestBls12377Sizes(t *testing.T) { + c := NewBls12_377() + assert.Positive(t, c.CoordinateByteSize()) + assert.Positive(t, c.G1ByteSize()) + assert.Positive(t, c.CompressedG1ByteSize()) + assert.Positive(t, c.G2ByteSize()) + assert.Positive(t, c.CompressedG2ByteSize()) + assert.Positive(t, c.ScalarByteSize()) +} + +// --------------------------------------------------------------------------- +// ExpandMsgXmd edge-case: ell > 255 must error. +// --------------------------------------------------------------------------- + +func TestExpandMsgXmdEllTooBig(t *testing.T) { + msg := []byte("test") + dst := []byte("dst") + // sha256 produces 32 bytes per block; 255*32 = 8160 bytes max. + // Requesting 8161 bytes triggers ell > 255. + _, err := ExpandMsgXmd(msg, dst, 256*32+1, newSHA256) + assert.Error(t, err) +} + +func TestExpandMsgXmdDstTooLong(t *testing.T) { + msg := []byte("test") + dst := make([]byte, 256) + _, err := ExpandMsgXmd(msg, dst, 32, newSHA256) + assert.Error(t, err) +} + +// newSHA256 helper (used in ExpandMsgXmd tests). +func newSHA256() hash.Hash { + return sha256.New() +} diff --git a/driver/gurvy/swu.go b/driver/gurvy/swu.go new file mode 100644 index 0000000..4d2b6b4 --- /dev/null +++ b/driver/gurvy/swu.go @@ -0,0 +1,196 @@ +/* +Copyright IBM Corp. All Rights Reserved. +Copyright 2020 ConsenSys Software Inc. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package gurvy + +import ( + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" +) + +// SWU parameters for G1 (from kilic, in Montgomery form) +// These match the BLS12-381 curve parameters for Simplified SWU map +var ( + // Curve coefficients in Montgomery form + swuA = fp.Element{ + 0x2f65aa0e9af5aa51, + 0x86464c2d1e8416c3, + 0xb85ce591b7bd31e2, + 0x27e11c91b5f24e7c, + 0x28376eda6bfc1835, + 0x155455c3e5071d85, + } + swuB = fp.Element{ + 0xfb996971fe22a1e0, + 0x9aa93eb35b742d6f, + 0x8c476013de99c5c4, + 0x873e27c3a221e571, + 0xca72b5e45a52d888, + 0x06824061418a386b, + } + + // SWU Z parameter and its inverse + swuZ = fp.Element{ + 0x886c00000023ffdc, + 0x0f70008d3090001d, + 0x77672417ed5828c3, + 0x9dac23e943dc1740, + 0x50553f1b9c131521, + 0x078c712fbe0ab6e8, + } + swuZInv = fp.Element{ + 0x0e8a2e8ba2e83e10, + 0x5b28ba2ca4d745d1, + 0x678cd5473847377a, + 0x4c506dd8a8076116, + 0x9bcb227d79284139, + 0x0e8d3154b0ba099a, + } + + // -b/a in Montgomery form + swuMinusBOverA = fp.Element{ + 0x052583c93555a7fe, + 0x3b40d72430f93c82, + 0x1b75faa0105ec983, + 0x2527e7dc63851767, + 0x99fffd1f34fc181d, + 0x097cab54770ca0d3, + } +) + +// Sqrt computes the square root of a field element using Tonelli-Shanks +// Since q ≡ 3 (mod 4), sqrt(x) = x^((q+1)/4) +func Sqrt(z *fp.Element, x *fp.Element) { + z.Sqrt(x) +} + +// IsQuadraticResidue checks if x is a quadratic residue using Euler's criterion +// Returns 1 if quadratic residue, 0 otherwise +func IsQuadraticResidue(x *fp.Element) uint64 { + if x.IsZero() { + return 1 + } + var tmp fp.Element + tmp.Sqrt(x) + if tmp.IsZero() { + return 0 + } + + return 1 +} + +// Sgn0 returns the sign of a field element. +// Matches kilic's big-endian sign (returns 1 if x <= (p-1)/2, 0 otherwise) +func Sgn0(x *fp.Element) uint64 { + bits := x.Bits() + var pMinus1Over2 = [6]uint64{ + 0xdcff7fffffffd555, + 0xf55ffff58a9ffff, + 0xb39869507b587b12, + 0xb23ba5c279c2895f, + 0x258dd3db21a5d66b, + 0xd0088f51cbff34d, + } + for i := 5; i >= 0; i-- { + if bits[i] < pMinus1Over2[i] { + return 1 + } else if bits[i] > pMinus1Over2[i] { + return 0 + } + } + + return 1 +} + +// signLE returns the parity sign of a field element. +// Matches kilic's sign() function: returns true if the least-significant bit +// of the canonical (non-Montgomery) representation is 0. +func signLE(x *fp.Element) bool { + bits := x.Bits() + + return bits[0]&1 == 0 +} + +// SwuMapG1BE implements the Simplified SWU map for G1. +// The name "BE" refers to kilic's naming; the sign normalisation uses the +// same parity (LE) convention as kilic's swuMapG1 (sign() = r[0]&1==0). +// Returns x, y coordinates of the mapped point on the isogenous curve E'. +func SwuMapG1BE(u *fp.Element) (*fp.Element, *fp.Element) { + var tv0, tv1, x1, x2, gx1, gx2, x, y2, y fp.Element + + // tv0 = u^2 + tv0.Square(u) + + // tv0 = tv0 * z + tv0.Mul(&tv0, &swuZ) + + // tv1 = tv0^2 + tv1.Square(&tv0) + + // x1 = tv0 + tv1 + x1.Add(&tv0, &tv1) + + // x1 = x1^(-1) + x1.Inverse(&x1) + + // e1 = x1 == 0 + e1 := x1.IsZero() + + // x1 = x1 + 1 + var one fp.Element + one.SetOne() + x1.Add(&x1, &one) + + // if e1: x1 = zInv + if e1 { + x1.Set(&swuZInv) + } + + // x1 = x1 * (-b/a) + x1.Mul(&x1, &swuMinusBOverA) + + // gx1 = x1^3 + a*x1 + b + // gx1 = x1^2 + gx1.Square(&x1) + // gx1 = gx1 + a + gx1.Add(&gx1, &swuA) + // gx1 = gx1 * x1 + gx1.Mul(&gx1, &x1) + // gx1 = gx1 + b + gx1.Add(&gx1, &swuB) + + // x2 = tv0 * x1 + x2.Mul(&tv0, &x1) + + // tv1 = tv0 * tv1 + tv1.Mul(&tv0, &tv1) + + // gx2 = gx1 * tv1 + gx2.Mul(&gx1, &tv1) + + // e2 = gx1 is quadratic residue + e2 := IsQuadraticResidue(&gx1) + + // if e2: x = x1, y2 = gx1 else: x = x2, y2 = gx2 + if e2 == 1 { + x.Set(&x1) + y2.Set(&gx1) + } else { + x.Set(&x2) + y2.Set(&gx2) + } + + // y = sqrt(y2) + Sqrt(&y, &y2) + + // if sign(y) != sign(u): y = -y + // Uses parity (LE) sign to match kilic's swuMapG1 + if signLE(&y) != signLE(u) { + y.Neg(&y) + } + + return &x, &y +} diff --git a/driver/kilic/bls12-381.go b/driver/kilic/bls12-381.go deleted file mode 100644 index a69c14e..0000000 --- a/driver/kilic/bls12-381.go +++ /dev/null @@ -1,510 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package kilic - -import ( - "fmt" - "math/big" - - "github.com/IBM/mathlib/driver" - "github.com/IBM/mathlib/driver/common" - bls12381 "github.com/kilic/bls12-381" -) - -/*********************************************************************/ - -type bls12_381G1 struct { - bls12381.PointG1 - bls12381.G1 -} - -func (g *bls12_381G1) Clone(a driver.G1) { - g.Set(&a.(*bls12_381G1).PointG1) -} - -func (e *bls12_381G1) Copy() driver.G1 { - c := &bls12_381G1{G1: *bls12381.NewG1()} - c.Set(&e.PointG1) - - return c -} - -func (g *bls12_381G1) Add(a driver.G1) { - g.G1.Add(&g.PointG1, &g.PointG1, &a.(*bls12_381G1).PointG1) -} - -func (g *bls12_381G1) Mul(a driver.Zr) driver.G1 { - g1 := bls12381.NewG1() - res := g1.New() - - g1.MulScalarBig(res, &g.PointG1, &a.(*common.BaseZr).Int) - - return &bls12_381G1{ - G1: *g1, - PointG1: *res, - } -} - -func (g *bls12_381G1) Mul2(e driver.Zr, Q driver.G1, f driver.Zr) driver.G1 { - a := g.Mul(e) - b := Q.Mul(f) - a.Add(b) - - return a -} - -func (g *bls12_381G1) Mul2InPlace(e driver.Zr, Q driver.G1, f driver.Zr) { - a := g.Mul(e) - b := Q.Mul(f) - a.Add(b) - - g.Set(&a.(*bls12_381G1).PointG1) -} - -func (g *bls12_381G1) Equals(a driver.G1) bool { - g1 := bls12381.NewG1() - - return g1.Equal(&a.(*bls12_381G1).PointG1, &g.PointG1) -} - -func (g *bls12_381G1) Bytes() []byte { - g1 := bls12381.NewG1() - raw := g1.ToUncompressed(&g.PointG1) - - return raw[:] -} - -func (g *bls12_381G1) Compressed() []byte { - raw := g.ToCompressed(&g.PointG1) - - return raw[:] -} - -func (g *bls12_381G1) Sub(a driver.G1) { - g.G1.Sub(&g.PointG1, &g.PointG1, &a.(*bls12_381G1).PointG1) -} - -func (g *bls12_381G1) IsInfinity() bool { - return g.IsZero(&g.PointG1) -} - -func (g *bls12_381G1) String() string { - gb := g.Bytes() - x := new(big.Int).SetBytes(gb[:len(gb)/2]) - y := new(big.Int).SetBytes(gb[len(gb)/2:]) - - return "(" + x.String() + "," + y.String() + ")" -} - -func (g *bls12_381G1) Neg() { - g.G1.Neg(&g.PointG1, &g.PointG1) -} - -/*********************************************************************/ - -type bls12_381G2 struct { - bls12381.PointG2 - bls12381.G2 -} - -func (g *bls12_381G2) Clone(a driver.G2) { - g.Set(&a.(*bls12_381G2).PointG2) -} - -func (e *bls12_381G2) Copy() driver.G2 { - c := &bls12_381G2{ - G2: *bls12381.NewG2(), - } - c.Set(&e.PointG2) - - return c -} - -func (g *bls12_381G2) Mul(a driver.Zr) driver.G2 { - g2 := bls12381.NewG2() - res := g2.New() - - g2.MulScalarBig(res, &g.PointG2, &a.(*common.BaseZr).Int) - - return &bls12_381G2{ - G2: *g2, - PointG2: *res, - } -} - -func (g *bls12_381G2) Add(a driver.G2) { - g.G2.Add(&g.PointG2, &g.PointG2, &a.(*bls12_381G2).PointG2) -} - -func (g *bls12_381G2) Sub(a driver.G2) { - g.G2.Sub(&g.PointG2, &g.PointG2, &a.(*bls12_381G2).PointG2) -} - -func (g *bls12_381G2) Affine() { - g2 := bls12381.NewG2() - g.PointG2 = *g2.Affine(&g.PointG2) -} - -func (g *bls12_381G2) Bytes() []byte { - g2 := bls12381.NewG2() - raw := g2.ToUncompressed(&g.PointG2) - - return raw[:] -} - -func (g *bls12_381G2) Compressed() []byte { - g2 := bls12381.NewG2() - raw := g2.ToCompressed(&g.PointG2) - - return raw[:] -} - -func (g *bls12_381G2) String() string { - // FIXME - return "" -} - -func (g *bls12_381G2) Equals(a driver.G2) bool { - g2 := bls12381.NewG2() - - return g2.Equal(&a.(*bls12_381G2).PointG2, &g.PointG2) -} - -/*********************************************************************/ - -type bls12_381Gt struct { - bls12381.E - bls12381.GT - GTInitialised bool -} - -func (g *bls12_381Gt) Exp(x driver.Zr) driver.Gt { - gt := bls12381.NewGT() - res := gt.New() - gt.Exp(res, &g.E, &x.(*common.BaseZr).Int) - - return &bls12_381Gt{ - E: *res, - GT: *gt, - GTInitialised: true, - } -} - -func (g *bls12_381Gt) Equals(a driver.Gt) bool { - return g.Equal(&a.(*bls12_381Gt).E) -} - -func (g *bls12_381Gt) Inverse() { - if !g.GTInitialised { - g.GT = *bls12381.NewGT() - } - g.GT.Inverse(&g.E, &g.E) -} - -func (g *bls12_381Gt) Mul(a driver.Gt) { - if !g.GTInitialised { - g.GT = *bls12381.NewGT() - } - g.GT.Mul(&g.E, &g.E, &a.(*bls12_381Gt).E) -} - -func (g *bls12_381Gt) IsUnity() bool { - return g.IsOne() -} - -func (g *bls12_381Gt) ToString() string { - // FIXME - return "" -} - -func (g *bls12_381Gt) Bytes() []byte { - if !g.GTInitialised { - g.GT = *bls12381.NewGT() - } - raw := g.ToBytes(&g.E) - - return raw[:] -} - -/*********************************************************************/ - -func NewBls12_381() *Bls12_381 { - return &Bls12_381{common.CurveBase{Modulus: *bls12381.NewG1().Q()}} -} - -func NewBls12_381BBS() *Bls12_381BBS { - return &Bls12_381BBS{*NewBls12_381()} -} - -type Bls12_381 struct { - common.CurveBase -} - -func (c *Bls12_381) MultiScalarMul(a []driver.G1, b []driver.Zr) driver.G1 { - g1 := c.NewG1() - for i := range a { - g1.Add(a[i].Mul(b[i])) - } - - return g1 -} - -type Bls12_381BBS struct { - Bls12_381 -} - -func (c *Bls12_381) Pairing(p2 driver.G2, p1 driver.G1) driver.Gt { - bls := bls12381.NewEngine() - bls.AddPair(&p1.(*bls12_381G1).PointG1, &p2.(*bls12_381G2).PointG2) - - return &bls12_381Gt{ - E: *bls.Result(), - } -} - -func (c *Bls12_381) Pairing2(p2a, p2b driver.G2, p1a, p1b driver.G1) driver.Gt { - bls := bls12381.NewEngine() - bls.AddPair(&p1a.(*bls12_381G1).PointG1, &p2a.(*bls12_381G2).PointG2) - bls.AddPair(&p1b.(*bls12_381G1).PointG1, &p2b.(*bls12_381G2).PointG2) - - return &bls12_381Gt{ - E: *bls.Result(), - } -} - -func (c *Bls12_381) FExp(a driver.Gt) driver.Gt { - return a -} - -func (c *Bls12_381) GenG1() driver.G1 { - g := bls12381.NewG1() - g1 := g.One() - - return &bls12_381G1{ - G1: *g, - PointG1: *g1, - } -} - -func (c *Bls12_381) GenG2() driver.G2 { - g := bls12381.NewG2() - g2 := g.One() - - return &bls12_381G2{ - G2: *g, - PointG2: *g2, - } -} - -func (c *Bls12_381) GenGt() driver.Gt { - g1 := c.GenG1() - g2 := c.GenG2() - gengt := c.Pairing(g2, g1) - gengt = c.FExp(gengt) - - return gengt -} - -func (c *Bls12_381) CoordinateByteSize() int { - return fpByteSize -} - -func (c *Bls12_381) G1ByteSize() int { - return 2 * fpByteSize -} - -func (c *Bls12_381) CompressedG1ByteSize() int { - return fpByteSize -} - -func (c *Bls12_381) G2ByteSize() int { - return 4 * fpByteSize -} - -func (c *Bls12_381) CompressedG2ByteSize() int { - return 2 * fpByteSize -} - -func (c *Bls12_381) ScalarByteSize() int { - return common.ScalarByteSize -} - -func (c *Bls12_381) NewG1() driver.G1 { - return &bls12_381G1{G1: *bls12381.NewG1()} -} - -func (c *Bls12_381) NewG2() driver.G2 { - return &bls12_381G2{G2: *bls12381.NewG2()} -} - -func (c *Bls12_381) NewG1FromBytes(b []byte) driver.G1 { - g1 := bls12381.NewG1() - p, err := g1.FromUncompressed(b) - if err != nil { - panic(fmt.Sprintf("set bytes failed [%s]", err.Error())) - } - - return &bls12_381G1{ - PointG1: *p, - G1: *g1, - } -} - -func (c *Bls12_381) NewG2FromBytes(b []byte) driver.G2 { - g2 := bls12381.NewG2() - p, err := g2.FromUncompressed(b) - if err != nil { - panic(fmt.Sprintf("set bytes failed [%s]", err.Error())) - } - - return &bls12_381G2{ - G2: *g2, - PointG2: *p, - } -} - -func (c *Bls12_381) NewG1FromCompressed(b []byte) driver.G1 { - g1 := bls12381.NewG1() - p, err := g1.FromCompressed(b) - if err != nil { - panic(fmt.Sprintf("set bytes failed [%s]", err.Error())) - } - - return &bls12_381G1{ - PointG1: *p, - G1: *g1, - } -} - -func (c *Bls12_381) NewG2FromCompressed(b []byte) driver.G2 { - g2 := bls12381.NewG2() - p, err := g2.FromCompressed(b) - if err != nil { - panic(fmt.Sprintf("set bytes failed [%s]", err.Error())) - } - - return &bls12_381G2{ - G2: *g2, - PointG2: *p, - } -} - -func (c *Bls12_381) NewGtFromBytes(b []byte) driver.Gt { - gt := bls12381.NewGT() - p, err := gt.FromBytes(b) - if err != nil { - panic(fmt.Sprintf("set bytes failed [%s]", err.Error())) - } - - return &bls12_381Gt{ - E: *p, - GT: *gt, - GTInitialised: true, - } -} - -func (c *Bls12_381) HashToG1(data []byte) driver.G1 { - g1 := bls12381.NewG1() - p, err := g1.HashToCurve(data, []byte{}) - if err != nil { - panic(fmt.Sprintf("HashToCurve failed [%s]", err.Error())) - } - - return &bls12_381G1{ - PointG1: *p, - G1: *g1, - } -} - -func (c *Bls12_381) HashToG2(data []byte) driver.G2 { - g2 := bls12381.NewG2() - p, err := g2.HashToCurve(data, []byte{}) - if err != nil { - panic(fmt.Sprintf("HashToCurve failed [%s]", err.Error())) - } - - return &bls12_381G2{ - PointG2: *p, - G2: *g2, - } -} - -func (c *Bls12_381) HashToG1WithDomain(data, domain []byte) driver.G1 { - g1 := bls12381.NewG1() - p, err := g1.HashToCurve(data, domain) - if err != nil { - panic(fmt.Sprintf("HashToCurve failed [%s]", err.Error())) - } - - return &bls12_381G1{ - PointG1: *p, - G1: *g1, - } -} - -func (c *Bls12_381) HashToG2WithDomain(data, domain []byte) driver.G2 { - g2 := bls12381.NewG2() - p, err := g2.HashToCurve(data, domain) - if err != nil { - panic(fmt.Sprintf("HashToCurve failed [%s]", err.Error())) - } - - return &bls12_381G2{ - PointG2: *p, - G2: *g2, - } -} - -func (c *Bls12_381BBS) HashToG1(data []byte) driver.G1 { - p, err := HashToG1GenericBESwu(data, []byte{}) - if err != nil { - panic(fmt.Sprintf("HashToCurve failed [%s]", err.Error())) - } - - return &bls12_381G1{ - PointG1: *p, - G1: *bls12381.NewG1(), - } -} - -func (c *Bls12_381BBS) HashToG2(data []byte) driver.G2 { - g2 := bls12381.NewG2() - p, err := g2.HashToCurve(data, []byte{}) - if err != nil { - panic(fmt.Sprintf("HashToCurve failed [%s]", err.Error())) - } - - return &bls12_381G2{ - PointG2: *p, - G2: *g2, - } -} - -func (c *Bls12_381BBS) HashToG1WithDomain(data, domain []byte) driver.G1 { - p, err := HashToG1GenericBESwu(data, domain) - if err != nil { - panic(fmt.Sprintf("HashToCurve failed [%s]", err.Error())) - } - - return &bls12_381G1{ - PointG1: *p, - G1: *bls12381.NewG1(), - } -} - -func (c *Bls12_381BBS) HashToG2WithDomain(data, domain []byte) driver.G2 { - g2 := bls12381.NewG2() - p, err := g2.HashToCurve(data, domain) - if err != nil { - panic(fmt.Sprintf("HashToCurve failed [%s]", err.Error())) - } - - return &bls12_381G2{ - PointG2: *p, - G2: *g2, - } -} diff --git a/driver/kilic/custom.go b/driver/kilic/custom.go deleted file mode 100644 index de86a90..0000000 --- a/driver/kilic/custom.go +++ /dev/null @@ -1,353 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package kilic - -import ( - "errors" - "hash" - "unsafe" - _ "unsafe" - - bls12381 "github.com/kilic/bls12-381" - "golang.org/x/crypto/blake2b" -) - -const fpByteSize = 48 - -const fpNumberOfLimbs = 6 - -type Fe [fpNumberOfLimbs]uint64 - -var modulus = Fe{0xb9feffffffffaaab, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a} - -// r1 = r mod p -var r1 = &Fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493} - -var swuParamsForG1 = struct { - z *Fe - zInv *Fe - a *Fe - b *Fe - minusBOverA *Fe -}{ - a: &Fe{0x2f65aa0e9af5aa51, 0x86464c2d1e8416c3, 0xb85ce591b7bd31e2, 0x27e11c91b5f24e7c, 0x28376eda6bfc1835, 0x155455c3e5071d85}, - b: &Fe{0xfb996971fe22a1e0, 0x9aa93eb35b742d6f, 0x8c476013de99c5c4, 0x873e27c3a221e571, 0xca72b5e45a52d888, 0x06824061418a386b}, - z: &Fe{0x886c00000023ffdc, 0x0f70008d3090001d, 0x77672417ed5828c3, 0x9dac23e943dc1740, 0x50553f1b9c131521, 0x078c712fbe0ab6e8}, - zInv: &Fe{0x0e8a2e8ba2e83e10, 0x5b28ba2ca4d745d1, 0x678cd5473847377a, 0x4c506dd8a8076116, 0x9bcb227d79284139, 0x0e8d3154b0ba099a}, - minusBOverA: &Fe{0x052583c93555a7fe, 0x3b40d72430f93c82, 0x1b75faa0105ec983, 0x2527e7dc63851767, 0x99fffd1f34fc181d, 0x097cab54770ca0d3}, -} - -func (fe *Fe) setBytes(in []byte) *Fe { - l := min(len(in), fpByteSize) - padded := make([]byte, fpByteSize) - copy(padded[fpByteSize-l:], in[:]) - var a int - for i := range fpNumberOfLimbs { - a = fpByteSize - i*8 - fe[i] = uint64(padded[a-1]) | uint64(padded[a-2])<<8 | - uint64(padded[a-3])<<16 | uint64(padded[a-4])<<24 | - uint64(padded[a-5])<<32 | uint64(padded[a-6])<<40 | - uint64(padded[a-7])<<48 | uint64(padded[a-8])<<56 - } - - return fe -} - -func (fe *Fe) isValid() bool { - return fe.cmp(&modulus) == -1 -} - -func (fe *Fe) cmp(fe2 *Fe) int { - for i := fpNumberOfLimbs - 1; i >= 0; i-- { - if fe[i] > fe2[i] { - return 1 - } else if fe[i] < fe2[i] { - return -1 - } - } - - return 0 -} - -func (fe *Fe) isZero() bool { - return (fe[5] | fe[4] | fe[3] | fe[2] | fe[1] | fe[0]) == 0 -} - -func (fe *Fe) one() *Fe { - return fe.set(r1) -} - -func (fe *Fe) set(fe2 *Fe) *Fe { - fe[0] = fe2[0] - fe[1] = fe2[1] - fe[2] = fe2[2] - fe[3] = fe2[3] - fe[4] = fe2[4] - fe[5] = fe2[5] - - return fe -} - -func (e *Fe) signBE() bool { - negZ, z := new(Fe), new(Fe) - fromMont(z, e) - neg(negZ, z) - - return negZ.cmp(z) > -1 -} - -//go:linkname add github.com/kilic/bls12-381.add -func add(c, a, b *Fe) - -//go:linkname toMont github.com/kilic/bls12-381.toMont -func toMont(a, b *Fe) - -//go:linkname inverse github.com/kilic/bls12-381.inverse -func inverse(inv, e *Fe) - -//go:linkname isQuadraticNonResidue github.com/kilic/bls12-381.isQuadraticNonResidue -func isQuadraticNonResidue(a *Fe) bool - -//go:linkname sqrt github.com/kilic/bls12-381.sqrt -func sqrt(c, a *Fe) bool - -//go:linkname square github.com/kilic/bls12-381.square -func square(c, a *Fe) - -//go:linkname fromMont github.com/kilic/bls12-381.fromMont -func fromMont(c, a *Fe) - -//go:linkname neg github.com/kilic/bls12-381.neg -func neg(c, a *Fe) - -//go:linkname isogenyMapG1 github.com/kilic/bls12-381.isogenyMapG1 -func isogenyMapG1(x, y *Fe) - -func swuMapG1Pre(u *Fe) (*Fe, *Fe, *Fe) { - var params = swuParamsForG1 - var tv [4]*Fe - for i := range 4 { - tv[i] = new(Fe) - } - square(tv[0], u) - mul(tv[0], tv[0], params.z) - square(tv[1], tv[0]) - x1 := new(Fe) - add(x1, tv[0], tv[1]) - inverse(x1, x1) - e1 := x1.isZero() - one := new(Fe).one() - add(x1, x1, one) - if e1 { - x1.set(params.zInv) - } - mul(x1, x1, params.minusBOverA) - gx1 := new(Fe) - square(gx1, x1) - add(gx1, gx1, params.a) - mul(gx1, gx1, x1) - add(gx1, gx1, params.b) - x2 := new(Fe) - mul(x2, tv[0], x1) - mul(tv[1], tv[0], tv[1]) - gx2 := new(Fe) - mul(gx2, gx1, tv[1]) - e2 := !isQuadraticNonResidue(gx1) - x, y2 := new(Fe), new(Fe) - if e2 { - x.set(x1) - y2.set(gx1) - } else { - x.set(x2) - y2.set(gx2) - } - y := new(Fe) - sqrt(y, y2) - - // This function is modified to perform the sign correction outside. - return x, y, u -} - -// SwuMapG1BE is implementation of Simplified Shallue-van de Woestijne-Ulas Method -// follows the implementation at draft-irtf-cfrg-hash-to-curve-06. -// uses big-endian variant: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-4.1.1 -func SwuMapG1BE(u *Fe) (*Fe, *Fe) { - x, y, u := swuMapG1Pre(u) - - if y.signBE() != u.signBE() { - neg(y, y) - } - - return x, y -} - -type PointG1 [3]Fe - -func pointG1tobls12381PointG1(p *PointG1) *bls12381.PointG1 { - return (*bls12381.PointG1)(unsafe.Pointer(p)) -} - -func feAtPos(pos int, p *bls12381.PointG1) *Fe { - return (*Fe)(unsafe.Pointer(&(p[pos]))) -} - -func HashToG1GenericBESwu(data, domain []byte) (*bls12381.PointG1, error) { - hashFunc := func() hash.Hash { - // We pass a null key so error is impossible here. - h, _ := blake2b.New512(nil) - - return h - } - - p, err := HashToCurveGenericBESwu(data, domain, hashFunc) - if err != nil { - return nil, err - } - - return p, nil -} - -func HashToCurveGenericBESwu(msg, domain []byte, hashFunc func() hash.Hash) (*bls12381.PointG1, error) { - g := bls12381.NewG1() - hashRes, err := hashToFpXMD(hashFunc, msg, domain, 2) - if err != nil { - return nil, err - } - u0, u1 := hashRes[0], hashRes[1] - - x0, y0 := SwuMapG1BE(u0) - x1, y1 := SwuMapG1BE(u1) - one := new(Fe).one() - p0, p1 := pointG1tobls12381PointG1(&PointG1{*x0, *y0, *one}), pointG1tobls12381PointG1(&PointG1{*x1, *y1, *one}) - - g.Add(p0, p0, p1) - g.Affine(p0) - isogenyMapG1(feAtPos(0, p0), feAtPos(1, p0)) - g.ClearCofactor(p0) - - return g.Affine(p0), nil -} - -func hashToFpXMD(f func() hash.Hash, msg []byte, domain []byte, count int) ([]*Fe, error) { - randBytes, err := expandMsgXMD(f, msg, domain, count*64) - if err != nil { - return nil, err - } - - els := make([]*Fe, count) - for i := range count { - var err error - - els[i], err = from64Bytes(randBytes[i*64 : (i+1)*64]) - if err != nil { - return nil, err - } - } - - return els, nil -} - -func expandMsgXMD(f func() hash.Hash, msg []byte, domain []byte, outLen int) ([]byte, error) { - h := f() - if len(domain) > 255 { - return nil, errors.New("invalid domain length") - } - if outLen > 65535 { - return nil, errors.New("invalid outLen") - } - domainLen := uint8(len(domain)) // #nosec G115 - - // DST_prime = DST || I2OSP(len(DST), 1) - // b_0 = H(Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime) - _, _ = h.Write(make([]byte, h.BlockSize())) - _, _ = h.Write(msg) - _, _ = h.Write([]byte{uint8(outLen >> 8), uint8(outLen)}) // #nosec G115 - _, _ = h.Write([]byte{0}) - _, _ = h.Write(domain) - _, _ = h.Write([]byte{domainLen}) - b0 := h.Sum(nil) - - // b_1 = H(b_0 || I2OSP(1, 1) || DST_prime) - h.Reset() - _, _ = h.Write(b0) - _, _ = h.Write([]byte{1}) - _, _ = h.Write(domain) - _, _ = h.Write([]byte{domainLen}) - b1 := h.Sum(nil) - - // b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime) - ell := (outLen + h.Size() - 1) / h.Size() - bi := b1 - out := make([]byte, outLen) - for i := 1; i < ell; i++ { - h.Reset() - // b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime) - tmp := make([]byte, h.Size()) - for j := range h.Size() { - tmp[j] = b0[j] ^ bi[j] - } - _, _ = h.Write(tmp) - _, _ = h.Write([]byte{1 + uint8(i)}) - _, _ = h.Write(domain) - _, _ = h.Write([]byte{domainLen}) - - // b_1 || ... || b_(ell - 1) - copy(out[(i-1)*h.Size():i*h.Size()], bi[:]) - bi = h.Sum(nil) - } - // b_ell - copy(out[(ell-1)*h.Size():], bi[:]) - - return out[:outLen], nil -} - -func from64Bytes(in []byte) (*Fe, error) { - if len(in) != 32*2 { - return nil, errors.New("input string must be equal 64 bytes") - } - a0 := make([]byte, fpByteSize) - copy(a0[fpByteSize-32:fpByteSize], in[:32]) - a1 := make([]byte, fpByteSize) - copy(a1[fpByteSize-32:fpByteSize], in[32:]) - e0, err := fromBytes(a0) - if err != nil { - return nil, err - } - e1, err := fromBytes(a1) - if err != nil { - return nil, err - } - // F = 2 ^ 256 * R - F := Fe{ - 0x75b3cd7c5ce820f, - 0x3ec6ba621c3edb0b, - 0x168a13d82bff6bce, - 0x87663c4bf8c449d2, - 0x15f34c83ddc8d830, - 0xf9628b49caa2e85, - } - - mul(e0, e0, &F) - add(e1, e1, e0) - - return e1, nil -} - -func fromBytes(in []byte) (*Fe, error) { - fe := &Fe{} - if len(in) != fpByteSize { - return nil, errors.New("input string must be equal 48 bytes") - } - fe.setBytes(in) - if !fe.isValid() { - return nil, errors.New("must be less than modulus") - } - toMont(fe, fe) - - return fe, nil -} diff --git a/driver/kilic/custom_amd64.go b/driver/kilic/custom_amd64.go deleted file mode 100644 index 6149881..0000000 --- a/driver/kilic/custom_amd64.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build amd64 && !generic - -/* -Copyright IBM Corp. All Rights Reserved. -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package kilic - -import ( - _ "unsafe" - - "golang.org/x/sys/cpu" -) - -func init() { - if !cpu.X86.HasADX || !cpu.X86.HasBMI2 { - mul = mulNoADX - } -} - -var mul func(c, a, b *Fe) = mulADX - -//go:linkname mulADX github.com/kilic/bls12-381.mulADX -func mulADX(c, a, b *Fe) - -//go:linkname mulNoADX github.com/kilic/bls12-381.mulNoADX -func mulNoADX(c, a, b *Fe) diff --git a/driver/kilic/custom_generic.go b/driver/kilic/custom_generic.go deleted file mode 100644 index 6fa670b..0000000 --- a/driver/kilic/custom_generic.go +++ /dev/null @@ -1,175 +0,0 @@ -//go:build !amd64 || generic - -/* -Copyright IBM Corp. All Rights Reserved. -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package kilic - -import "math/bits" - -// madd0 hi = a*b + c (discards lo bits) -func madd0(a, b, c uint64) (hi uint64) { - var carry, lo uint64 - hi, lo = bits.Mul64(a, b) - _, carry = bits.Add64(lo, c, 0) - hi, _ = bits.Add64(hi, 0, carry) - - return -} - -// madd1 hi, lo = a*b + c -func madd1(a, b, c uint64) (hi uint64, lo uint64) { - var carry uint64 - hi, lo = bits.Mul64(a, b) - lo, carry = bits.Add64(lo, c, 0) - hi, _ = bits.Add64(hi, 0, carry) - - return -} - -// madd2 hi, lo = a*b + c + d -func madd2(a, b, c, d uint64) (hi uint64, lo uint64) { - var carry uint64 - hi, lo = bits.Mul64(a, b) - c, carry = bits.Add64(c, d, 0) - hi, _ = bits.Add64(hi, 0, carry) - lo, carry = bits.Add64(lo, c, 0) - hi, _ = bits.Add64(hi, 0, carry) - - return -} - -func madd3(a, b, c, d, e uint64) (hi uint64, lo uint64) { - var carry uint64 - hi, lo = bits.Mul64(a, b) - c, carry = bits.Add64(c, d, 0) - hi, _ = bits.Add64(hi, 0, carry) - lo, carry = bits.Add64(lo, c, 0) - hi, _ = bits.Add64(hi, e, carry) - - return -} - -func mul(z, x, y *Fe) { - var t [6]uint64 - var c [3]uint64 - { - // round 0 - v := x[0] - c[1], c[0] = bits.Mul64(v, y[0]) - m := c[0] * 9940570264628428797 - c[2] = madd0(m, 13402431016077863595, c[0]) - c[1], c[0] = madd1(v, y[1], c[1]) - c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0]) - c[1], c[0] = madd1(v, y[2], c[1]) - c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0]) - c[1], c[0] = madd1(v, y[3], c[1]) - c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0]) - c[1], c[0] = madd1(v, y[4], c[1]) - c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0]) - c[1], c[0] = madd1(v, y[5], c[1]) - t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) - } - { - // round 1 - v := x[1] - c[1], c[0] = madd1(v, y[0], t[0]) - m := c[0] * 9940570264628428797 - c[2] = madd0(m, 13402431016077863595, c[0]) - c[1], c[0] = madd2(v, y[1], c[1], t[1]) - c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0]) - c[1], c[0] = madd2(v, y[2], c[1], t[2]) - c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0]) - c[1], c[0] = madd2(v, y[3], c[1], t[3]) - c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0]) - c[1], c[0] = madd2(v, y[4], c[1], t[4]) - c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0]) - c[1], c[0] = madd2(v, y[5], c[1], t[5]) - t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) - } - { - // round 2 - v := x[2] - c[1], c[0] = madd1(v, y[0], t[0]) - m := c[0] * 9940570264628428797 - c[2] = madd0(m, 13402431016077863595, c[0]) - c[1], c[0] = madd2(v, y[1], c[1], t[1]) - c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0]) - c[1], c[0] = madd2(v, y[2], c[1], t[2]) - c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0]) - c[1], c[0] = madd2(v, y[3], c[1], t[3]) - c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0]) - c[1], c[0] = madd2(v, y[4], c[1], t[4]) - c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0]) - c[1], c[0] = madd2(v, y[5], c[1], t[5]) - t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) - } - { - // round 3 - v := x[3] - c[1], c[0] = madd1(v, y[0], t[0]) - m := c[0] * 9940570264628428797 - c[2] = madd0(m, 13402431016077863595, c[0]) - c[1], c[0] = madd2(v, y[1], c[1], t[1]) - c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0]) - c[1], c[0] = madd2(v, y[2], c[1], t[2]) - c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0]) - c[1], c[0] = madd2(v, y[3], c[1], t[3]) - c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0]) - c[1], c[0] = madd2(v, y[4], c[1], t[4]) - c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0]) - c[1], c[0] = madd2(v, y[5], c[1], t[5]) - t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) - } - { - // round 4 - v := x[4] - c[1], c[0] = madd1(v, y[0], t[0]) - m := c[0] * 9940570264628428797 - c[2] = madd0(m, 13402431016077863595, c[0]) - c[1], c[0] = madd2(v, y[1], c[1], t[1]) - c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0]) - c[1], c[0] = madd2(v, y[2], c[1], t[2]) - c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0]) - c[1], c[0] = madd2(v, y[3], c[1], t[3]) - c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0]) - c[1], c[0] = madd2(v, y[4], c[1], t[4]) - c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0]) - c[1], c[0] = madd2(v, y[5], c[1], t[5]) - t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) - } - { - // round 5 - v := x[5] - c[1], c[0] = madd1(v, y[0], t[0]) - m := c[0] * 9940570264628428797 - c[2] = madd0(m, 13402431016077863595, c[0]) - c[1], c[0] = madd2(v, y[1], c[1], t[1]) - c[2], z[0] = madd2(m, 2210141511517208575, c[2], c[0]) - c[1], c[0] = madd2(v, y[2], c[1], t[2]) - c[2], z[1] = madd2(m, 7435674573564081700, c[2], c[0]) - c[1], c[0] = madd2(v, y[3], c[1], t[3]) - c[2], z[2] = madd2(m, 7239337960414712511, c[2], c[0]) - c[1], c[0] = madd2(v, y[4], c[1], t[4]) - c[2], z[3] = madd2(m, 5412103778470702295, c[2], c[0]) - c[1], c[0] = madd2(v, y[5], c[1], t[5]) - z[5], z[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1]) - } - - // if z > q --> z -= q - // note: this is NOT constant time - //nolint:staticcheck - if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) { - var b uint64 - z[0], b = bits.Sub64(z[0], 13402431016077863595, 0) - z[1], b = bits.Sub64(z[1], 2210141511517208575, b) - z[2], b = bits.Sub64(z[2], 7435674573564081700, b) - z[3], b = bits.Sub64(z[3], 7239337960414712511, b) - z[4], b = bits.Sub64(z[4], 5412103778470702295, b) - z[5], _ = bits.Sub64(z[5], 1873798617647539866, b) - } -} diff --git a/go.mod b/go.mod index a02e6ac..a871b5e 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,8 @@ go 1.26.3 require ( github.com/consensys/gnark-crypto v0.20.1 github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 - github.com/kilic/bls12-381 v0.1.0 github.com/stretchr/testify v1.11.1 - golang.org/x/crypto v0.52.0 - golang.org/x/sys v0.45.0 + golang.org/x/crypto v0.53.0 ) require ( @@ -17,5 +15,6 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect + golang.org/x/sys v0.46.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1eaff0d..bb2882f 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 h1:B1Nt8hKb//KvgGRprk0h1t4lCnwhE9/ryb1WqfZbV+M= github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE= -github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= -github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -21,15 +19,10 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= -golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/math.go b/math.go index d797ff6..ad8f002 100644 --- a/math.go +++ b/math.go @@ -35,10 +35,10 @@ SPDX-License-Identifier: Apache-2.0 // - FP256BN_AMCL: 256-bit Barreto-Naehrig curve (AMCL backend) // - BN254: 254-bit Barreto-Naehrig curve (Gurvy backend) // - FP256BN_AMCL_MIRACL: 256-bit BN curve MIRACL variant (AMCL backend) -// - BLS12_381: BLS12-381 curve (Kilic backend) +// - BLS12_381: BLS12-381 curve (deprecated, slot is locked) // - BLS12_377_GURVY: BLS12-377 curve (Gurvy backend) // - BLS12_381_GURVY: BLS12-381 curve (Gurvy backend) -// - BLS12_381_BBS: BLS12-381 optimized for BBS+ signatures (Kilic backend) +// - BLS12_381_BBS: BLS12-381 optimized for BBS+ signatures (Gurvy backend) // - BLS12_381_BBS_GURVY: BLS12-381 for BBS+ (Gurvy backend) // // # Thread Safety @@ -59,7 +59,6 @@ import ( "github.com/IBM/mathlib/driver/amcl" "github.com/IBM/mathlib/driver/gurvy" "github.com/IBM/mathlib/driver/gurvy/bls12381" - "github.com/IBM/mathlib/driver/kilic" ) // CurveID identifies a specific elliptic curve configuration and its backend implementation. @@ -80,9 +79,8 @@ const ( // Provided for legacy compatibility with MIRACL-based systems. FP256BN_AMCL_MIRACL - // BLS12_381 represents the BLS12-381 curve using the Kilic backend. - // Recommended for new projects due to excellent security margins and wide adoption. - // Suitable for BLS signatures and modern cryptographic protocols. + // BLS12_381 is deprecated. The slot is locked to preserve existing CurveID ordinals. + // Use BLS12_381_GURVY instead. BLS12_381 // BLS12_377_GURVY represents the BLS12-377 curve using the Gurvy backend. @@ -93,8 +91,8 @@ const ( // Performance-optimized implementation of BLS12-381 with assembly optimizations. BLS12_381_GURVY - // BLS12_381_BBS is equivalent to BLS12_381 up to HashToG1 and HashToG2. - // Those functions follow the rules of the standard draft. + // BLS12_381_BBS uses the Gurvy backend for BLS12-381 with BBS+-compatible + // HashToG1 and HashToG2 (standard draft). Equivalent to BLS12_381_BBS_GURVY. BLS12_381_BBS // BLS12_381_BBS_GURVY is equivalent to BLS12_381_GURVY up to HashToG1 and HashToG2. @@ -183,18 +181,8 @@ var Curves []*Curve = []*Curve{ curveID: FP256BN_AMCL_MIRACL, }, { - c: kilic.NewBls12_381(), - GenG1: NewG1((&kilic.Bls12_381{}).GenG1(), BLS12_381), - GenG2: NewG2((&kilic.Bls12_381{}).GenG2(), BLS12_381), - GenGt: NewGt((&kilic.Bls12_381{}).GenGt(), BLS12_381), - GroupOrder: NewZr(kilic.NewBls12_381().GroupOrder(), BLS12_381), - CoordByteSize: (&kilic.Bls12_381{}).CoordinateByteSize(), - G1ByteSize: (&kilic.Bls12_381{}).G1ByteSize(), - CompressedG1ByteSize: (&kilic.Bls12_381{}).CompressedG1ByteSize(), - G2ByteSize: (&kilic.Bls12_381{}).G2ByteSize(), - CompressedG2ByteSize: (&kilic.Bls12_381{}).CompressedG2ByteSize(), - ScalarByteSize: (&kilic.Bls12_381{}).ScalarByteSize(), - curveID: BLS12_381, + // This curve is deprecated. Nevertheless, the index BLS12_381 cannot be reused and is therefore locked. + curveID: BLS12_381, }, { c: gurvy.NewBls12_377(), @@ -225,17 +213,17 @@ var Curves []*Curve = []*Curve{ curveID: BLS12_381_GURVY, }, { - c: kilic.NewBls12_381BBS(), - GenG1: NewG1(kilic.NewBls12_381BBS().GenG1(), BLS12_381_BBS), - GenG2: NewG2(kilic.NewBls12_381BBS().GenG2(), BLS12_381_BBS), - GenGt: NewGt(kilic.NewBls12_381BBS().GenGt(), BLS12_381_BBS), - GroupOrder: NewZr(kilic.NewBls12_381().GroupOrder(), BLS12_381_BBS), - CoordByteSize: kilic.NewBls12_381BBS().CoordinateByteSize(), - G1ByteSize: kilic.NewBls12_381BBS().G1ByteSize(), - CompressedG1ByteSize: kilic.NewBls12_381BBS().CompressedG1ByteSize(), - G2ByteSize: kilic.NewBls12_381BBS().G2ByteSize(), - CompressedG2ByteSize: kilic.NewBls12_381BBS().CompressedG2ByteSize(), - ScalarByteSize: kilic.NewBls12_381BBS().ScalarByteSize(), + c: bls12381.NewBBSCurve(), + GenG1: NewG1(bls12381.NewBBSCurve().GenG1(), BLS12_381_BBS), + GenG2: NewG2(bls12381.NewBBSCurve().GenG2(), BLS12_381_BBS), + GenGt: NewGt(bls12381.NewBBSCurve().GenGt(), BLS12_381_BBS), + GroupOrder: NewZr(bls12381.NewCurve().GroupOrder(), BLS12_381_BBS), + CoordByteSize: bls12381.NewBBSCurve().CoordinateByteSize(), + G1ByteSize: bls12381.NewBBSCurve().G1ByteSize(), + CompressedG1ByteSize: bls12381.NewBBSCurve().CompressedG1ByteSize(), + G2ByteSize: bls12381.NewBBSCurve().G2ByteSize(), + CompressedG2ByteSize: bls12381.NewBBSCurve().CompressedG2ByteSize(), + ScalarByteSize: bls12381.NewBBSCurve().ScalarByteSize(), curveID: BLS12_381_BBS, }, { @@ -738,9 +726,19 @@ func (c *Curve) ID() CurveID { return c.curveID } +// IsDeprecated returns true if this curve slot is deprecated and has no active implementation. +// Deprecated curves preserve their CurveID slot but all operations will fail. +func (c *Curve) IsDeprecated() bool { + return c.c == nil +} + // Rand returns a cryptographically secure random number generator. -// Returns an error if the RNG cannot be initialized. +// Returns an error if the RNG cannot be initialized or if the curve is deprecated. func (c *Curve) Rand() (io.Reader, error) { + if c.c == nil { + return nil, fmt.Errorf("curve %s is deprecated", CurveIDToString(c.curveID)) + } + return c.c.Rand() } diff --git a/math_test.go b/math_test.go index aab51de..06a6c58 100644 --- a/math_test.go +++ b/math_test.go @@ -26,6 +26,10 @@ var seed = time.Now().Unix() func TestImmutability(t *testing.T) { for _, curve := range Curves { + if curve.IsDeprecated() { + continue + } + rng, err := curve.Rand() require.NoError(t, err) @@ -38,6 +42,10 @@ func TestImmutability(t *testing.T) { func TestCurveId(t *testing.T) { for _, curve := range Curves { + if curve.IsDeprecated() { + continue + } + rng, err := curve.Rand() require.NoError(t, err) @@ -851,6 +859,10 @@ func TestJSONMarshalerFails(t *testing.T) { func TestCurves(t *testing.T) { for _, curve := range Curves { + if curve.IsDeprecated() { + continue + } + testNotZeroAfterAdd(t, curve) testModAdd(t, curve) testModAdd2(t, curve) @@ -876,70 +888,38 @@ func TestCurves(t *testing.T) { } } -func Test381Compat(t *testing.T) { - rng, err := Curves[BLS12_381].Rand() - require.NoError(t, err) - - kilic := Curves[BLS12_381] - gurvy := Curves[BLS12_381_GURVY] - - rk := kilic.NewRandomZr(rng) - rg := gurvy.NewZrFromBytes(rk.Bytes()) - assert.Equal(t, rk.Bytes(), rg.Bytes()) - - g1g := gurvy.GenG1.Mul(rg) - g1k := kilic.GenG1.Mul(rk) - assert.Equal(t, g1g.Bytes(), g1k.Bytes()) - assert.Equal(t, g1g.Compressed(), g1k.Compressed()) - - g2g := gurvy.GenG2.Mul(rg) - g2k := kilic.GenG2.Mul(rk) - assert.Equal(t, g2g.Bytes(), g2k.Bytes()) - assert.Equal(t, g2g.Compressed(), g2k.Compressed()) - - gtg := gurvy.GenGt.Exp(rg) - gtk := kilic.GenGt.Exp(rk) - assert.Equal(t, gtg.Bytes(), gtk.Bytes()) - - hg := gurvy.HashToG1([]byte("Chase!")) - hk := kilic.HashToG1([]byte("Chase!")) - assert.Equal(t, hg.Bytes(), hk.Bytes()) - - hg = gurvy.HashToG1WithDomain([]byte("CD"), []byte("EF")) - hk = kilic.HashToG1WithDomain([]byte("CD"), []byte("EF")) - assert.Equal(t, hg.Bytes(), hk.Bytes()) -} - func Test381BBSCompat(t *testing.T) { + // BLS12_381_BBS and BLS12_381_BBS_GURVY now share the same Gurvy backend. + // Verify they produce identical results. rng, err := Curves[BLS12_381_BBS].Rand() require.NoError(t, err) - kilic := Curves[BLS12_381_BBS] - gurvy := Curves[BLS12_381_BBS_GURVY] + bbs := Curves[BLS12_381_BBS] + bbsGurvy := Curves[BLS12_381_BBS_GURVY] - rk := kilic.NewRandomZr(rng) - rg := gurvy.NewZrFromBytes(rk.Bytes()) + rk := bbs.NewRandomZr(rng) + rg := bbsGurvy.NewZrFromBytes(rk.Bytes()) assert.Equal(t, rk.Bytes(), rg.Bytes()) - g1g := gurvy.GenG1.Mul(rg) - g1k := kilic.GenG1.Mul(rk) - assert.Equal(t, g1g.Bytes(), g1k.Bytes()) - assert.Equal(t, g1g.Compressed(), g1k.Compressed()) + g1bbs := bbs.GenG1.Mul(rk) + g1bbsGurvy := bbsGurvy.GenG1.Mul(rg) + assert.Equal(t, g1bbs.Bytes(), g1bbsGurvy.Bytes()) + assert.Equal(t, g1bbs.Compressed(), g1bbsGurvy.Compressed()) - g2g := gurvy.GenG2.Mul(rg) - g2k := kilic.GenG2.Mul(rk) - assert.Equal(t, g2g.Bytes(), g2k.Bytes()) - assert.Equal(t, g2g.Compressed(), g2k.Compressed()) + g2bbs := bbs.GenG2.Mul(rk) + g2bbsGurvy := bbsGurvy.GenG2.Mul(rg) + assert.Equal(t, g2bbs.Bytes(), g2bbsGurvy.Bytes()) + assert.Equal(t, g2bbs.Compressed(), g2bbsGurvy.Compressed()) - gtg := gurvy.GenGt.Exp(rg) - gtk := kilic.GenGt.Exp(rk) - assert.Equal(t, gtg.Bytes(), gtk.Bytes()) + gtbbs := bbs.GenGt.Exp(rk) + gtbbsGurvy := bbsGurvy.GenGt.Exp(rg) + assert.Equal(t, gtbbs.Bytes(), gtbbsGurvy.Bytes()) - hg := gurvy.HashToG1([]byte("Chase!")) - hk := kilic.HashToG1([]byte("Chase!")) - assert.Equal(t, hg.Bytes(), hk.Bytes()) + hbbs := bbs.HashToG1([]byte("Chase!")) + hbbsGurvy := bbsGurvy.HashToG1([]byte("Chase!")) + assert.Equal(t, hbbs.Bytes(), hbbsGurvy.Bytes()) - hg = gurvy.HashToG1WithDomain([]byte("CD"), []byte("EF")) - hk = kilic.HashToG1WithDomain([]byte("CD"), []byte("EF")) - assert.Equal(t, hg.Bytes(), hk.Bytes()) + hbbs = bbs.HashToG1WithDomain([]byte("CD"), []byte("EF")) + hbbsGurvy = bbsGurvy.HashToG1WithDomain([]byte("CD"), []byte("EF")) + assert.Equal(t, hbbs.Bytes(), hbbsGurvy.Bytes()) } diff --git a/perf_test.go b/perf_test.go index b820f56..4506b44 100644 --- a/perf_test.go +++ b/perf_test.go @@ -14,7 +14,6 @@ import ( bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" - kilic "github.com/kilic/bls12-381" ) func newRandZr(rng io.Reader, m *big.Int) *big.Int { @@ -51,20 +50,6 @@ func blsInitGurvy(b *testing.B) (*bls12381.G2Affine, *big.Int) { return g, x } -func blsInitKilic(b *testing.B) (*kilic.PointG2, *big.Int) { - b.Helper() - rng := rand.Reader - - _g := kilic.NewG2() - g2 := _g.One() - g := _g.New() - - _g.MulScalarBig(g, g2, newRandZr(rng, fr.Modulus())) - x := newRandZr(rng, fr.Modulus()) - - return g, x -} - func pokPedersenCommittmentInit(b *testing.B, curve *Curve) (io.Reader, *G1, *G1, *Zr, error) { b.Helper() rng, err := curve.Rand() @@ -92,66 +77,6 @@ func pokPedersenCommittmentInitGurvy(b *testing.B) (io.Reader, *bls12381.G1Affin return rng, g, h, x } -func pokPedersenCommittmentInitKilic(b *testing.B) (io.Reader, *kilic.PointG1, *kilic.PointG1, *big.Int) { - b.Helper() - rng := rand.Reader - - _g := kilic.NewG1() - g1 := _g.One() - g := _g.New() - h := _g.New() - - _g.MulScalarBig(g, g1, newRandZr(rng, fr.Modulus())) - _g.MulScalarBig(h, g1, newRandZr(rng, fr.Modulus())) - x := newRandZr(rng, fr.Modulus()) - - return rng, g, h, x -} - -func Benchmark_Sequential_PedersenCommitmentPoKKilic(b *testing.B) { - rng, g, h, x := pokPedersenCommittmentInitKilic(b) - _g := kilic.NewG1() - tmp := _g.New() - mod := fr.Modulus() - - b.ResetTimer() - - b.Run("curve BLS12_381 (direct)", func(b *testing.B) { - for range b.N { - r := newRandZr(rng, mod) - c := _g.New() - _g.MulScalarBig(c, g, x) - _g.MulScalarBig(tmp, h, r) - _g.Add(c, c, tmp) - - x_tilde := newRandZr(rng, mod) - r_tilde := newRandZr(rng, mod) - t := _g.New() - _g.MulScalarBig(t, g, x_tilde) - _g.MulScalarBig(tmp, h, r_tilde) - _g.Add(t, t, tmp) - - chal := newRandZr(rng, mod) - - x_hat := new(big.Int).Add(x_tilde, new(big.Int).Mul(chal, x)) - r_hat := new(big.Int).Add(r_tilde, new(big.Int).Mul(chal, r)) - - v1 := _g.New() - _g.MulScalarBig(v1, g, x_hat) - _g.MulScalarBig(tmp, h, r_hat) - _g.Add(v1, v1, tmp) - - v2 := _g.New() - _g.MulScalarBig(v2, c, chal) - _g.Add(v2, v2, t) - - if !_g.Equal(v1, v2) { - panic("invalid PoK") - } - } - }) -} - func Benchmark_Sequential_PedersenCommitmentPoKGurvy(b *testing.B) { rng, g, h, x := pokPedersenCommittmentInitGurvy(b) @@ -188,6 +113,10 @@ func Benchmark_Sequential_PedersenCommitmentPoKGurvy(b *testing.B) { func Benchmark_Sequential_PedersenCommitmentPoK(b *testing.B) { for _, curve := range Curves { + if curve.IsDeprecated() { + continue + } + rng, g, h, x, err := pokPedersenCommittmentInit(b, curve) if err != nil { panic(err) @@ -227,6 +156,10 @@ func Benchmark_Sequential_PedersenCommitmentPoK(b *testing.B) { func Benchmark_Sequential_BLS(b *testing.B) { for _, curve := range Curves { + if curve.IsDeprecated() { + continue + } + g, x, err := blsInit(b, curve) if err != nil { panic(err) @@ -311,59 +244,13 @@ func Benchmark_Parallel_BLSGurvy(b *testing.B) { }) } -func Benchmark_Parallel_BLSKilic(b *testing.B) { - g, x := blsInitKilic(b) - _g := kilic.NewG2() - pk := _g.New() - _g.MulScalarBig(pk, g, x) - - b.ResetTimer() - - var sig *kilic.PointG1 - - b.Run("sign curve BLS12_381_GURVY (direct)", func(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - _g := kilic.NewG1() - for pb.Next() { - g1 := kilic.NewG1() - h, err := g1.HashToCurve([]byte("msg"), []byte("context")) - if err != nil { - panic(err) - } - - sig = _g.New() - _g.MulScalarBig(sig, h, x) - } - }) - }) - - kilic.NewG1().Neg(sig, sig) - - b.ResetTimer() - - b.Run("verify curve BLS12_381_GURVY (direct)", func(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - g1 := kilic.NewG1() - h, err := g1.HashToCurve([]byte("msg"), []byte("context")) - if err != nil { - panic(err) - } - bls := kilic.NewEngine() - bls.AddPair(sig, g) - bls.AddPair(h, pk) - - if !bls.Check() { - panic("invalid signature") - } - } - }) - }) -} - func Benchmark_Parallel_BLS(b *testing.B) { for _, curve := range Curves { - if curve.curveID != BLS12_381 && curve.curveID != BLS12_381_GURVY { + if curve.IsDeprecated() { + continue + } + + if curve.curveID != BLS12_381_GURVY { continue } @@ -481,81 +368,3 @@ func Benchmark_Parallel_IndividualOpsGurvy(b *testing.B) { }) }) } - -func Benchmark_Parallel_IndividualOpsKilic(b *testing.B) { - curve := Curves[BLS12_381] - g_kili, x_kili := blsInitKilic(b) - g_math, x_math, err := blsInit(b, curve) - if err != nil { - panic(err) - } - - _g := kilic.NewG2() - pk_kili := _g.New() - _g.MulScalarBig(pk_kili, g_kili, x_kili) - pk_math := g_math.Mul(x_math) - - var h_kili *kilic.PointG1 - var h_math *G1 - - var sig_kili *kilic.PointG1 - var sig_math *G1 - - var t_math *Gt - - b.Run("hash/kilic", func(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - g1 := kilic.NewG1() - h_kili, _ = g1.HashToCurve([]byte("msg"), []byte("context")) - } - }) - }) - - b.Run("hash/mathlib", func(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - h_math = curve.HashToG1WithDomain([]byte("msg"), []byte("context")) - } - }) - }) - - b.Run("sign/kilic", func(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - _g := kilic.NewG1() - for pb.Next() { - sig_kili = _g.New() - _g.MulScalarBig(sig_kili, h_kili, x_kili) - } - }) - }) - - b.Run("sign/mathlib", func(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - sig_math = h_math.Mul(x_math) - } - }) - }) - - b.Run("pairing2/kilic", func(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - bls := kilic.NewEngine() - bls.AddPair(sig_kili, g_kili) - bls.AddPair(h_kili, pk_kili) - bls.Result() - } - }) - }) - - b.Run("pairing2/mathlib", func(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - t_math = curve.Pairing2(g_math, sig_math, pk_math, h_math) - - t_math = curve.FExp(t_math) - } - }) - }) -}