-
Notifications
You must be signed in to change notification settings - Fork 168
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
G2hash #428
Changes from 4 commits
b9ea94a
1bd8fa7
c973168
2d95ab2
78796c8
77e41db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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)}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this belongs in There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
|
There was a problem hiding this comment.
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:
Likewise for the other Equals as well.
There was a problem hiding this comment.
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).