Skip to content

Commit 315971c

Browse files
authored
Reduce instead of error (#29)
1 parent 85249ac commit 315971c

File tree

5 files changed

+153
-9
lines changed

5 files changed

+153
-9
lines changed

.github/workflows/test.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ jobs:
3232

3333
- name: Run fuzzy field tests
3434
run: |
35-
go test -tags=fuzz -fuzz=Fuzz -fuzztime=30s github.com/elliottech/poseidon_crypto/field
35+
for test in $(go test -list='Fuzz.*' github.com/elliottech/poseidon_crypto/field | grep ^Fuzz); do
36+
go test -fuzz="^${test}$" -fuzztime=30s github.com/elliottech/poseidon_crypto/field
37+
done
3638
- name: Run fuzzy signature tests
3739
run: |
3840
go test -tags=fuzz -fuzz=Fuzz -fuzztime=30s github.com/elliottech/poseidon_crypto/signature/schnorr
@@ -41,6 +43,11 @@ jobs:
4143
for test in $(go test -list='Fuzz.*' github.com/elliottech/poseidon_crypto/int | grep ^Fuzz); do
4244
go test -fuzz="^${test}$" -fuzztime=30s github.com/elliottech/poseidon_crypto/int
4345
done
46+
- name: Run fuzzy ecgfp5 tests
47+
run: |
48+
for test in $(go test -list='Fuzz.*' github.com/elliottech/poseidon_crypto/curve/ecgfp5 | grep ^Fuzz); do
49+
go test -fuzz="^${test}$" -fuzztime=30s github.com/elliottech/poseidon_crypto/curve/ecgfp5
50+
done
4451
4552
lint:
4653
name: Lint

curve/ecgfp5/scalar_field.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@ func ScalarElementFromLittleEndianBytes(data []byte) ECgFp5Scalar {
5151
value[i] = binary.LittleEndian.Uint64(data[i*8:])
5252
}
5353

54-
if !value.IsCanonical() {
55-
panic("trying to deserialize non-canonical bytes")
54+
bigValue := ToNonCanonicalBigInt(value)
55+
if bigValue.Cmp(ORDER) < 0 {
56+
return value
5657
}
5758

58-
return value
59+
return FromNonCanonicalBigInt(bigValue)
5960
}
6061

6162
func (s ECgFp5Scalar) SplitTo4BitLimbs() [80]uint8 {
@@ -163,12 +164,26 @@ func Select(c uint64, a0, a1 ECgFp5Scalar) ECgFp5Scalar {
163164
}
164165

165166
func (s ECgFp5Scalar) Add(rhs ECgFp5Scalar) ECgFp5Scalar {
167+
if !s.IsCanonical() {
168+
panic("Add: first operand 's' must be canonical (< n)")
169+
}
170+
if !rhs.IsCanonical() {
171+
panic("Add: second operand 'rhs' must be canonical (< n)")
172+
}
173+
166174
r0 := s.AddInner(rhs)
167175
r1, c := r0.SubInner(N) // one reduce is enough if s < n and rhs < n
168176
return Select(c, r1, r0)
169177
}
170178

171179
func (s *ECgFp5Scalar) Sub(rhs ECgFp5Scalar) ECgFp5Scalar {
180+
if !s.IsCanonical() {
181+
panic("Sub: first operand 's' must be canonical (< n)")
182+
}
183+
if !rhs.IsCanonical() {
184+
panic("Sub: second operand 'rhs' must be canonical (< n)")
185+
}
186+
172187
r0, c := s.SubInner(rhs)
173188
r1 := r0.AddInner(N) // one add is enough if s < n and rhs < n
174189
return Select(c, r0, r1)
@@ -242,7 +257,7 @@ func FromGfp5(fp5 gFp5.Element) ECgFp5Scalar {
242257
result := new(big.Int)
243258
for i := 4; i >= 0; i-- {
244259
result.Lsh(result, 64)
245-
result.Or(result, new(big.Int).SetUint64(fp5[i].ToCanonicalUint64())) // it always fit to
260+
result.Or(result, new(big.Int).SetUint64(fp5[i].ToCanonicalUint64()))
246261
}
247262

248263
return FromNonCanonicalBigInt(result)

curve/ecgfp5/scalar_field_test.go

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ecgfp5
22

33
import (
4+
"math/big"
45
"testing"
56

67
g "github.com/elliottech/poseidon_crypto/field/goldilocks"
@@ -24,6 +25,40 @@ func TestSerdes(t *testing.T) {
2425
}
2526
}
2627

28+
func TestScalarElementFromLittleEndianBytesReduces(t *testing.T) {
29+
// Create a byte array that represents a scalar larger than the order
30+
bigScalar := new(big.Int).Add(ORDER, big.NewInt(1234567890))
31+
leBytes := bigScalar.Bytes()
32+
s := ScalarElementFromLittleEndianBytes(leBytes)
33+
34+
if ToNonCanonicalBigInt(s).Cmp(ORDER) != -1 {
35+
t.Fatalf("Expected scalar to be reduced modulo order, but got %v", ToNonCanonicalBigInt(s))
36+
}
37+
}
38+
39+
func FuzzSerdes(f *testing.F) {
40+
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})
41+
f.Add(ORDER.Bytes())
42+
43+
f.Fuzz(func(t *testing.T, a []byte) {
44+
// take 40 bytes only
45+
if len(a) > 40 {
46+
a = a[:40]
47+
} else if len(a) < 40 {
48+
// pad with zeros
49+
a = append(a, make([]byte, 40-len(a))...)
50+
}
51+
s := ScalarElementFromLittleEndianBytes(a)
52+
53+
b := s.ToLittleEndianBytes()
54+
ss := ScalarElementFromLittleEndianBytes(b)
55+
56+
if !s.Equals(ss) {
57+
t.Fatalf("Serdes mismatch: %v != %v", s, ss)
58+
}
59+
})
60+
}
61+
2762
func TestSplitTo4LimbBits(t *testing.T) {
2863
scalar := ECgFp5Scalar{
2964
6950590877883398434,
@@ -120,12 +155,31 @@ func TestAddScalar(t *testing.T) {
120155
}
121156
}
122157

158+
func FuzzAddScalar(f *testing.F) {
159+
f.Add([]byte{1, 2, 3, 4}, []byte{5, 6, 7, 8})
160+
f.Add(ORDER.Bytes(), ORDER.Bytes())
161+
162+
f.Fuzz(func(t *testing.T, a []byte, b []byte) {
163+
aBig := new(big.Int).SetBytes(a)
164+
scalar1 := FromNonCanonicalBigInt(aBig)
165+
bBig := new(big.Int).SetBytes(b)
166+
scalar2 := FromNonCanonicalBigInt(bBig)
167+
168+
result := scalar1.Add(scalar2)
169+
resultBig := FromNonCanonicalBigInt(new(big.Int).Add(aBig, bBig))
170+
171+
if !result.Equals(resultBig) {
172+
t.Fatalf("Addition mismatch: %v + %v != %v", scalar1, scalar2, result)
173+
}
174+
})
175+
}
176+
123177
func TestSub(t *testing.T) {
124178
scalar1 := ECgFp5Scalar{1, 2, 0, 0, 0}
125-
scalar2 := ECgFp5Scalar{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}
179+
scalar2 := ECgFp5Scalar{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x0FFFFFFFFFFFFFFF}
126180

127181
result := scalar1.Sub(scalar2)
128-
expectedValues := ECgFp5Scalar{0xe80fd996948bffe3, 0xe8885c39d724a09e, 0x7fffffe6cfb80639, 0x7ffffff100000016, 0x7ffffffd80000007}
182+
expectedValues := ECgFp5Scalar{0xe80fd996948bffe3, 0xe8885c39d724a09e, 0x7fffffe6cfb80639, 0x7ffffff100000016, 8070450521510510599}
129183

130184
for i := 0; i < 5; i++ {
131185
if result[i] != expectedValues[i] {
@@ -134,6 +188,25 @@ func TestSub(t *testing.T) {
134188
}
135189
}
136190

191+
func FuzzSubScalar(f *testing.F) {
192+
f.Add([]byte{1, 2, 3, 4}, []byte{5, 6, 7, 8})
193+
f.Add(ORDER.Bytes(), ORDER.Bytes())
194+
195+
f.Fuzz(func(t *testing.T, a []byte, b []byte) {
196+
aBig := new(big.Int).SetBytes(a)
197+
scalar1 := FromNonCanonicalBigInt(aBig)
198+
bBig := new(big.Int).SetBytes(b)
199+
scalar2 := FromNonCanonicalBigInt(bBig)
200+
201+
result := scalar1.Sub(scalar2)
202+
resultBig := FromNonCanonicalBigInt(new(big.Int).Sub(aBig, bBig))
203+
204+
if !result.Equals(resultBig) {
205+
t.Fatalf("Subtraction mismatch: %v - %v != %v", scalar1, scalar2, result)
206+
}
207+
})
208+
}
209+
137210
func TestSelect(t *testing.T) {
138211
a0 := ECgFp5Scalar{1, 2, 3, 4, 5}
139212
a1 := ECgFp5Scalar{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFD, 0xFFFFFFFFFFFFFFFC, 0xFFFFFFFFFFFFFFFB}
@@ -155,7 +228,7 @@ func TestSelect(t *testing.T) {
155228

156229
func TestMontyMul(t *testing.T) {
157230
scalar1 := ECgFp5Scalar{1, 2, 3, 4, 5}
158-
scalar2 := ECgFp5Scalar{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}
231+
scalar2 := ECgFp5Scalar{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF} // montymul can work with non-canonical inputs
159232

160233
result := scalar1.MontyMul(scalar2)
161234
expectedValues := ECgFp5Scalar{10974894505036100890, 7458803775930281466, 744239893213209819, 3396127080529349464, 5979369289905897562}
@@ -192,6 +265,25 @@ func TestMul(t *testing.T) {
192265
}
193266
}
194267

268+
func FuzzMulScalar(f *testing.F) {
269+
f.Add([]byte{1, 2, 3, 4}, []byte{5, 6, 7, 8})
270+
f.Add(ORDER.Bytes(), ORDER.Bytes())
271+
272+
f.Fuzz(func(t *testing.T, a []byte, b []byte) {
273+
aBig := new(big.Int).SetBytes(a)
274+
scalar1 := FromNonCanonicalBigInt(aBig)
275+
bBig := new(big.Int).SetBytes(b)
276+
scalar2 := FromNonCanonicalBigInt(bBig)
277+
278+
result := scalar1.Mul(scalar2)
279+
resultBig := FromNonCanonicalBigInt(new(big.Int).Mul(aBig, bBig))
280+
281+
if !result.Equals(resultBig) {
282+
t.Fatalf("Multiplication mismatch: %v * %v != %v", scalar1, scalar2, result)
283+
}
284+
})
285+
}
286+
195287
func TestRecodeSigned(t *testing.T) {
196288
var ss [50]int32
197289
scalar := ECgFp5Scalar{

field/field_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ var inputs = []uint64{
7272
9223372036854775798, 9223372036854775799, 9223372036854775800, 9223372036854775801, 9223372036854775802, 9223372036854775803, 9223372036854775804, 9223372036854775805,
7373
9223372036854775806, 9223372036854775807, 9223372036854775808, 9223372036854775809, 9223372036854775810, 9223372036854775811, 9223372036854775812, 9223372036854775813,
7474
9223372036854775814, 9223372036854775815, 9223372036854775816, 9223372036854775817, 18446744069414584311, 18446744069414584312, 18446744069414584313, 18446744069414584314,
75-
18446744069414584315, 18446744069414584316, 18446744069414584317, 18446744069414584318, 18446744069414584319, 18446744069414584320,
75+
18446744069414584315, 18446744069414584316, 18446744069414584317, 18446744069414584318, 18446744069414584319, 18446744069414584320, 18446744069414584321, 18446744069414584323,
76+
math.MaxUint64,
7677
}
7778

7879
func NewBigInt(x uint64) *big.Int {
@@ -427,6 +428,27 @@ func FuzzTestF(f *testing.F) {
427428
})
428429
}
429430

431+
func FuzzEquivalenceF(f *testing.F) {
432+
f.Add(uint64(0))
433+
f.Add(uint64(1))
434+
f.Add(g.ORDER - 1)
435+
f.Add(g.ORDER + 1)
436+
f.Add(uint64(math.MaxUint64))
437+
438+
f.Fuzz(func(t *testing.T, val uint64) {
439+
fVal := g.GoldilocksField(val)
440+
gVal := g.FromUint64(val)
441+
442+
if fVal.ToCanonicalUint64() != gVal.Uint64() {
443+
t.Fatalf("FromUint64: Expected %d to be equal, but got %d and %d", val, fVal.ToCanonicalUint64(), gVal.Uint64())
444+
}
445+
446+
if g.SquareF(fVal).ToCanonicalUint64() != new(g.Element).Square(&gVal).Uint64() {
447+
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())
448+
}
449+
})
450+
}
451+
430452
// Quintic extension tests
431453

432454
func TestQuinticExtensionAddSubMulSquare(t *testing.T) {

signature/schnorr/schnorr_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,14 @@ func TestBytes(t *testing.T) {
192192
if err := Validate(pk.ToLittleEndianBytes(), hashedMsg.ToLittleEndianBytes(), sig2.ToBytes()); err != nil {
193193
t.Fatalf("Signature is invalid")
194194
}
195+
196+
// Works with non-canonical inputs
197+
sig3 := sig2
198+
sig3.S = sig3.S.AddInner(curve.N)
199+
sig3.E = sig3.E.AddInner(curve.N)
200+
if err := Validate(pk.ToLittleEndianBytes(), hashedMsg.ToLittleEndianBytes(), sig3.ToBytes()); err != nil {
201+
t.Fatalf("Signature is invalid")
202+
}
195203
}
196204

197205
func BenchmarkSignatureVerify(b *testing.B) {

0 commit comments

Comments
 (0)