Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filippo version of Ed25519 #462

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*~
moc_*
ext/
.idea/
dissent
docs/html
test.log
Expand Down
1 change: 0 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@

To manually browse the doc start from [sidebar.md](sidebar.md). Otherwise visit [https://dedis.github.io/kyber/](https://dedis.github.io/kyber/)
2 changes: 1 addition & 1 deletion docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

The kyber go library posses several functions for cryptographic operations on
different cureves like ed25519 and the functions can be used to perform various
operations on points, scalars and groups in linear or varaible time and can be
operations on points, scalars and groups in linear or variable time and can be
used to implement the same.
2 changes: 1 addition & 1 deletion docs/point.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ group. It returns a `true/false` indicating wether the Points are equal or not.
| Output | - `Point` : Caller Point with value equal to the neutral identity element |

This function sets the receiver object to a neutral identity element, depending
upon the suite taken into consideration.
upon the suite taken into consideration.

### Base()

Expand Down
2 changes: 1 addition & 1 deletion docs/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
- [Point](point.md)
- [Scalar](scalar.md)
- [Marshaling](marshalling.md)
- [XOF](xof.md)
- [XOF](xof.md)
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module go.dedis.ch/kyber/v3

require (
filippo.io/edwards25519 v1.0.0
github.com/stretchr/testify v1.3.0
go.dedis.ch/fixbuf v1.0.3
go.dedis.ch/protobuf v1.0.11
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
16 changes: 16 additions & 0 deletions group/filippo_ed25519/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package filippo_ed25519

import (
filippo_ed25519 "filippo.io/edwards25519"
"math/big"
)

var primeOrder, _ = new(big.Int).SetString("7237005577332262213973186563042994240857116359379907606001950938285454250989", 10)
var cofactor = new(big.Int).SetInt64(8)
var fullOrder = new(big.Int).Mul(primeOrder, cofactor)

var filippoPrimeOrderScalar = setBigInt(primeOrder)
var filippoCofactorScalar = setBigInt(cofactor)
var filippoNullPoint = Point{filippo_ed25519.NewIdentityPoint()}

var marshalPointID = [16]byte{'f', 'i', 'l', 'i', 'p', 'p', 'o', '_', 'e', 'd', '.', 'p', 'o', 'i', 'n', 't'}
80 changes: 80 additions & 0 deletions group/filippo_ed25519/curve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package filippo_ed25519

import (
"crypto/cipher"
"crypto/sha512"
filippo_ed25519 "filippo.io/edwards25519"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/util/random"
)

// Curve represents the Ed25519 group.
// There are no parameters and no initialization is required
// because it supports only this one specific curve.
type Curve struct {
}

// Return the name of the curve, "Ed25519".
func (c *Curve) String() string {
return "Ed25519"
}

// ScalarLen returns 32, the size in bytes of an encoded Scalar
// for the Ed25519 curve.
func (c *Curve) ScalarLen() int {
return 32
}

// Scalar creates a new Scalar for the prime-order subgroup of the Ed25519 curve.
// The scalars in this package implement kyber.Scalar's SetBytes
// method, interpreting the bytes as a little-endian integer, in order to remain
// compatible with other Ed25519 implementations, and with the standard implementation
// of the EdDSA signature.
func (c *Curve) Scalar() kyber.Scalar {
return &Scalar{new(filippo_ed25519.Scalar)}
}

// PointLen returns 32, the size in bytes of an encoded Point on the Ed25519 curve.
func (c *Curve) PointLen() int {
return 32
}

// Point creates a new Point on the Ed25519 curve.
func (c *Curve) Point() kyber.Point {
P := Point{new(filippo_ed25519.Point)}
return &P
}

// NewKeyAndSeedWithInput returns a formatted Ed25519 key (avoid subgroup attack by
// requiring it to be a multiple of 8). It also returns the input and the digest used
Copy link
Contributor

Choose a reason for hiding this comment

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

requiring it to be a multiple of 8 where is this check being done ?
If I understand correctly, lines 53-54 do not seem to guarantee a multiple of 8, counter example: 0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I am correct then zero would also be a valid output for what we are trying to do.

Copy link
Contributor

Choose a reason for hiding this comment

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

@si-co do you agree ?

Copy link

@si-co si-co Mar 13, 2023

Choose a reason for hiding this comment

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

This function seems to have different problems:

  • It is not documented.
  • I don't understand why it uses sha512 when only 32 bytes are needed;.
  • I seems to try to implement clamping, but it is wrong.
  • It uses digest[:32] to initialize the scalar, but returns digest[32:], which is not the digest used to generate the key.

IMHO the best way to implement this function would be to use SetBytesWithClamping with sha256 to hash the buffer, assuming that's what the function is trying to do. If @parinayc20 adds the appropriate documentation we can continue the discussion.

// to generate the key.
func (c *Curve) NewKeyAndSeedWithInput(buffer []byte) (kyber.Scalar, []byte, []byte) {
digest := sha512.Sum512(buffer[:])
digest[0] &= 0xf8
digest[31] &= 0xf

// In here the 31st byte has been done AND 16 just to bring the secret key chosen below the group order i.e. l
// in which the highest priority byte is 16. Doing the normal procedure of unsetting the highest priority bit and
// setting the highest priority bit leads to errors when the secret is passed to the filippo library which expects
// all the scalars to be below l.

secret := c.Scalar().(*Scalar)
secret.SetBytes(digest[:32])
return secret, buffer, digest[32:]
}

// NewKeyAndSeed returns a formatted Ed25519 key (avoid subgroup attack by requiring
// it to be a multiple of 8). It also returns the seed and the input used to generate
// the key.
func (c *Curve) NewKeyAndSeed(stream cipher.Stream) (kyber.Scalar, []byte, []byte) {
var buffer [32]byte
random.Bytes(buffer[:], stream)
return c.NewKeyAndSeedWithInput(buffer[:])
}

// NewKey returns a formatted Ed25519 key (avoiding subgroup attack by requiring
// it to be a multiple of 8). NewKey implements the kyber/util/key.Generator interface.
func (c *Curve) NewKey(stream cipher.Stream) kyber.Scalar {
secret, _, _ := c.NewKeyAndSeed(stream)
return secret
}
45 changes: 45 additions & 0 deletions group/filippo_ed25519/curve_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package filippo_ed25519

import (
"github.com/stretchr/testify/assert"
"go.dedis.ch/kyber/v3/util/test"
"math"
"testing"
)

var tSuite = NewBlakeSHA256FilippoEd25519()
var groupBench = test.NewGroupBench(tSuite)

func TestSuite(t *testing.T) { test.SuiteTest(t, tSuite) }

func TestCurve_NewKey(t *testing.T) {
group := Curve{}
stream := tSuite.RandomStream()

for i := 0.0; i < math.Pow(10, 6); i++ {
s := group.NewKey(stream).(*Scalar)

// little-endian check of a multiple of 8
b, _ := s.MarshalBinary()
assert.Equal(t, uint8(0), b[0]&7)
}
}

func BenchmarkScalarAdd(b *testing.B) { groupBench.ScalarAdd(b.N) }
func BenchmarkScalarSub(b *testing.B) { groupBench.ScalarSub(b.N) }
func BenchmarkScalarNeg(b *testing.B) { groupBench.ScalarNeg(b.N) }
func BenchmarkScalarMul(b *testing.B) { groupBench.ScalarMul(b.N) }
func BenchmarkScalarDiv(b *testing.B) { groupBench.ScalarDiv(b.N) }
func BenchmarkScalarInv(b *testing.B) { groupBench.ScalarInv(b.N) }
func BenchmarkScalarPick(b *testing.B) { groupBench.ScalarPick(b.N) }
func BenchmarkScalarEncode(b *testing.B) { groupBench.ScalarEncode(b.N) }
func BenchmarkScalarDecode(b *testing.B) { groupBench.ScalarDecode(b.N) }

func BenchmarkPointAdd(b *testing.B) { groupBench.PointAdd(b.N) }
func BenchmarkPointSub(b *testing.B) { groupBench.PointSub(b.N) }
func BenchmarkPointNeg(b *testing.B) { groupBench.PointNeg(b.N) }
func BenchmarkPointMul(b *testing.B) { groupBench.PointMul(b.N) }
func BenchmarkPointBaseMul(b *testing.B) { groupBench.PointBaseMul(b.N) }
func BenchmarkPointPick(b *testing.B) { groupBench.PointPick(b.N) }
func BenchmarkPointEncode(b *testing.B) { groupBench.PointEncode(b.N) }
func BenchmarkPointDecode(b *testing.B) { groupBench.PointDecode(b.N) }
198 changes: 198 additions & 0 deletions group/filippo_ed25519/point.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package filippo_ed25519

import (
"crypto/cipher"
"encoding/hex"
"errors"
filippo_ed25519 "filippo.io/edwards25519"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/group/internal/marshalling"
"io"
)

type Point struct {
point *filippo_ed25519.Point
}

func (p *Point) Equal(s2 kyber.Point) bool {
return p.point.Equal(s2.(*Point).point) == 1
}

func (p *Point) Null() kyber.Point {
p.point = filippo_ed25519.NewIdentityPoint()
return p
}

func (p *Point) Base() kyber.Point {
p.point = filippo_ed25519.NewGeneratorPoint()
return p
}

func (p *Point) Pick(rand cipher.Stream) kyber.Point {
return p.Embed(nil, rand)
}

func (p *Point) Set(a kyber.Point) kyber.Point {
if p.point == nil {
p.point = new(filippo_ed25519.Point)
}
p.point.Set(a.(*Point).point)
return p
}

func (p *Point) Clone() kyber.Point {
p2 := new(Point)
p2.point = new(filippo_ed25519.Point)
p2.point.Set(p.point)
return p2
}

func (p *Point) Add(a, b kyber.Point) kyber.Point {
if p.point == nil {
p.point = new(filippo_ed25519.Point)
}
p.point.Add(a.(*Point).point, b.(*Point).point)
return p
}

func (p *Point) Sub(a, b kyber.Point) kyber.Point {
if p.point == nil {
p.point = new(filippo_ed25519.Point)
}
p.point.Subtract(a.(*Point).point, b.(*Point).point)
return p
}

func (p *Point) Neg(a kyber.Point) kyber.Point {
if p.point == nil {
p.point = new(filippo_ed25519.Point)
}
p.point.Negate(a.(*Point).point)
return p
}

func (p *Point) Mul(a kyber.Scalar, b kyber.Point) kyber.Point {
if p == nil || p.point == nil {
p.point = new(filippo_ed25519.Point)
}
if b == nil || b.(*Point).point == nil {
p.point = p.point.ScalarBaseMult(a.(*Scalar).scalar)
} else {
p.point.ScalarMult(a.(*Scalar).scalar, b.(*Point).point)
}
return p
}

func (p *Point) EmbedLen() int {
// Reserve the most-significant 8 bits for pseudo-randomness.
// Reserve the least-significant 8 bits for embedded data length.
// (Hopefully it's unlikely we'll need >=2048-bit curves soon.)
return (255 - 8 - 8) / 8
}

func (p *Point) Embed(data []byte, rand cipher.Stream) kyber.Point {

// How many bytes to embed?
dl := p.EmbedLen()
if dl > len(data) {
dl = len(data)
}

p.point = new(filippo_ed25519.Point)

// Github issue raised for the problem of the infinite loop

for {
// Pick a random point, with optional embedded data
var b [32]byte
rand.XORKeyStream(b[:], b[:])
if data != nil {
b[0] = byte(dl) // Encode length in low 8 bits
copy(b[1:1+dl], data) // Copy in data to embed
}

_, err := p.point.SetBytes(b[:])
if err != nil {
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

it seems this would produce an infinite loop with a maliciously crafted data input, as:

  • SetBytes returns an error if b[:] does not represent a valid point on the curve
  • b[:] is crafted by copying bytes from data and data's length (or a constant, p.EmbedLen()).
  • on failure, we repeat the same deterministic operations

}

// If we're using the full group,
// we just need any point on the curve, so we're done.
// if c.full {
// return P,data[dl:]
// }
Comment on lines +119 to +123
Copy link
Contributor

Choose a reason for hiding this comment

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

commented out code


// We're using the prime-order subgroup,
// so we need to make sure the point is in that subencoding.
// If we're not trying to embed data,
// we can convert our point into one in the subgroup
// simply by multiplying it by the cofactor.

if data == nil {
p.Mul(filippoCofactorScalar, p) // multiply by cofactor
if p.Equal(&filippoNullPoint) {
continue // unlucky; try again
}
return p // success
}

// Since we need the point's y-coordinate to hold our data,
// we must simply check if the point is in the subgroup
// and retry point generation until it is.
var Q Point
Q.Mul(filippoPrimeOrderScalar, p)
if Q.Equal(&filippoNullPoint) {
return p // success
}
// Keep trying...
}
}

func (p *Point) Data() ([]byte, error) {
if p.point == nil {
return nil, errors.New("point not initialized")
}

b := p.point.Bytes()
dl := int(b[0])
if dl > p.EmbedLen() {
return nil, errors.New("invalid embedded data length")
}
return b[1 : 1+dl], nil
}

func (p *Point) MarshalSize() int {
return 32
}

func (p *Point) String() string {
b := p.point.Bytes()
return hex.EncodeToString(b)
}

func (p *Point) MarshalBinary() ([]byte, error) {
if p.point == nil {
return nil, errors.New("point not initialized")
}
return p.point.Bytes(), nil
}

func (p *Point) MarshalID() []byte {
return marshalPointID[:]
}

func (p *Point) UnmarshalBinary(b []byte) error {
if p.point == nil {
p.point = new(filippo_ed25519.Point)
}
_, err := p.point.SetBytes(b)
return err
}

func (p *Point) MarshalTo(w io.Writer) (int, error) {
return marshalling.PointMarshalTo(p, w)
}

func (p *Point) UnmarshalFrom(r io.Reader) (int, error) {
return marshalling.PointUnmarshalFrom(p, r)
}
Loading