diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 03d8ff6..1b5664d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,9 @@ jobs: - name: Run fuzzy field tests run: | - go test -tags=fuzz -fuzz=Fuzz -fuzztime=30s github.com/elliottech/poseidon_crypto/field + for test in $(go test -list='Fuzz.*' github.com/elliottech/poseidon_crypto/field | grep ^Fuzz); do + go test -fuzz="^${test}$" -fuzztime=30s github.com/elliottech/poseidon_crypto/field + done - name: Run fuzzy signature tests run: | go test -tags=fuzz -fuzz=Fuzz -fuzztime=30s github.com/elliottech/poseidon_crypto/signature/schnorr @@ -41,6 +43,11 @@ jobs: for test in $(go test -list='Fuzz.*' github.com/elliottech/poseidon_crypto/int | grep ^Fuzz); do go test -fuzz="^${test}$" -fuzztime=30s github.com/elliottech/poseidon_crypto/int done + - name: Run fuzzy ecgfp5 tests + run: | + for test in $(go test -list='Fuzz.*' github.com/elliottech/poseidon_crypto/curve/ecgfp5 | grep ^Fuzz); do + go test -fuzz="^${test}$" -fuzztime=30s github.com/elliottech/poseidon_crypto/curve/ecgfp5 + done lint: name: Lint diff --git a/curve/ecgfp5/scalar_field.go b/curve/ecgfp5/scalar_field.go index b14b29a..ea2a763 100644 --- a/curve/ecgfp5/scalar_field.go +++ b/curve/ecgfp5/scalar_field.go @@ -51,11 +51,12 @@ func ScalarElementFromLittleEndianBytes(data []byte) ECgFp5Scalar { value[i] = binary.LittleEndian.Uint64(data[i*8:]) } - if !value.IsCanonical() { - panic("trying to deserialize non-canonical bytes") + bigValue := ToNonCanonicalBigInt(value) + if bigValue.Cmp(ORDER) < 0 { + return value } - return value + return FromNonCanonicalBigInt(bigValue) } func (s ECgFp5Scalar) SplitTo4BitLimbs() [80]uint8 { @@ -163,12 +164,26 @@ func Select(c uint64, a0, a1 ECgFp5Scalar) ECgFp5Scalar { } func (s ECgFp5Scalar) Add(rhs ECgFp5Scalar) ECgFp5Scalar { + if !s.IsCanonical() { + panic("Add: first operand 's' must be canonical (< n)") + } + if !rhs.IsCanonical() { + panic("Add: second operand 'rhs' must be canonical (< n)") + } + r0 := s.AddInner(rhs) r1, c := r0.SubInner(N) // one reduce is enough if s < n and rhs < n return Select(c, r1, r0) } func (s *ECgFp5Scalar) Sub(rhs ECgFp5Scalar) ECgFp5Scalar { + if !s.IsCanonical() { + panic("Sub: first operand 's' must be canonical (< n)") + } + if !rhs.IsCanonical() { + panic("Sub: second operand 'rhs' must be canonical (< n)") + } + r0, c := s.SubInner(rhs) r1 := r0.AddInner(N) // one add is enough if s < n and rhs < n return Select(c, r0, r1) @@ -242,7 +257,7 @@ func FromGfp5(fp5 gFp5.Element) ECgFp5Scalar { result := new(big.Int) for i := 4; i >= 0; i-- { result.Lsh(result, 64) - result.Or(result, new(big.Int).SetUint64(fp5[i].ToCanonicalUint64())) // it always fit to + result.Or(result, new(big.Int).SetUint64(fp5[i].ToCanonicalUint64())) } return FromNonCanonicalBigInt(result) diff --git a/curve/ecgfp5/scalar_field_test.go b/curve/ecgfp5/scalar_field_test.go index 38d99ea..68c648a 100644 --- a/curve/ecgfp5/scalar_field_test.go +++ b/curve/ecgfp5/scalar_field_test.go @@ -1,6 +1,7 @@ package ecgfp5 import ( + "math/big" "testing" g "github.com/elliottech/poseidon_crypto/field/goldilocks" @@ -24,6 +25,40 @@ func TestSerdes(t *testing.T) { } } +func TestScalarElementFromLittleEndianBytesReduces(t *testing.T) { + // Create a byte array that represents a scalar larger than the order + bigScalar := new(big.Int).Add(ORDER, big.NewInt(1234567890)) + leBytes := bigScalar.Bytes() + s := ScalarElementFromLittleEndianBytes(leBytes) + + if ToNonCanonicalBigInt(s).Cmp(ORDER) != -1 { + t.Fatalf("Expected scalar to be reduced modulo order, but got %v", ToNonCanonicalBigInt(s)) + } +} + +func FuzzSerdes(f *testing.F) { + f.Add([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40}) + f.Add(ORDER.Bytes()) + + f.Fuzz(func(t *testing.T, a []byte) { + // take 40 bytes only + if len(a) > 40 { + a = a[:40] + } else if len(a) < 40 { + // pad with zeros + a = append(a, make([]byte, 40-len(a))...) + } + s := ScalarElementFromLittleEndianBytes(a) + + b := s.ToLittleEndianBytes() + ss := ScalarElementFromLittleEndianBytes(b) + + if !s.Equals(ss) { + t.Fatalf("Serdes mismatch: %v != %v", s, ss) + } + }) +} + func TestSplitTo4LimbBits(t *testing.T) { scalar := ECgFp5Scalar{ 6950590877883398434, @@ -120,12 +155,31 @@ func TestAddScalar(t *testing.T) { } } +func FuzzAddScalar(f *testing.F) { + f.Add([]byte{1, 2, 3, 4}, []byte{5, 6, 7, 8}) + f.Add(ORDER.Bytes(), ORDER.Bytes()) + + f.Fuzz(func(t *testing.T, a []byte, b []byte) { + aBig := new(big.Int).SetBytes(a) + scalar1 := FromNonCanonicalBigInt(aBig) + bBig := new(big.Int).SetBytes(b) + scalar2 := FromNonCanonicalBigInt(bBig) + + result := scalar1.Add(scalar2) + resultBig := FromNonCanonicalBigInt(new(big.Int).Add(aBig, bBig)) + + if !result.Equals(resultBig) { + t.Fatalf("Addition mismatch: %v + %v != %v", scalar1, scalar2, result) + } + }) +} + func TestSub(t *testing.T) { scalar1 := ECgFp5Scalar{1, 2, 0, 0, 0} - scalar2 := ECgFp5Scalar{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF} + scalar2 := ECgFp5Scalar{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x0FFFFFFFFFFFFFFF} result := scalar1.Sub(scalar2) - expectedValues := ECgFp5Scalar{0xe80fd996948bffe3, 0xe8885c39d724a09e, 0x7fffffe6cfb80639, 0x7ffffff100000016, 0x7ffffffd80000007} + expectedValues := ECgFp5Scalar{0xe80fd996948bffe3, 0xe8885c39d724a09e, 0x7fffffe6cfb80639, 0x7ffffff100000016, 8070450521510510599} for i := 0; i < 5; i++ { if result[i] != expectedValues[i] { @@ -134,6 +188,25 @@ func TestSub(t *testing.T) { } } +func FuzzSubScalar(f *testing.F) { + f.Add([]byte{1, 2, 3, 4}, []byte{5, 6, 7, 8}) + f.Add(ORDER.Bytes(), ORDER.Bytes()) + + f.Fuzz(func(t *testing.T, a []byte, b []byte) { + aBig := new(big.Int).SetBytes(a) + scalar1 := FromNonCanonicalBigInt(aBig) + bBig := new(big.Int).SetBytes(b) + scalar2 := FromNonCanonicalBigInt(bBig) + + result := scalar1.Sub(scalar2) + resultBig := FromNonCanonicalBigInt(new(big.Int).Sub(aBig, bBig)) + + if !result.Equals(resultBig) { + t.Fatalf("Subtraction mismatch: %v - %v != %v", scalar1, scalar2, result) + } + }) +} + func TestSelect(t *testing.T) { a0 := ECgFp5Scalar{1, 2, 3, 4, 5} a1 := ECgFp5Scalar{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFD, 0xFFFFFFFFFFFFFFFC, 0xFFFFFFFFFFFFFFFB} @@ -155,7 +228,7 @@ func TestSelect(t *testing.T) { func TestMontyMul(t *testing.T) { scalar1 := ECgFp5Scalar{1, 2, 3, 4, 5} - scalar2 := ECgFp5Scalar{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF} + scalar2 := ECgFp5Scalar{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF} // montymul can work with non-canonical inputs result := scalar1.MontyMul(scalar2) expectedValues := ECgFp5Scalar{10974894505036100890, 7458803775930281466, 744239893213209819, 3396127080529349464, 5979369289905897562} @@ -192,6 +265,25 @@ func TestMul(t *testing.T) { } } +func FuzzMulScalar(f *testing.F) { + f.Add([]byte{1, 2, 3, 4}, []byte{5, 6, 7, 8}) + f.Add(ORDER.Bytes(), ORDER.Bytes()) + + f.Fuzz(func(t *testing.T, a []byte, b []byte) { + aBig := new(big.Int).SetBytes(a) + scalar1 := FromNonCanonicalBigInt(aBig) + bBig := new(big.Int).SetBytes(b) + scalar2 := FromNonCanonicalBigInt(bBig) + + result := scalar1.Mul(scalar2) + resultBig := FromNonCanonicalBigInt(new(big.Int).Mul(aBig, bBig)) + + if !result.Equals(resultBig) { + t.Fatalf("Multiplication mismatch: %v * %v != %v", scalar1, scalar2, result) + } + }) +} + func TestRecodeSigned(t *testing.T) { var ss [50]int32 scalar := ECgFp5Scalar{ diff --git a/field/field_test.go b/field/field_test.go index b89ee06..a8c8085 100644 --- a/field/field_test.go +++ b/field/field_test.go @@ -72,7 +72,8 @@ var inputs = []uint64{ 9223372036854775798, 9223372036854775799, 9223372036854775800, 9223372036854775801, 9223372036854775802, 9223372036854775803, 9223372036854775804, 9223372036854775805, 9223372036854775806, 9223372036854775807, 9223372036854775808, 9223372036854775809, 9223372036854775810, 9223372036854775811, 9223372036854775812, 9223372036854775813, 9223372036854775814, 9223372036854775815, 9223372036854775816, 9223372036854775817, 18446744069414584311, 18446744069414584312, 18446744069414584313, 18446744069414584314, - 18446744069414584315, 18446744069414584316, 18446744069414584317, 18446744069414584318, 18446744069414584319, 18446744069414584320, + 18446744069414584315, 18446744069414584316, 18446744069414584317, 18446744069414584318, 18446744069414584319, 18446744069414584320, 18446744069414584321, 18446744069414584323, + math.MaxUint64, } func NewBigInt(x uint64) *big.Int { @@ -427,6 +428,27 @@ func FuzzTestF(f *testing.F) { }) } +func FuzzEquivalenceF(f *testing.F) { + f.Add(uint64(0)) + f.Add(uint64(1)) + f.Add(g.ORDER - 1) + f.Add(g.ORDER + 1) + f.Add(uint64(math.MaxUint64)) + + f.Fuzz(func(t *testing.T, val uint64) { + fVal := g.GoldilocksField(val) + gVal := g.FromUint64(val) + + if fVal.ToCanonicalUint64() != gVal.Uint64() { + t.Fatalf("FromUint64: Expected %d to be equal, but got %d and %d", val, fVal.ToCanonicalUint64(), gVal.Uint64()) + } + + if g.SquareF(fVal).ToCanonicalUint64() != new(g.Element).Square(&gVal).Uint64() { + t.Fatalf("Square: Expected square of %d to be equal, but got %d and %d", val, g.SquareF(fVal).ToCanonicalUint64(), new(g.Element).Square(&gVal).Uint64()) + } + }) +} + // Quintic extension tests func TestQuinticExtensionAddSubMulSquare(t *testing.T) { diff --git a/signature/schnorr/schnorr_test.go b/signature/schnorr/schnorr_test.go index 4b03588..0eab7a5 100644 --- a/signature/schnorr/schnorr_test.go +++ b/signature/schnorr/schnorr_test.go @@ -192,6 +192,14 @@ func TestBytes(t *testing.T) { if err := Validate(pk.ToLittleEndianBytes(), hashedMsg.ToLittleEndianBytes(), sig2.ToBytes()); err != nil { t.Fatalf("Signature is invalid") } + + // Works with non-canonical inputs + sig3 := sig2 + sig3.S = sig3.S.AddInner(curve.N) + sig3.E = sig3.E.AddInner(curve.N) + if err := Validate(pk.ToLittleEndianBytes(), hashedMsg.ToLittleEndianBytes(), sig3.ToBytes()); err != nil { + t.Fatalf("Signature is invalid") + } } func BenchmarkSignatureVerify(b *testing.B) {