Skip to content

Commit

Permalink
EdDSA Verification Checks (#430)
Browse files Browse the repository at this point in the history
Added `edda.VerifyWithChecks` which checks if the scalars and
points are canonical and ensures the points do not have a small
order.

Refer: RFC8032§5.1.7 and https://eprint.iacr.org/2020/823.pdf

Builds on top of #427 and closes #426 and #311.

Co-authored-by: David Cerezo <[email protected]>
Co-authored-by: Linus Gasser <[email protected]>
  • Loading branch information
3 people authored Aug 6, 2020
1 parent 0115c49 commit 88245de
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 8 deletions.
26 changes: 26 additions & 0 deletions group/edwards25519/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -1444,3 +1444,29 @@ var base = [32][8]preComputedGroupElement{
},
},
}

// Ed25519 "weak keys"
// Copied from https://github.com/jedisct1/libsodium/blob/4744636721d2e420f8bbe2d563f31b1f5e682229/src/libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c#L1130
// We did not consider `prime` and `prime+1` since they would be discarded because of `IsCanonical` checks
// TODO: is there a reference to the SUPERCOP ref10 implementation available?
var weakKeys = [][]byte{
/* 0 (order 4) */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
/* 1 (order 1) */
{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
/* 2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */
{0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4,
0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6,
0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05},
/* 55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */
{0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b,
0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39,
0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a},
/* p-1 (order 2) */
{0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}}
34 changes: 34 additions & 0 deletions group/edwards25519/point.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,37 @@ func (P *point) Mul(s kyber.Scalar, A kyber.Point) kyber.Point {

return P
}

// HasSmallOrder determines whether the group element has small order
//
// Provides resilience against malicious key substitution attacks (M-S-UEO)
// and message bound security (MSB) even for malicious keys
// See paper https://eprint.iacr.org/2020/823.pdf for definitions and theorems
//
// This is the same code as in
// https://github.com/jedisct1/libsodium/blob/4744636721d2e420f8bbe2d563f31b1f5e682229/src/libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c#L1170
func (P *point) HasSmallOrder() bool {
s, err := P.MarshalBinary()
if err != nil {
return false
}

var c [5]byte

for j := 0; j < 31; j++ {
for i := 0; i < 5; i++ {
c[i] |= s[j] ^ weakKeys[i][j]
}
}
for i := 0; i < 5; i++ {
c[i] |= (s[31] & 0x7f) ^ weakKeys[i][31]
}

// Constant time verification if one or more of the c's are zero
var k uint16
for i := 0; i < 5; i++ {
k |= uint16(c[i]) - 1
}

return (k>>8)&1 > 0
}
12 changes: 12 additions & 0 deletions group/edwards25519/point_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package edwards25519

import (
"encoding/hex"
"fmt"
"testing"

Expand All @@ -11,3 +12,14 @@ func TestPoint_Marshal(t *testing.T) {
p := point{}
require.Equal(t, "ed.point", fmt.Sprintf("%s", p.MarshalID()))
}

// TestPoint_HasSmallOrder ensures weakKeys are considered to have
// a small order
func TestPoint_HasSmallOrder(t *testing.T) {
for _, key := range weakKeys {
p := point{}
err := p.UnmarshalBinary(key)
require.Nil(t, err)
require.True(t, p.HasSmallOrder(), fmt.Sprintf("%s should be considered to have a small order", hex.EncodeToString(key)))
}
}
116 changes: 108 additions & 8 deletions sign/eddsa/eddsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ import (
"crypto/sha512"
"errors"
"fmt"
"math/big"

"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/group/edwards25519"
)

var group = new(edwards25519.Curve)

// TODO: maybe export prime and primeOrder from edwards25519/const or allow it to be
// retrieved from the curve?
var prime, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
var primeOrder, _ = new(big.Int).SetString("7237005577332262213973186563042994240857116359379907606001950938285454250989", 10)

// EdDSA is a structure holding the data necessary to make a series of
// EdDSA signatures.
type EdDSA struct {
Expand All @@ -26,6 +32,15 @@ type EdDSA struct {
prefix []byte
}

// edDSAPoint is used to verify signatures
// with checks around canonicality and group order
type edDSAPoint interface {
kyber.Point
// HasSmallOrder checks if the given buffer (in little endian)
// represents a point with a small order
HasSmallOrder() bool
}

// NewEdDSA will return a freshly generated key pair to use for generating
// EdDSA signatures.
func NewEdDSA(stream cipher.Stream) *EdDSA {
Expand Down Expand Up @@ -119,31 +134,50 @@ func (e *EdDSA) Sign(msg []byte) ([]byte, error) {
return sig[:], nil
}

// Verify uses a public key, a message and a signature. It will return nil if
// sig is a valid signature for msg created by key public, or an error otherwise.
func Verify(public kyber.Point, msg, sig []byte) error {
// VerifyWithChecks uses a public key buffer, a message and a signature.
// It will return nil if sig is a valid signature for msg created by
// key public, or an error otherwise. Compared to `Verify`, it performs
// additional checks around the canonicality and ensures the public key
// does not have a small order.
func VerifyWithChecks(pub, msg, sig []byte) error {
if len(sig) != 64 {
return fmt.Errorf("signature length invalid, expect 64 but got %v", len(sig))
}
if !scalarIsCanonical(sig[32:]) {
return fmt.Errorf("signature is not canonical")
}
if !pointIsCanonical(pub) {
return fmt.Errorf("public key is not canonical")
}

if !pointIsCanonical(sig[:32]) {
return fmt.Errorf("R is not canonical")
}
R := group.Point()
if err := R.UnmarshalBinary(sig[:32]); err != nil {
return fmt.Errorf("got R invalid point: %s", err)
}
if R.(edDSAPoint).HasSmallOrder() {
return fmt.Errorf("R has small order")
}

s := group.Scalar()
if err := s.UnmarshalBinary(sig[32:]); err != nil {
return fmt.Errorf("schnorr: s invalid scalar %s", err)
}

// reconstruct h = H(R || Public || Msg)
Pbuff, err := public.MarshalBinary()
if err != nil {
return err
public := group.Point()
if err := public.UnmarshalBinary(pub); err != nil {
return fmt.Errorf("invalid public key: %s", err)
}
if public.(edDSAPoint).HasSmallOrder() {
return fmt.Errorf("public key has small order")
}

// reconstruct h = H(R || Public || Msg)
hash := sha512.New()
_, _ = hash.Write(sig[:32])
_, _ = hash.Write(Pbuff)
_, _ = hash.Write(pub)
_, _ = hash.Write(msg)

h := group.Scalar().SetBytes(hash.Sum(nil))
Expand All @@ -157,3 +191,69 @@ func Verify(public kyber.Point, msg, sig []byte) error {
}
return nil
}

// Verify uses a public key, a message and a signature. It will return nil if
// sig is a valid signature for msg created by key public, or an error otherwise.
func Verify(public kyber.Point, msg, sig []byte) error {
PBuf, err := public.MarshalBinary()
if err != nil {
return fmt.Errorf("error unmarshalling public key: %s", err)
}
return VerifyWithChecks(PBuf, msg, sig)
}

// scalarIsCanonical whether scalar s is in the range 0<=s<L as required by RFC8032, Section 5.1.7.
// Also provides Strong Unforgeability under Chosen Message Attacks (SUF-CMA)
// See paper https://eprint.iacr.org/2020/823.pdf for definitions and theorems
// See https://github.com/jedisct1/libsodium/blob/4744636721d2e420f8bbe2d563f31b1f5e682229/src/libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c#L2568
// for a reference
func scalarIsCanonical(sb []byte) bool {
if len(sb) != 32 {
return false
}

if sb[31]&0xf0 == 0 {
return true
}

L := primeOrder.Bytes()
for i, j := 0, 31; i < j; i, j = i+1, j-1 {
L[i], L[j] = L[j], L[i]
}

var c byte
var n byte = 1

for i := 31; i >= 0; i-- {
// subtraction might lead to an underflow which needs
// to be accounted for in the right shift
c |= byte((uint16(sb[i])-uint16(L[i]))>>8) & n
n &= byte((uint16(sb[i]) ^ uint16(L[i]) - 1) >> 8)
}

return c != 0
}

// pointIsCanonical determines whether the group element is canonical
//
// Checks whether group element s is less than p, according to RFC8032§5.1.3.1
// https://tools.ietf.org/html/rfc8032#section-5.1.3
//
// Taken from
// https://github.com/jedisct1/libsodium/blob/4744636721d2e420f8bbe2d563f31b1f5e682229/src/libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c#L1113
func pointIsCanonical(s []byte) bool {
if len(s) != 32 {
return false
}

c := (s[31] & 0x7f) ^ 0x7f
for i := 30; i > 0; i-- {
c |= s[i] ^ 0xff
}

// subtraction might underflow
c = byte((uint16(c) - 1) >> 8)
d := byte((0xed - 1 - uint16(s[0])) >> 8)

return 1-(c&d&1) == 1
}
Loading

0 comments on commit 88245de

Please sign in to comment.