package bls12381 import ( "errors" "math" "math/big" ) // PointG2 is type for point in G2. // PointG2 is both used for Affine and Jacobian point representation. // If z is equal to one the point is accounted as in affine form. type PointG2 [3]fe2 // Set copies valeus of one point to another. func (p *PointG2) Set(p2 *PointG2) *PointG2 { p[0].set(&p2[0]) p[1].set(&p2[1]) p[2].set(&p2[2]) return p } func (p *PointG2) Zero() *PointG2 { p[0].zero() p[1].one() p[2].zero() return p } type tempG2 struct { t [9]*fe2 } // G2 is struct for G2 group. type G2 struct { f *fp2 tempG2 } // NewG2 constructs a new G2 instance. func NewG2() *G2 { return newG2(nil) } func newG2(f *fp2) *G2 { if f == nil { f = newFp2() } t := newTempG2() return &G2{f, t} } func newTempG2() tempG2 { t := [9]*fe2{} for i := 0; i < 9; i++ { t[i] = &fe2{} } return tempG2{t} } // Q returns group order in big.Int. func (g *G2) Q() *big.Int { return new(big.Int).Set(q) } // FromUncompressed expects byte slice larger than 192 bytes and given bytes returns a new point in G2. // Serialization rules are in line with zcash library. See below for details. // https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization // https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html func (g *G2) FromUncompressed(uncompressed []byte) (*PointG2, error) { if len(uncompressed) < 192 { return nil, errors.New("input string should be equal or larger than 192") } var in [192]byte copy(in[:], uncompressed[:192]) if in[0]&(1<<7) != 0 { return nil, errors.New("compression flag should be zero") } if in[0]&(1<<5) != 0 { return nil, errors.New("sort flag should be zero") } if in[0]&(1<<6) != 0 { for i, v := range in { if (i == 0 && v != 0x40) || (i != 0 && v != 0x00) { return nil, errors.New("input string should be zero when infinity flag is set") } } return g.Zero(), nil } in[0] &= 0x1f x, err := g.f.fromBytes(in[:96]) if err != nil { return nil, err } y, err := g.f.fromBytes(in[96:]) if err != nil { return nil, err } z := new(fe2).one() p := &PointG2{*x, *y, *z} if !g.IsOnCurve(p) { return nil, errors.New("point is not on curve") } if !g.InCorrectSubgroup(p) { return nil, errors.New("point is not on correct subgroup") } return p, nil } // ToUncompressed given a G2 point returns bytes in uncompressed (x, y) form of the point. // Serialization rules are in line with zcash library. See below for details. // https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization // https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html func (g *G2) ToUncompressed(p *PointG2) []byte { out := make([]byte, 192) g.Affine(p) if g.IsZero(p) { out[0] |= 1 << 6 return out } copy(out[:96], g.f.toBytes(&p[0])) copy(out[96:], g.f.toBytes(&p[1])) return out } // FromCompressed expects byte slice larger than 96 bytes and given bytes returns a new point in G2. // Serialization rules are in line with zcash library. See below for details. // https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization // https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html func (g *G2) FromCompressed(compressed []byte) (*PointG2, error) { if len(compressed) < 96 { return nil, errors.New("input string should be equal or larger than 96") } var in [96]byte copy(in[:], compressed[:]) if in[0]&(1<<7) == 0 { return nil, errors.New("bad compression") } if in[0]&(1<<6) != 0 { // in[0] == (1 << 6) + (1 << 7) for i, v := range in { if (i == 0 && v != 0xc0) || (i != 0 && v != 0x00) { return nil, errors.New("input string should be zero when infinity flag is set") } } return g.Zero(), nil } a := in[0]&(1<<5) != 0 in[0] &= 0x1f x, err := g.f.fromBytes(in[:]) if err != nil { return nil, err } // solve curve equation y := &fe2{} g.f.square(y, x) g.f.mul(y, y, x) g.f.add(y, y, b2) if ok := g.f.sqrt(y, y); !ok { return nil, errors.New("point is not on curve") } if y.signBE() == a { g.f.neg(y, y) } z := new(fe2).one() p := &PointG2{*x, *y, *z} if !g.InCorrectSubgroup(p) { return nil, errors.New("point is not on correct subgroup") } return p, nil } // ToCompressed given a G2 point returns bytes in compressed form of the point. // Serialization rules are in line with zcash library. See below for details. // https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization // https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html func (g *G2) ToCompressed(p *PointG2) []byte { out := make([]byte, 96) g.Affine(p) if g.IsZero(p) { out[0] |= 1 << 6 } else { copy(out[:], g.f.toBytes(&p[0])) if !p[1].signBE() { out[0] |= 1 << 5 } } out[0] |= 1 << 7 return out } func (g *G2) fromBytesUnchecked(in []byte) (*PointG2, error) { p0, err := g.f.fromBytes(in[:96]) if err != nil { return nil, err } p1, err := g.f.fromBytes(in[96:]) if err != nil { return nil, err } p2 := new(fe2).one() return &PointG2{*p0, *p1, *p2}, nil } // FromBytes constructs a new point given uncompressed byte input. // FromBytes does not take zcash flags into account. // Byte input expected to be larger than 96 bytes. // First 192 bytes should be concatenation of x and y values // Point (0, 0) is considered as infinity. func (g *G2) FromBytes(in []byte) (*PointG2, error) { if len(in) < 192 { return nil, errors.New("input string should be equal or larger than 192") } p0, err := g.f.fromBytes(in[:96]) if err != nil { return nil, err } p1, err := g.f.fromBytes(in[96:]) if err != nil { return nil, err } // check if given input points to infinity if p0.isZero() && p1.isZero() { return g.Zero(), nil } p2 := new(fe2).one() p := &PointG2{*p0, *p1, *p2} if !g.IsOnCurve(p) { return nil, errors.New("point is not on curve") } return p, nil } // ToBytes serializes a point into bytes in uncompressed form, // does not take zcash flags into account, // returns (0, 0) if point is infinity. func (g *G2) ToBytes(p *PointG2) []byte { out := make([]byte, 192) if g.IsZero(p) { return out } g.Affine(p) copy(out[:96], g.f.toBytes(&p[0])) copy(out[96:], g.f.toBytes(&p[1])) return out } // New creates a new G2 Point which is equal to zero in other words point at infinity. func (g *G2) New() *PointG2 { return new(PointG2).Zero() } // Zero returns a new G2 Point which is equal to point at infinity. func (g *G2) Zero() *PointG2 { return new(PointG2).Zero() } // One returns a new G2 Point which is equal to generator point. func (g *G2) One() *PointG2 { p := &PointG2{} return p.Set(&g2One) } // IsZero returns true if given point is equal to zero. func (g *G2) IsZero(p *PointG2) bool { return p[2].isZero() } // Equal checks if given two G2 point is equal in their affine form. func (g *G2) Equal(p1, p2 *PointG2) bool { if g.IsZero(p1) { return g.IsZero(p2) } if g.IsZero(p2) { return g.IsZero(p1) } t := g.t g.f.square(t[0], &p1[2]) g.f.square(t[1], &p2[2]) g.f.mul(t[2], t[0], &p2[0]) g.f.mul(t[3], t[1], &p1[0]) g.f.mul(t[0], t[0], &p1[2]) g.f.mul(t[1], t[1], &p2[2]) g.f.mul(t[1], t[1], &p1[1]) g.f.mul(t[0], t[0], &p2[1]) return t[0].equal(t[1]) && t[2].equal(t[3]) } // InCorrectSubgroup checks whether given point is in correct subgroup. func (g *G2) InCorrectSubgroup(p *PointG2) bool { tmp := &PointG2{} g.MulScalar(tmp, p, q) return g.IsZero(tmp) } // IsOnCurve checks a G2 point is on curve. func (g *G2) IsOnCurve(p *PointG2) bool { if g.IsZero(p) { return true } t := g.t g.f.square(t[0], &p[1]) g.f.square(t[1], &p[0]) g.f.mul(t[1], t[1], &p[0]) g.f.square(t[2], &p[2]) g.f.square(t[3], t[2]) g.f.mul(t[2], t[2], t[3]) g.f.mul(t[2], b2, t[2]) g.f.add(t[1], t[1], t[2]) return t[0].equal(t[1]) } // IsAffine checks a G2 point whether it is in affine form. func (g *G2) IsAffine(p *PointG2) bool { return p[2].isOne() } // Affine calculates affine form of given G2 point. func (g *G2) Affine(p *PointG2) *PointG2 { if g.IsZero(p) { return p } if !g.IsAffine(p) { t := g.t g.f.inverse(t[0], &p[2]) g.f.square(t[1], t[0]) g.f.mul(&p[0], &p[0], t[1]) g.f.mul(t[0], t[0], t[1]) g.f.mul(&p[1], &p[1], t[0]) p[2].one() } return p } // Add adds two G2 points p1, p2 and assigns the result to point at first argument. func (g *G2) Add(r, p1, p2 *PointG2) *PointG2 { // http://www.hyperelliptic.org/EFD/gp/auto-shortw-jacobian-0.html#addition-add-2007-bl if g.IsZero(p1) { return r.Set(p2) } if g.IsZero(p2) { return r.Set(p1) } t := g.t g.f.square(t[7], &p1[2]) g.f.mul(t[1], &p2[0], t[7]) g.f.mul(t[2], &p1[2], t[7]) g.f.mul(t[0], &p2[1], t[2]) g.f.square(t[8], &p2[2]) g.f.mul(t[3], &p1[0], t[8]) g.f.mul(t[4], &p2[2], t[8]) g.f.mul(t[2], &p1[1], t[4]) if t[1].equal(t[3]) { if t[0].equal(t[2]) { return g.Double(r, p1) } else { return r.Zero() } } g.f.sub(t[1], t[1], t[3]) g.f.double(t[4], t[1]) g.f.square(t[4], t[4]) g.f.mul(t[5], t[1], t[4]) g.f.sub(t[0], t[0], t[2]) g.f.double(t[0], t[0]) g.f.square(t[6], t[0]) g.f.sub(t[6], t[6], t[5]) g.f.mul(t[3], t[3], t[4]) g.f.double(t[4], t[3]) g.f.sub(&r[0], t[6], t[4]) g.f.sub(t[4], t[3], &r[0]) g.f.mul(t[6], t[2], t[5]) g.f.double(t[6], t[6]) g.f.mul(t[0], t[0], t[4]) g.f.sub(&r[1], t[0], t[6]) g.f.add(t[0], &p1[2], &p2[2]) g.f.square(t[0], t[0]) g.f.sub(t[0], t[0], t[7]) g.f.sub(t[0], t[0], t[8]) g.f.mul(&r[2], t[0], t[1]) return r } // Double doubles a G2 point p and assigns the result to the point at first argument. func (g *G2) Double(r, p *PointG2) *PointG2 { // http://www.hyperelliptic.org/EFD/gp/auto-shortw-jacobian-0.html#doubling-dbl-2009-l if g.IsZero(p) { return r.Set(p) } t := g.t g.f.square(t[0], &p[0]) g.f.square(t[1], &p[1]) g.f.square(t[2], t[1]) g.f.add(t[1], &p[0], t[1]) g.f.square(t[1], t[1]) g.f.sub(t[1], t[1], t[0]) g.f.sub(t[1], t[1], t[2]) g.f.double(t[1], t[1]) g.f.double(t[3], t[0]) g.f.add(t[0], t[3], t[0]) g.f.square(t[4], t[0]) g.f.double(t[3], t[1]) g.f.sub(&r[0], t[4], t[3]) g.f.sub(t[1], t[1], &r[0]) g.f.double(t[2], t[2]) g.f.double(t[2], t[2]) g.f.double(t[2], t[2]) g.f.mul(t[0], t[0], t[1]) g.f.sub(t[1], t[0], t[2]) g.f.mul(t[0], &p[1], &p[2]) r[1].set(t[1]) g.f.double(&r[2], t[0]) return r } // Neg negates a G2 point p and assigns the result to the point at first argument. func (g *G2) Neg(r, p *PointG2) *PointG2 { r[0].set(&p[0]) g.f.neg(&r[1], &p[1]) r[2].set(&p[2]) return r } // Sub subtracts two G2 points p1, p2 and assigns the result to point at first argument. func (g *G2) Sub(c, a, b *PointG2) *PointG2 { d := &PointG2{} g.Neg(d, b) g.Add(c, a, d) return c } // MulScalar multiplies a point by given scalar value in big.Int and assigns the result to point at first argument. func (g *G2) MulScalar(c, p *PointG2, e *big.Int) *PointG2 { q, n := &PointG2{}, &PointG2{} n.Set(p) l := e.BitLen() for i := 0; i < l; i++ { if e.Bit(i) == 1 { g.Add(q, q, n) } g.Double(n, n) } return c.Set(q) } // ClearCofactor maps given a G2 point to correct subgroup func (g *G2) ClearCofactor(p *PointG2) *PointG2 { return g.wnafMul(p, p, cofactorEFFG2) } // MultiExp calculates multi exponentiation. Given pairs of G2 point and scalar values // (P_0, e_0), (P_1, e_1), ... (P_n, e_n) calculates r = e_0 * P_0 + e_1 * P_1 + ... + e_n * P_n // Length of points and scalars are expected to be equal, otherwise an error is returned. // Result is assigned to point at first argument. func (g *G2) MultiExp(r *PointG2, points []*PointG2, powers []*big.Int) (*PointG2, error) { if len(points) != len(powers) { return nil, errors.New("point and scalar vectors should be in same length") } var c uint32 = 3 if len(powers) >= 32 { c = uint32(math.Ceil(math.Log10(float64(len(powers))))) } bucketSize, numBits := (1<= 0; i-- { g.Add(sum, sum, bucket[i]) g.Add(acc, acc, sum) } windows[j] = g.New() windows[j].Set(acc) j++ cur += c } acc.Zero() for i := len(windows) - 1; i >= 0; i-- { for j := uint32(0); j < c; j++ { g.Double(acc, acc) } g.Add(acc, acc, windows[i]) } return r.Set(acc), nil } func (g *G2) wnafMul(c, p *PointG2, e *big.Int) *PointG2 { windowSize := uint(6) precompTable := make([]*PointG2, (1 << (windowSize - 1))) for i := 0; i < len(precompTable); i++ { precompTable[i] = g.New() } var indexForPositive uint64 = (1 << (windowSize - 2)) precompTable[indexForPositive].Set(p) g.Neg(precompTable[indexForPositive-1], p) doubled, precomp := g.New(), g.New() g.Double(doubled, p) precomp.Set(p) for i := uint64(1); i < indexForPositive; i++ { g.Add(precomp, precomp, doubled) precompTable[indexForPositive+i].Set(precomp) g.Neg(precompTable[indexForPositive-1-i], precomp) } wnaf := wnaf(e, windowSize) q := g.Zero() found := false var idx uint64 for i := len(wnaf) - 1; i >= 0; i-- { if found { g.Double(q, q) } if wnaf[i] != 0 { found = true if wnaf[i] > 0 { idx = uint64(wnaf[i] >> 1) g.Add(q, q, precompTable[indexForPositive+idx]) } else { idx = uint64(((0 - wnaf[i]) >> 1)) g.Add(q, q, precompTable[indexForPositive-1-idx]) } } } return c.Set(q) } // MapToCurve given a byte slice returns a valid G2 point. // This mapping function implements the Simplified Shallue-van de Woestijne-Ulas method. // https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-6.6.2 // Input byte slice should be a valid field element, otherwise an error is returned. func (g *G2) MapToCurve(in []byte) (*PointG2, error) { fp2 := g.f u, err := fp2.fromBytes(in) if err != nil { return nil, err } x, y := swuMapG2(fp2, u) isogenyMapG2(fp2, x, y) z := new(fe2).one() q := &PointG2{*x, *y, *z} g.ClearCofactor(q) return g.Affine(q), nil } // EncodeToCurve given a message and domain seperator tag returns the hash result // which is a valid curve point. // Implementation follows BLS12381G1_XMD:SHA-256_SSWU_NU_ suite at // https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06 func (g *G2) EncodeToCurve(msg, domain []byte) (*PointG2, error) { hashRes, err := hashToFpXMDSHA256(msg, domain, 2) if err != nil { return nil, err } fp2 := g.f u := &fe2{*hashRes[0], *hashRes[1]} x, y := swuMapG2(fp2, u) isogenyMapG2(fp2, x, y) z := new(fe2).one() q := &PointG2{*x, *y, *z} g.ClearCofactor(q) return g.Affine(q), nil } // HashToCurve given a message and domain seperator tag returns the hash result // which is a valid curve point. // Implementation follows BLS12381G1_XMD:SHA-256_SSWU_RO_ suite at // https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06 func (g *G2) HashToCurve(msg, domain []byte) (*PointG2, error) { hashRes, err := hashToFpXMDSHA256(msg, domain, 4) if err != nil { return nil, err } fp2 := g.f u0, u1 := &fe2{*hashRes[0], *hashRes[1]}, &fe2{*hashRes[2], *hashRes[3]} x0, y0 := swuMapG2(fp2, u0) x1, y1 := swuMapG2(fp2, u1) z0 := new(fe2).one() z1 := new(fe2).one() p0, p1 := &PointG2{*x0, *y0, *z0}, &PointG2{*x1, *y1, *z1} g.Add(p0, p0, p1) g.Affine(p0) isogenyMapG2(fp2, &p0[0], &p0[1]) g.ClearCofactor(p0) return g.Affine(p0), nil }