Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ jobs:
- name: Run fuzzy field tests
run: |
go test -tags=fuzz -fuzz=Fuzz -fuzztime=30s github.com/elliottech/poseidon_crypto/field
- name: Run fuzzy signature tests
run: |
go test -tags=fuzz -fuzz=Fuzz -fuzztime=30s github.com/elliottech/poseidon_crypto/signature/schnorr
- name: Run fuzzy int tests
run: |
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

lint:
name: Lint
Expand Down
143 changes: 142 additions & 1 deletion int/int_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package int

import "testing"
import (
"math"
"math/big"
"testing"
)

func TestRecodeSigned5_161(t *testing.T) {
scalar1 := Signed161{
Expand All @@ -18,3 +22,140 @@ func TestRecodeSigned5_161(t *testing.T) {
}
}
}

func FuzzTestAdd(f *testing.F) {
f.Add(uint64(0), uint64(0), uint64(0), uint64(0))
f.Add(uint64(math.MaxUint64), uint64(1), uint64(1), uint64(0))
f.Add(uint64(math.MaxUint64), uint64(0), uint64(1), uint64(0))
f.Add(uint64(math.MaxUint64), uint64(math.MaxUint64), uint64(math.MaxUint64), uint64(math.MaxUint64))

f.Fuzz(func(t *testing.T, a, b, c, d uint64) {
i128a := UInt128{Hi: a, Lo: b}
i128b := UInt128{Hi: c, Lo: d}
result := AddUInt128(i128a, i128b)

expected := new(big.Int).Add(new(big.Int).SetBits([]big.Word{big.Word(b), big.Word(a)}), new(big.Int).SetBits([]big.Word{big.Word(d), big.Word(c)}))
expectedReduced := new(big.Int).And(expected, new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), big.NewInt(1)))

if !equalsBigInt(result, expectedReduced) {
t.Errorf("Add(%d, %d): got %v, want %v",
a, b, result.ToBigInt(), expected)
}

i128ab := AddUint64(a, b)
expected = new(big.Int).Add(new(big.Int).SetUint64(a), new(big.Int).SetUint64(b))
if !equalsBigInt(i128ab, expected) {
t.Errorf("AddUint64(%d, %d): got %v, want %v",
a, b, i128ab.ToBigInt(), expected)
}
})
}

func FuzzTestAddUInt64(f *testing.F) {
f.Add(uint64(0), uint64(0), uint64(0))
f.Add(uint64(math.MaxUint64), uint64(1), uint64(1))
f.Add(uint64(math.MaxUint64), uint64(0), uint64(0))
f.Add(uint64(math.MaxUint64), uint64(math.MaxUint64), uint64(math.MaxUint64))

f.Fuzz(func(t *testing.T, hi, lo uint64, b uint64) {
i128 := UInt128{Hi: hi, Lo: lo}
result := AddUint128AndUint64(i128, b)

big128 := i128.ToBigInt()
expected := new(big.Int).Add(big128, new(big.Int).SetUint64(b))

if fitsInInt128(expected) {
if !equalsBigInt(result, expected) {
t.Errorf("AddInt64({%d, %d}, %d): got %v, want %v",
hi, lo, b, result.ToBigInt(), expected)
}
} else {
expectedReduced := new(big.Int).And(expected, new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), big.NewInt(1)))
if !equalsBigInt(result, expectedReduced) {
t.Errorf("AddInt64({%d, %d}, %d): got %v, want %v (reduced)",
hi, lo, b, result.ToBigInt(), expectedReduced)
}
}
})
}

func FuzzTestMulUInt64(f *testing.F) {
f.Add(uint64(0), uint64(0))
f.Add(uint64(math.MaxUint64), uint64(1))
f.Add(uint64(math.MaxUint64), uint64(0))
f.Add(uint64(math.MaxUint64), uint64(math.MaxUint64))

f.Fuzz(func(t *testing.T, a, b uint64) {
result := MulUInt64(a, b)
expected := new(big.Int).Mul(new(big.Int).SetUint64(a), new(big.Int).SetUint64(b))

if fitsInInt128(expected) {
if !equalsBigInt(result, expected) {
t.Errorf("MulInt64(%d, %d): got %v, want %v",
a, b, result.ToBigInt(), expected)
}
}
})
}

func FuzzTestMulUint128AndUint64(f *testing.F) {
f.Add(uint64(0), uint64(0), uint64(0))
f.Add(uint64(math.MaxUint64), uint64(1), uint64(1))
f.Add(uint64(math.MaxUint64), uint64(0), uint64(0))
f.Add(uint64(math.MaxUint64), uint64(math.MaxUint64), uint64(math.MaxUint64))

f.Fuzz(func(t *testing.T, hi, lo uint64, b uint64) {
i128 := UInt128{Hi: hi, Lo: lo}
result := MulUint128AndUint64(i128, b)

big128 := i128.ToBigInt()
expected := new(big.Int).Mul(big128, new(big.Int).SetUint64(b))

if fitsInInt128(expected) {
if !equalsBigInt(result, expected) {
t.Errorf("MulUint128AndUint64({%d, %d}, %d): got %v, want %v",
hi, lo, b, result.ToBigInt(), expected)
}
} else {
expectedReduced := new(big.Int).And(expected, new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), big.NewInt(1)))
if !equalsBigInt(result, expectedReduced) {
t.Errorf("MulUint128AndUint64({%d, %d}, %d): got %v, want %v (reduced)",
hi, lo, b, result.ToBigInt(), expectedReduced)
}
}
})
}

func FuzzTestSubUint128AndUint64(f *testing.F) {
f.Add(uint64(0), uint64(0), uint64(0))
f.Add(uint64(1), uint64(1), uint64(0))
f.Add(uint64(0), uint64(1), uint64(0))
f.Add(uint64(math.MaxUint64), uint64(math.MaxUint64), uint64(math.MaxUint64))

f.Fuzz(func(t *testing.T, hi, lo uint64, b uint64) {
i128 := UInt128{Hi: hi, Lo: lo}
result := SubUint128AndUint64(i128, b)

expected := new(big.Int).Sub(new(big.Int).SetBits([]big.Word{big.Word(lo), big.Word(hi)}), new(big.Int).SetUint64(b))
if expected.Sign() < 0 {
expected.Add(expected, new(big.Int).Lsh(big.NewInt(1), 128))
}

if !equalsBigInt(result, expected) {
t.Errorf("SubUint64({%d, %d}, %d): got %v, want %v",
hi, lo, b, result.ToBigInt(), expected)
}
})
}

func equalsBigInt(a UInt128, b *big.Int) bool {
return a.ToBigInt().Cmp(b) == 0
}

func fitsInInt128(b *big.Int) bool {
max := new(big.Int).Lsh(big.NewInt(1), 127)
max.Sub(max, big.NewInt(1)) // (2^127) - 1
min := new(big.Int).Lsh(big.NewInt(-1), 127) // -(2^127)

return b.Cmp(min) >= 0 && b.Cmp(max) <= 0
}
12 changes: 11 additions & 1 deletion int/uint128.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package int

import "math/bits"
import (
"math/big"
"math/bits"
)

type UInt128 struct {
Hi, Lo uint64
}

func (u UInt128) ToBigInt() *big.Int {
bi := new(big.Int).SetUint64(u.Hi)
bi.Lsh(bi, 64)
bi.Add(bi, new(big.Int).SetUint64(u.Lo))
return bi
}

func UInt128FromUint64(v uint64) UInt128 {
return UInt128{Hi: 0, Lo: v}
}
Expand Down
24 changes: 24 additions & 0 deletions signature/schnorr/schnorr_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package signature

import (
"encoding/binary"
"math/big"
"testing"

curve "github.com/elliottech/poseidon_crypto/curve/ecgfp5"
Expand All @@ -24,6 +26,28 @@ func TestSchnorrSignAndVerify(t *testing.T) {
}
}

func FuzzTestSchnorrSignAndVerify(f *testing.F) {
f.Add([]byte{1, 2, 3, 4}, []byte{5, 6, 7, 8})

f.Fuzz(func(t *testing.T, a, b []byte) {
scalar := curve.FromNonCanonicalBigInt(new(big.Int).SetBytes(a))

msgBytes := make([]g.GoldilocksField, 0)
for i := 0; i < len(b); i += 8 {
var chunk [8]byte
copy(chunk[:], b[i:min(i+8, len(b))])
msgBytes = append(msgBytes, g.GoldilocksField(binary.LittleEndian.Uint64(chunk[:])))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would g.GoldilocksField(binary.LittleEndian.Uint64(chunk[:])) fail because of non-canonical form?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no goldilocks field can work in non-canonical form too

}
hashedMsg := p2.HashToQuinticExtension(msgBytes)

sig := SchnorrSignHashedMessage(hashedMsg, scalar)
pk := SchnorrPkFromSk(scalar)
if !IsSchnorrSignatureValid(pk, hashedMsg, sig) {
t.Fatalf("Signature is invalid")
}
})
}

func TestComparativeSchnorrSignAndVerify(t *testing.T) {
sks := []curve.ECgFp5Scalar{
curve.ECgFp5Scalar{
Expand Down