Skip to content

Commit

Permalink
Merge pull request #14 from jlandrews/point_compression
Browse files Browse the repository at this point in the history
Point compression on G2
  • Loading branch information
jlandrews authored Mar 8, 2018
2 parents 0431622 + 79438a9 commit e1f67d3
Show file tree
Hide file tree
Showing 6 changed files with 465 additions and 241 deletions.
59 changes: 24 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,50 +25,39 @@ The generator `g_2` is defined as: `(1155973203298638710799100402139228578392581
The identity element for both groups (The point at infinity in affine space) is internally represented as `(0,0)`

## Benchmarks
The following benchmarks are from a 3.80GHz i7-7700HQ CPU with 16GB ram.
The following benchmarks are from a 3.80GHz i7-7700HQ CPU with 16GB ram. Note that all code is running on a single core.

For reference, the pairing operation (the slowest operation involved) takes ~14 milliseconds.
For reference, the pairing operation (the slowest operation involved) takes ~1.6 milliseconds.
```
$ go test github.com/ethereum/go-ethereum/crypto/bn256 -bench .
BenchmarkPairing-8 100 13845045 ns/op
BenchmarkG1-8 10000 141018 ns/op
BenchmarkG2-8 3000 471002 ns/op
BenchmarkPairing-8 1000 1609893 ns/op
PASS
ok github.com/ethereum/go-ethereum/crypto/bn256 1.842s
ok github.com/ethereum/go-ethereum/crypto/bn256/cloudflare 4.725s
```
- `Signing` ~2 milliseconds
- `Signature verification` ~30 milliseconds, using two pairings.
- `Multi Signature verification` ~30 milliseconds + ~60 microseconds per signer, two pairings + n point additions
- `Aggregate Signature verification` ~15 milliseconds per signer/message pair, with n+1 pairings.

- `Signing` ~.25 milliseconds
- `Signature verification` ~3.4 milliseconds, using two pairings.
- `Multi Signature verification` ~3.7 milliseconds + ~2 microseconds per signer, two pairings + n point additions
- `Aggregate Signature verification` ~.2 milliseconds per signer/message pair, with n+1 pairings.

```
$ go test github.com/jlandrews/bgls -v -bench .
=== RUN TestAltbnHashToCurve
--- PASS: TestAltbnHashToCurve (0.01s)
=== RUN TestEthereumHash
--- PASS: TestEthereumHash (0.00s)
=== RUN TestSingleSigner
--- PASS: TestSingleSigner (0.10s)
=== RUN TestAggregation
--- PASS: TestAggregation (0.48s)
=== RUN TestMultiSig
--- PASS: TestMultiSig (0.81s)
=== RUN TestKnownCases
--- PASS: TestKnownCases (0.08s)
BenchmarkKeygen-8 300 4610235 ns/op
BenchmarkAltBnHashToCurve-8 20000 91348 ns/op
BenchmarkSigning-8 1000 2201775 ns/op
BenchmarkVerification-8 50 30001975 ns/op
BenchmarkMultiVerification64-8 50 32210135 ns/op
BenchmarkMultiVerification128-8 50 33022116 ns/op
BenchmarkMultiVerification256-8 50 37648131 ns/op
BenchmarkMultiVerification512-8 30 45162677 ns/op
BenchmarkMultiVerification1024-8 20 59932214 ns/op
BenchmarkMultiVerification2048-8 20 90268680 ns/op
BenchmarkAggregateVerification-8 100 15534821 ns/op
BenchmarkKeygen-8 3000 465450 ns/op
BenchmarkAltBnHashToCurve-8 10000 101616 ns/op
BenchmarkSigning-8 5000 250310 ns/op
BenchmarkVerification-8 500 3269717 ns/op
BenchmarkMultiVerification64-8 500 3878868 ns/op
BenchmarkMultiVerification128-8 300 3989605 ns/op
BenchmarkMultiVerification256-8 300 4326581 ns/op
BenchmarkMultiVerification512-8 300 4865951 ns/op
BenchmarkMultiVerification1024-8 200 5961354 ns/op
BenchmarkMultiVerification2048-8 200 8321105 ns/op
BenchmarkAggregateVerification-8 1000 1711109 ns/op
PASS
ok github.com/jlandrews/bgls 42.092s
ok github.com/jlandrews/bgls 27.739s
```
For comparison, the ed25519 implementation in go yields much faster key generation signing and single signature verification. At ~145 microseconds per verification, the multi signature verification is actually faster beyond ~350 signatures.
For comparison, the ed25519 implementation in go yields much faster key generation signing and single signature verification. However, at ~145 microseconds per verification, the multi signature verification is actually faster beyond ~26 signatures.
```
$ go test golang.org/x/crypto/ed25519 -bench .
BenchmarkKeyGeneration-8 30000 51878 ns/op
Expand Down
199 changes: 163 additions & 36 deletions alt_bn128.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ package bgls

import (
"bytes"
"fmt"
"math/big"

"github.com/dchest/blake2b"
"github.com/ethereum/go-ethereum/crypto/bn256"
"github.com/ethereum/go-ethereum/crypto/bn256/cloudflare"
gosha3 "github.com/ethereum/go-ethereum/crypto/sha3"
"golang.org/x/crypto/sha3"
)
Expand All @@ -31,15 +32,16 @@ type altbn128PointT struct {
// Altbn128Inst is the instance for the altbn128 curve, with all of its functions.
var Altbn128 = &altbn128{}

// AltbnMkG1Point copies points into []byte and unmarshals to get around curvePoint not being exported
// This is copied from bn256.G1.Marshal (modified)
// MakeG1Point copies points into []byte and unmarshals to get around curvePoint not being exported
func (curve *altbn128) MakeG1Point(x, y *big.Int) (Point1, bool) {
xBytes, yBytes := x.Bytes(), y.Bytes()
ret := make([]byte, 64)
copy(ret[32-len(xBytes):], xBytes)
copy(ret[64-len(yBytes):], yBytes)
result, ok := new(bn256.G1).Unmarshal(ret)
if !ok {
result := new(bn256.G1)
var ok error
_, ok = result.Unmarshal(ret)
if ok != nil {
return nil, false
}
return &altbn128Point1{result}, true
Expand All @@ -55,19 +57,38 @@ func (g1Point *altbn128Point1) Add(otherPoint1 Point1) (Point1, bool) {
}

func (g1Point *altbn128Point1) Copy() Point1 {
p, _ := new(bn256.G1).Unmarshal(g1Point.point.Marshal())
return &altbn128Point1{p}
result := new(bn256.G1)
result.Unmarshal(g1Point.point.Marshal())
return &altbn128Point1{result}
}

func (g1Point *altbn128Point1) Equals(otherPoint1 Point1) bool {
if other, ok := (otherPoint1).(*altbn128Point1); ok {
return bytes.Equal(g1Point.Marshal(), other.Marshal())
return bytes.Equal(g1Point.point.Marshal(), other.point.Marshal())
}
return false
}

func (g1Point *altbn128Point1) Marshal() []byte {
return g1Point.point.Marshal()
x, y := g1Point.ToAffineCoords()
xBytes := pad32Bytes(x.Bytes())
y.Mul(y, two)
if y.Cmp(altbnG1Q) == 1 {
xBytes[0] += 128
}
return xBytes
}

func pad32Bytes(xBytes []byte) []byte {
if len(xBytes) < 32 {
offset := 32 - len(xBytes)
rawBytes := make([]byte, 32, 32)
for i := 0; i < len(xBytes); i++ {
rawBytes[i+offset] = xBytes[i]
}
return rawBytes
}
return xBytes
}

func (g1Point *altbn128Point1) Mul(scalar *big.Int) Point1 {
Expand All @@ -86,22 +107,31 @@ func (g1Point *altbn128Point1) Pair(g2Point Point2) (PointT, bool) {
}

func (g1Point *altbn128Point1) ToAffineCoords() (x, y *big.Int) {
Bytestream := g1Point.Marshal()
Bytestream := g1Point.point.Marshal()
xBytes, yBytes := Bytestream[:32], Bytestream[32:64]
x = new(big.Int).SetBytes(xBytes)
y = new(big.Int).SetBytes(yBytes)
return
}

// AltbnG2ToCoord takes a point in G2 of Altbn_128, and returns its affine coordinates
func (curve *altbn128) MakeG2Point(pt *bn256.G2) (xx, xy, yx, yy *big.Int) {
Bytestream := pt.Marshal()
xxBytes, xyBytes, yxBytes, yyBytes := Bytestream[:32], Bytestream[32:64], Bytestream[64:96], Bytestream[96:128]
xx = new(big.Int).SetBytes(xxBytes)
xy = new(big.Int).SetBytes(xyBytes)
yx = new(big.Int).SetBytes(yxBytes)
yy = new(big.Int).SetBytes(yyBytes)
return
// MakeG2Point copies points into []byte and unmarshals to get around twistPoint not being exported
func (curve *altbn128) MakeG2Point(xx, xy, yx, yy *big.Int) (Point2, bool) {
xxBytes, xyBytes := pad32Bytes(xx.Bytes()), pad32Bytes(xy.Bytes())
yxBytes, yyBytes := pad32Bytes(yx.Bytes()), pad32Bytes(yy.Bytes())
ret := make([]byte, 128)
copy(ret[:32], xxBytes)
copy(ret[32:], xyBytes)
copy(ret[64:], yxBytes)
copy(ret[96:], yyBytes)
result := new(bn256.G2)
var ok error
_, ok = result.Unmarshal(ret)
if ok != nil {
fmt.Println(ok)
fmt.Println(len(xxBytes), len(xyBytes), len(yxBytes), len(yyBytes))
return nil, false
}
return &altbn128Point2{result}, true
}

func (g2Point *altbn128Point2) Add(otherPoint2 Point2) (Point2, bool) {
Expand All @@ -114,19 +144,36 @@ func (g2Point *altbn128Point2) Add(otherPoint2 Point2) (Point2, bool) {
}

func (g2Point *altbn128Point2) Copy() Point2 {
p, _ := new(bn256.G2).Unmarshal(g2Point.point.Marshal())
return &altbn128Point2{p}
result := new(bn256.G2)
result.Unmarshal(g2Point.point.Marshal())
return &altbn128Point2{result}
}

func (g2Point *altbn128Point2) Equals(otherPoint2 Point2) bool {
if other, ok := (otherPoint2).(*altbn128Point2); ok {
return bytes.Equal(g2Point.Marshal(), other.Marshal())
return bytes.Equal(g2Point.point.Marshal(), other.point.Marshal())
}
return false
}

func (g2Point *altbn128Point2) Marshal() []byte {
return g2Point.point.Marshal()
xi, xr, yi, yr := g2Point.ToAffineCoords()
xiBytes := pad32Bytes(xi.Bytes())
xrBytes := pad32Bytes(xr.Bytes())
y2 := &complexNum{yi, yr}
y2.Exp(y2, two, altbnG1Q)
yi.Mul(yi, two)
yr.Mul(yr, two)
if yi.Cmp(altbnG1Q) == 1 {
xiBytes[0] += 128
}
if yr.Cmp(altbnG1Q) == 1 {
xrBytes[0] += 128
}
xBytes := make([]byte, 64, 64)
copy(xBytes[:32], xiBytes)
copy(xBytes[32:], xrBytes)
return xBytes
}

func (g2Point *altbn128Point2) Mul(scalar *big.Int) Point2 {
Expand All @@ -135,6 +182,17 @@ func (g2Point *altbn128Point2) Mul(scalar *big.Int) Point2 {
return ret
}

func (g2Point *altbn128Point2) ToAffineCoords() (xx, xy, yx, yy *big.Int) {
Bytestream := g2Point.point.Marshal()
xxBytes, xyBytes := Bytestream[:32], Bytestream[32:64]
yxBytes, yyBytes := Bytestream[64:96], Bytestream[96:128]
xx = new(big.Int).SetBytes(xxBytes)
xy = new(big.Int).SetBytes(xyBytes)
yx = new(big.Int).SetBytes(yxBytes)
yy = new(big.Int).SetBytes(yyBytes)
return
}

func (gTPoint altbn128PointT) Add(otherPointT PointT) (PointT, bool) {
if other, ok := (otherPointT).(altbn128PointT); ok {
sum := new(bn256.GT).Add(gTPoint.point, other.point)
Expand All @@ -145,8 +203,9 @@ func (gTPoint altbn128PointT) Add(otherPointT PointT) (PointT, bool) {
}

func (gTPoint altbn128PointT) Copy() PointT {
p, _ := new(bn256.GT).Unmarshal(gTPoint.point.Marshal())
return altbn128PointT{p}
result := new(bn256.GT)
result.Unmarshal(gTPoint.point.Marshal())
return &altbn128PointT{result}
}

func (gTPoint altbn128PointT) Marshal() []byte {
Expand All @@ -167,21 +226,82 @@ func (gTPoint altbn128PointT) Mul(scalar *big.Int) PointT {
}

func (curve *altbn128) UnmarshalG1(data []byte) (Point1, bool) {
if data == nil || len(data) != 64 {
if data == nil || (len(data) != 64 && len(data) != 32) {
return nil, false
}
if curvePoint, ok := new(bn256.G1).Unmarshal(data); ok {
return &altbn128Point1{curvePoint}, true
if len(data) == 64 { // No point compression
curvePoint := new(bn256.G1)
if _, ok := curvePoint.Unmarshal(data); ok == nil {
return &altbn128Point1{curvePoint}, true
}
} else if len(data) == 32 { // Point compression
ySgn := (data[0] >= 128)
if ySgn {
data[0] -= 128
}
x := new(big.Int).SetBytes(data)
if x.Cmp(zero) == 0 {
return Altbn128.MakeG1Point(zero, zero)
}
y := Altbn128.g1XToYSquared(x)
// Underlying library already checks that y is on the curve, thus isQuadRes isn't checked here
y = calcQuadRes(y, altbnG1Q)
doubleY := new(big.Int).Mul(y, two)
cmpRes := doubleY.Cmp(altbnG1Q)
if ySgn && cmpRes == -1 {
y.Sub(altbnG1Q, y)
} else if !ySgn && cmpRes == 1 {
y.Sub(altbnG1Q, y)
}
return Altbn128.MakeG1Point(x, y)
}
return nil, false
}

func (curve *altbn128) UnmarshalG2(data []byte) (Point2, bool) {
if data == nil || len(data) != 128 {
if data == nil || (len(data) != 64 && len(data) != 128) {
return nil, false
}
if curvePoint, ok := new(bn256.G2).Unmarshal(data); ok {
return &altbn128Point2{curvePoint}, true
if len(data) == 128 { // No point compression
curvePoint := new(bn256.G2)
if _, ok := curvePoint.Unmarshal(data); ok == nil {
return &altbn128Point2{curvePoint}, true
}
} else if len(data) == 64 { // Point compression
xiBytes := data[:32]
xrBytes := data[32:]
yiSgn := (xiBytes[0] >= 128)
yrSgn := (xrBytes[0] >= 128)
if yiSgn {
xiBytes[0] -= 128
}
if yrSgn {
xrBytes[0] -= 128
}
xi := new(big.Int).SetBytes(xiBytes)
xr := new(big.Int).SetBytes(xrBytes)
if xi.Cmp(zero) == 0 && xr.Cmp(zero) == 0 {
return Altbn128.MakeG2Point(zero, zero, zero, zero)
}
x := &complexNum{xi, xr}
y := Altbn128.g2XToYSquared(x)
// Underlying library already checks that y is on the curve, thus isQuadRes isn't checked here
y = calcComplexQuadRes(y, altbnG1Q)
doubleYRe := new(big.Int).Mul(y.re, two)
doubleYIm := new(big.Int).Mul(y.im, two)
cmpResRe := doubleYRe.Cmp(altbnG1Q)
cmpResIm := doubleYIm.Cmp(altbnG1Q)
if yiSgn && cmpResIm == -1 {
y.im.Sub(altbnG1Q, y.im)
} else if !yiSgn && cmpResIm == 1 {
y.im.Sub(altbnG1Q, y.im)
}
if yrSgn && cmpResRe == -1 {
y.re.Sub(altbnG1Q, y.re)
} else if !yrSgn && cmpResRe == 1 {
y.re.Sub(altbnG1Q, y.re)
}
return Altbn128.MakeG2Point(x.im, x.re, y.im, y.re)
}
return nil, false
}
Expand All @@ -190,7 +310,8 @@ func (curve *altbn128) UnmarshalGT(data []byte) (PointT, bool) {
if data == nil || len(data) != 384 {
return nil, false
}
if curvePoint, ok := new(bn256.GT).Unmarshal(data); ok {
curvePoint := new(bn256.GT)
if _, ok := curvePoint.Unmarshal(data); ok == nil {
return altbn128PointT{curvePoint}, true
}
return nil, false
Expand Down Expand Up @@ -219,8 +340,11 @@ func (curve *altbn128) g1XToYSquared(x *big.Int) *big.Int {
return result
}

func (curve *altbn128) getG2Q() *big.Int {
return altbnG2Q
func (curve *altbn128) g2XToYSquared(x *complexNum) *complexNum {
result := getComplexZero()
result.Exp(x, three, altbnG1Q)
result.Add(result, altbnG2B, altbnG1Q)
return result
}

func (curve *altbn128) GetG1() Point1 {
Expand All @@ -238,7 +362,10 @@ func (curve *altbn128) GetGT() PointT {
//curve specific constants
var altbnG1B = big.NewInt(3)
var altbnG1Q, _ = new(big.Int).SetString("21888242871839275222246405745257275088696311157297823662689037894645226208583", 10)
var altbnG2Q = new(big.Int).Mul(altbnG1Q, altbnG1Q)

var altbnG2BRe, _ = new(big.Int).SetString("19485874751759354771024239261021720505790618469301721065564631296452457478373", 10)
var altbnG2BIm, _ = new(big.Int).SetString("266929791119991161246907387137283842545076965332900288569378510910307636690", 10)
var altbnG2B = &complexNum{altbnG2BIm, altbnG2BRe}

//precomputed Z = (-1 + sqrt(-3))/2 in Fq
var altbnZ, _ = new(big.Int).SetString("2203960485148121921418603742825762020974279258880205651966", 10)
Expand Down Expand Up @@ -283,7 +410,7 @@ func AltbnKang12(message []byte) (p1, p2 *big.Int) {
return
}

// AltbnHashToCurve Hashes a message to a point on Altbn128 using Keccak3 and try and increment
// HashToG1 Hashes a message to a point on Altbn128 using Keccak3 and try and increment
// This is for compatability with Ethereum hashing.
// The return value is the altbn_128 library's internel representation for points.
func (curve *altbn128) HashToG1(message []byte) Point1 {
Expand Down
Loading

0 comments on commit e1f67d3

Please sign in to comment.