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

G2hash #428

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions pairing/bn256/gfp.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ func (e *gfP) Set(f *gfP) {
e[3] = f[3]
}

func (e *gfP) Equals(f *gfP) bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not constant time, is that ok? pointG1 and pointG2 are careful to be constant time:

func (p *pointG2) Equal(q kyber.Point) bool {
	x, _ := p.MarshalBinary()
	y, _ := q.MarshalBinary()
	return subtle.ConstantTimeCompare(x, y) == 1
}

Likewise for the other Equals as well.

Copy link
Author

@AkshayaMani AkshayaMani Jul 25, 2020

Choose a reason for hiding this comment

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

Ian writes: Note that Hash() is not constant time (either the existing G1 implementation or our new G2 one), but fair enough that Equals may as well be. I've fixed it in both gfP and gfP2 (in the latest commit 78796c8).

return e[0] == f[0] && e[1] == f[1] && e[2] == f[2] && e[3] == f[3]
}

func (e *gfP) Invert(f *gfP) {
bits := [4]uint64{0x185cac6c5e089665, 0xee5b88d120b5b59e, 0xaa6fecb86184dc21, 0x8fb501e34aa387f9}

Expand Down
83 changes: 83 additions & 0 deletions pairing/bn256/gfp2.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ type gfP2 struct {
x, y gfP // value is xi+y.
}

func gfP2Encode(in *gfP2) *gfP2 {
out := &gfP2{}
montEncode(&out.x, &in.x)
montEncode(&out.y, &in.y)
return out
}

func gfP2Decode(in *gfP2) *gfP2 {
out := &gfP2{}
montDecode(&out.x, &in.x)
Expand Down Expand Up @@ -157,3 +164,79 @@ func (e *gfP2) Clone() gfP2 {

return n
}

// Compute the norm in gfP of the element a in gfP2
func (e *gfP) Norm(a *gfP2) *gfP {
t1, t2 := &gfP{}, &gfP{}
gfpMul(t1, &a.x, &a.x)
gfpMul(t2, &a.y, &a.y)
gfpAdd(t1, t1, t2)

e.Set(t1)
return e
}

// Compute a to the power of r, where r is expressed as a [4]uint64
func (e *gfP2) Power(a *gfP2, r [4]uint64) *gfP2 {
sum := &gfP2{gfP{0}, *newGFp(1)}
power := &gfP2{}
power.Set(a)
for word := 0; word < 4; word++ {
for bit := uint(0); bit < 64; bit++ {
if (r[word]>>bit)&1 == 1 {
sum.Mul(sum, power)
}
power.Mul(power, power)
}
}
e.Set(sum)
return e
}

func (e *gfP2) Equals(a *gfP2) bool {
return e.x.Equals(&a.x) && e.y.Equals(&a.y)
}

// Compute the square root of the element a in gfP2
// Uses Algorithm 9 of https://eprint.iacr.org/2012/685.pdf
// In our case, n=1, and so q=p
// Returns nil if a is not a quadratic residue
func (e *gfP2) Sqrt(a *gfP2) *gfP2 {
if a.IsZero() {
e.SetZero()
return e
}
// (p-3)/4
pm34 := [4]uint64{0x86172b1b17822599, 0x7b96e234482d6d67,
0x6a9bfb2e18613708, 0x23ed4078d2a8e1fe}
// (p-1)/2
pm12 := [4]uint64{0x0c2e56362f044b33, 0xf72dc468905adacf,
0xd537f65c30c26e10, 0x47da80f1a551c3fc}

a1, x0, alpha := &gfP2{}, &gfP2{}, &gfP2{}
norm := &gfP{}
minus1gfP := newGFp(-1)
minus1 := &gfP2{gfP{0}, *newGFp(-1)}
one := &gfP2{gfP{0}, *newGFp(1)}

a1.Power(a, pm34)
x0.Mul(a1, a)
alpha.Mul(a1, x0)
norm.Norm(alpha)
if norm.Equals(minus1gfP) {
return nil
}
if alpha.Equals(minus1) {
// Set e = i * x0
copy(e.x[:], x0.y[:])
gfpNeg(&e.y, &x0.x)
} else {
b := &gfP2{}
// Compute b = (1+alpha)^((p-1)/2)
b.Add(alpha, one)
b.Power(b, pm12)
// e = b * x0
e.Mul(b, x0)
}
return e
}
95 changes: 95 additions & 0 deletions pairing/bn256/gfp2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package bn256

import (
"github.com/stretchr/testify/require"
"testing"
)

func TestGfP2Norm(t *testing.T) {
p := &gfP2{*newGFp(7), *newGFp(18)}
expectednorm := *newGFp(7*7 + 18*18)
var norm gfP
norm.Norm(p)
if norm != expectednorm {
t.Error("Norm mismatch")
}
}

func TestGfP2Power(t *testing.T) {
a := &gfP2{gfP{2}, gfP{3}}
ap := &gfP2{}
a = gfP2Encode(a)

// The characteristic p
p := [4]uint64{0x185cac6c5e089667, 0xee5b88d120b5b59e, 0xaa6fecb86184dc21, 0x8fb501e34aa387f9}

// Verify that a^p is the conjugate of a; that is, that a^p + a = 2 * Re(a)
ap.Power(a, p)
ap.Add(ap, a)
apd := gfP2Decode(ap)
require.Equal(t, apd, &gfP2{gfP{0}, gfP{6}})

// Two arbitrary exponents
r := [4]uint64{0x123456789abcdef0, 0x2468ace013579bdf, 0x89abcdef01234567, 0x13579bdf2468ace0}
s := [4]uint64{0x1122334455667788, 0x99aabbccddeeff00, 0x159d26ae37bf48c0, 0x0c84fb73ea62d951}

// Their sum r+s
rps := [4]uint64{0x235689bcf0235678, 0xbe1368acf1469adf, 0x9f48f49d38e28e27, 0x1fdc97530ecb8631}

// Verify that (a^r)*(a^s) = a^(r+s)
ar := &gfP2{}
as := &gfP2{}
arps := &gfP2{}
aras := &gfP2{}
ar.Power(a, r)
as.Power(a, s)
arps.Power(a, rps)
aras.Mul(ar, as)
require.Equal(t, aras, arps)

// Verify that (a^r)^s = (a^s)^r
ars := &gfP2{}
asr := &gfP2{}
ars.Power(ar, s)
asr.Power(as, r)
require.Equal(t, ars, asr)
}

func testsqrt(t *testing.T, a *gfP2) {
// A quadratic nonresidue
nonresidue := &gfP2{*newGFp(1), *newGFp(2)}
s := &gfP2{}

r := s.Sqrt(a)
if r == nil {
// Not a quadratic residue, but then nonresidue*a will be one
a.Mul(a, nonresidue)
r = s.Sqrt(a)
}

if r == nil {
t.Error("Neither a nor nonresidue*a is a quadratic residue")
} else {
s2 := &gfP2{}
s2.Mul(s, s)
if !s2.Equals(a) {
t.Error("Sqrt mismatch")
}
}
}

func TestGfP2Sqrt(t *testing.T) {
// Simple cases
testsqrt(t, &gfP2{*newGFp(0), *newGFp(0)})
testsqrt(t, &gfP2{*newGFp(0), *newGFp(1)})
testsqrt(t, &gfP2{*newGFp(1), *newGFp(0)})
testsqrt(t, &gfP2{*newGFp(0), *newGFp(-1)})

// Two tests of the alpha = -1 branch
testsqrt(t, &gfP2{*newGFp(0), *newGFp(-1)})
testsqrt(t, &gfP2{*newGFp(0), *newGFp(13)})

// Some other arbitrary tests, including a nonresidue
testsqrt(t, &gfP2{*newGFp(2), *newGFp(-41)})
testsqrt(t, &gfP2{*newGFp(2), *newGFp(-10)})
}
58 changes: 58 additions & 0 deletions pairing/bn256/point.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,64 @@ func (p *pointG2) String() string {
return "bn256.G2:" + p.g.String()
}

func (p *pointG2) Hash(m []byte) kyber.Point {
// Hash the data to get the "real" and "imaginary" parts of the
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this an existing standard that you are implementing, in which case, please mention it. Or are you syncing up to an existing implementation someplace else, in which case please reference it and add test vectors to the unit test that come from the other implementation.

Or is this your own invention?

It all looks reasonable to me, just trying to understand the context a bit more.

Copy link
Author

@AkshayaMani AkshayaMani Jul 25, 2020

Choose a reason for hiding this comment

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

Ian writes: I do not know of another implementation or standard for hashing to G2 on this curve. As above, the choice of algorithm follows closely the existing hash-to-G1, but the choice of exactly how to hash the input data to the initial x coordinate is somewhat abritrary. That's why the unit tests that check explicit output values are marked as regression tests: they're to test future implementations against this implementation, not this implementation against some external implementation or standard.

// initial attempt at an x coordinate. The real component
// becomes SHA256("r" || m) and the imaginary component becomes
// SHA256("i" || m).
x := &gfP2{*newGFp(0), *newGFp(0)}
realh := sha256.New()
realh.Write([]byte("r"))
realh.Write(m)
realhash := realh.Sum(nil)
imagh := sha256.New()
imagh.Write([]byte("i"))
imagh.Write(m)
imaghash := imagh.Sum(nil)
x.x.Unmarshal(imaghash[:])
x.y.Unmarshal(realhash[:])
montEncode(&x.x, &x.x)
montEncode(&x.y, &x.y)

// See if there's a corresponding y coordinate for this x. If not,
// increment (the "real" part of) x and keep trying.
y := &gfP2{}
one := &gfP2{*newGFp(0), *newGFp(1)}

for {
Copy link
Contributor

Choose a reason for hiding this comment

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

This same algorithm is already implemented in hashToPoint(), on line 226. Can you explain why this second implementation is needed for g2?

hashToPoint says, "ideally we want to do this using gfP, but gfP doesn't have a ModSqrt function". In this PR you are adding a Sort to gfp2. I don't understand why g1 and g2 need different approaches to the same problem.

Copy link
Contributor

Choose a reason for hiding this comment

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

By the way: I am not advocating that things should be all perfectly cleaned up and every single line of duplicate code removed. And the behaviour of hash to a G1 point must not be changed, or else the existing BLS signatures would all stop validating.

So if the explanation is, "we knew about hashToPoint and we know we can't touch it, but for X reason G2 needs the same thing but slightly different", then that's ok with me.

Copy link
Author

@AkshayaMani AkshayaMani Jul 25, 2020

Choose a reason for hiding this comment

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

Ian writes:

The core of the hash algorithm is indeed (intentionally) the same for G1 and G2, but there are a few differences:

  • They operate over different fields (gfP vs gfP2) and groups (G1 vs the twist group that contains G2), of course
  • The G2 version has to multiply by the G2 cofactor at the end
  • The initial x coordinate (in gfP for G1 and in gfP2 for G2) is a hash of the input data, but twice as much hash output has to be produced for G2, since gfP2 elements are twice as large

So I suppose it's possible that someone could implement Sqrt for gfP, and then make common interfaces for both the fields and the groups that unify the differences in the core of the algorithm, but still initialize the initial x coordinate differently, and multiply by the cofactor only in the G2 version? That would certainly clean up the existing G1Hash() function that has to convert everything to BigInt and back because of a lack of Sqrt in gfP, but I don't think that should be part of this PR.

// Compute y2 = x^3 + 3/xi
y2 := &gfP2{}
y2.Mul(x, x)
y2.Mul(y2, x)
y2.Add(y2, twistB)

// Take the square root, if possible
if y.Sqrt(y2) != nil {
break
}

// Add 1 to the "real" part of x
x.Add(x, one)
}

// Now we have a point on the twist curve
rawhashpoint := &twistPoint{*x, *y,
gfP2{*newGFp(0), *newGFp(1)},
gfP2{*newGFp(0), *newGFp(1)}}

// Multiply by the cofactor to get a point in G2
cofactor := bigFromBase10("65000549695646603732796438742359905743080310161342220753873227084684201343597")
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this belongs in constants.go?

Copy link
Author

@AkshayaMani AkshayaMani Jul 25, 2020

Choose a reason for hiding this comment

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

Ian writes: Makes sense; done (in the latest commit 77e41db).

rawhashpoint.Mul(rawhashpoint, cofactor)

// Create a pointG2 out of the twistPoint
if p.g == nil {
p.g = new(twistPoint)
}
p.g.Set(rawhashpoint)

return p
}

type pointGT struct {
g *gfP12
}
Expand Down
60 changes: 60 additions & 0 deletions pairing/bn256/point_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,63 @@ func TestPointG1_HashToPoint(t *testing.T) {
t.Error("hash does not match reference")
}
}

// Regression tests for hash-to-G2
func TestPointG2HashToPointRegression(t *testing.T) {
// regression test 1
p := new(pointG2).Hash([]byte("abc"))
pBuf, err := p.MarshalBinary()
if err != nil {
t.Error(err)
}
refBuf, err := hex.DecodeString("80dfe6c1dc83487bb7b72886e69934775b552f5db41fab6de00dcc9c3a59a14e836b8267e7a13e5afa904f9c011ad27de45af14c44b4dfeedf7c8e7d290dacd95f04d5463c622ce60b0c8ab9ae96d1f9ffb8d69d0207d6e3605372eb15f5a5530c0d64e7e8b6fedce3bd2993230bab11a43aec8bbb0153d461c8f9168e244c76")
if err != nil {
t.Error(err)
}
if !bytes.Equal(pBuf, refBuf) {
t.Error("hash does not match expected value")
}

// regression test 2
buf2, err := hex.DecodeString("e0a05cbb37fd6c159732a8c57b981773f7480695328b674d8a9cc083377f1811")
if err != nil {
t.Error(err)
}
p2 := new(pointG2).Hash(buf2)
p2Buf, err := p2.MarshalBinary()
if err != nil {
t.Error(err)
}
refBuf2, err := hex.DecodeString("0dfe629e88ab4afbaa36a415bad296f7329c39160b1232df1f0a2be393e19a4d0275b0223514724acf8f7f833202444da83a91e58db73eb37bd4def713e4bdf202a31171ba8753908126809fbb3ad266e959b4061755f405d12d90fbdbec8b10431ed85153b245f7788745255206c032caf8fbdd6432154a0d77dd24bc4d5937")
if err != nil {
t.Error(err)
}
if !bytes.Equal(p2Buf, refBuf2) {
t.Error("hash does not match expected value")
}
}

func testhashg2(t *testing.T, b []byte) {
p := new(pointG2)
p.Hash(b)
if !p.g.IsOnCurve() {
t.Error("hash to G2 yielded point not on curve")
}
// the order of the group, minus 1
orderm1 := bigFromBase10("65000549695646603732796438742359905742570406053903786389881062969044166799968")
G2 := new(groupG2)
orderm1scalar := G2.Scalar().SetBytes(orderm1.Bytes())
pmul := newPointG2()
pmul.Mul(orderm1scalar, p)
// Now add p one more time, and we should get O
pmul.Add(pmul, p)
if !pmul.g.z.IsZero() {
t.Error("hash to G2 yielded point of wrong order")
}
}

func TestPointG2HashtoPoint(t *testing.T) {
testhashg2(t, []byte(""))
testhashg2(t, []byte("abc"))
testhashg2(t, []byte("test hash string"))
}
30 changes: 30 additions & 0 deletions pairing/bn256/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,36 @@ func TestBilinearity(t *testing.T) {
require.Equal(t, pc, pd)
}

type hashablePoint interface {
Hash([]byte) kyber.Point
}

func TestHashBilinearity(t *testing.T) {
suite := NewSuite()

// Compute hash to G1
A := suite.G1().Point().(hashablePoint).Hash([]byte("1234"))

// Compute hash to G2
B := suite.G2().Point().(hashablePoint).Hash([]byte("1234"))

// Choose a random scalar s
s := suite.G1().Scalar().Pick(random.New())

// Compute As = A^s
As := suite.G1().Point().Mul(s, A)

// Compute Bs = B^s
Bs := suite.G2().Point().Mul(s, B)

// Compute e(As, B) and e(A, Bs)
res1 := suite.Pair(As, B)
res2 := suite.Pair(A, Bs)

// Check they are equal
require.Equal(t, res1, res2)
}

func TestTripartiteDiffieHellman(t *testing.T) {
suite := NewSuite()
a := suite.G1().Scalar().Pick(random.New())
Expand Down