2021-02-22 17:48:14 +00:00

425 lines
13 KiB
Go

// Package bn256 implements a particular bilinear group at the 128-bit security
// level.
//
// Bilinear groups are the basis of many of the new cryptographic protocols that
// have been proposed over the past decade. They consist of a triplet of groups
// (G₁, G₂ and GT) such that there exists a function e(g₁ˣ,g₂ʸ)=gTˣʸ (where gₓ
// is a generator of the respective group). That function is called a pairing
// function.
//
// This package specifically implements the Optimal Ate pairing over a 256-bit
// Barreto-Naehrig curve as described in
// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible
// with the implementation described in that paper.
package bn256
// This file implement some util functions for the MPC
// especially the serialization and deserialization functions for points in G1
import (
"errors"
"math/big"
)
// Constants related to the bn256 pairing friendly curve
const (
FqElementSize = 32
G1CompressedSize = FqElementSize + 1 // + 1 accounts for the additional byte used for masking
G1UncompressedSize = 2*FqElementSize + 1 // + 1 accounts for the additional byte used for masking
)
// https://github.com/ebfull/pairing/tree/master/src/bls12_381#serialization
// Bytes used to detect the formatting. By reading the first byte of the encoded point we can know it's nature
// ie: we can know if the point is the point at infinity, if it is encoded uncompressed or if it is encoded compressed
// Bit masking used to detect the serialization of the points and their nature
//
// The BSL12-381 curve is built over a 381-bit prime field.
// Thus each point coordinate is represented over 381 bits = 47bytes + 5bits
// Thus, to represent a point we need to have 48bytes, but the last 3 bits of the 48th byte will be set to 0
// These are these bits that are used to implement the masking, hence why the masking proposed by ebfull was:
const (
serializationMask = (1 << 5) - 1 // 0001 1111 // Enable to pick the 3 MSB corresponding to the serialization flag
serializationCompressed = 1 << 7 // 1000 0000
serializationInfinity = 1 << 6 // 0100 0000
serializationBigY = 1 << 5 // 0010 0000
)
// IsHigherY is used to distinguish between the 2 points of E
// that have the same x-coordinate
// The point e is assumed to be given in the affine form
func (e *G1) IsHigherY() bool {
// Check nil pointers
if e.p == nil {
e.p = &curvePoint{}
}
var yCoord gfP
//yCoord.Set(&e.p.y)
yCoord = e.p.y
var yCoordNeg gfP
gfpNeg(&yCoordNeg, &yCoord)
res := gfpCmp(&yCoord, &yCoordNeg)
if res == 1 { // yCoord > yCoordNeg
return true
} else if res == -1 {
return false
}
return false
}
// EncodeCompressed converts the compressed point e into bytes
// This function takes a point in the Jacobian form
// This function does not modify the point e
// (the variable `temp` is introduced to avoid to modify e)
func (e *G1) EncodeCompressed() []byte {
// Check nil pointers
if e.p == nil {
e.p = &curvePoint{}
}
e.p.MakeAffine()
ret := make([]byte, G1CompressedSize)
// Flag the encoding with the compressed flag
ret[0] |= serializationCompressed
if e.p.IsInfinity() {
// Flag the encoding with the infinity flag
ret[0] |= serializationInfinity
return ret
}
if e.IsHigherY() {
// Flag the encoding with the bigY flag
ret[0] |= serializationBigY
}
// We start the serializagtion of the coordinates at the index 1
// Since the index 0 in the `ret` corresponds to the masking
temp := &gfP{}
montDecode(temp, &e.p.x)
temp.Marshal(ret[1:])
return ret
}
// returns to buffer rather than allocation from GC
func (e *G1) EncodeCompressedToBuf(ret []byte) {
// Check nil pointers
if e.p == nil {
e.p = &curvePoint{}
}
e.p.MakeAffine()
//ret := make([]byte, G1CompressedSize)
// Flag the encoding with the compressed flag
ret[0] |= serializationCompressed
if e.p.IsInfinity() {
// Flag the encoding with the infinity flag
ret[0] |= serializationInfinity
return
}
if e.IsHigherY() {
// Flag the encoding with the bigY flag
ret[0] |= serializationBigY
}
// We start the serializagtion of the coordinates at the index 1
// Since the index 0 in the `ret` corresponds to the masking
temp := &gfP{}
montDecode(temp, &e.p.x)
temp.Marshal(ret[1:])
return
}
// EncodeUncompressed converts the compressed point e into bytes
// Take a point P in Jacobian form (where each coordinate is MontEncoded)
// and encodes it by going back to affine coordinates and montDecode all coordinates
// This function does not modify the point e
// (the variable `temp` is introduced to avoid to modify e)
/*
func (e *G1) EncodeUncompressed() []byte {
// Check nil pointers
if e.p == nil {
e.p = &curvePoint{}
}
e.p.MakeAffine()
ret := make([]byte, G1UncompressedSize)
if e.p.IsInfinity() {
// Flag the encoding with the infinity flag
ret[0] |= serializationInfinity
return ret
}
// We start the serialization of the coordinates at the index 1
// Since the index 0 in the `ret` corresponds to the masking
temp := &gfP{}
montDecode(temp, &e.p.x) // Store the montgomery decoding in temp
temp.Marshal(ret[1:33]) // Write temp in the `ret` slice, this is the x-coordinate
montDecode(temp, &e.p.y)
temp.Marshal(ret[33:]) // this is the y-coordinate
return ret
}
*/
func (e *G1) EncodeUncompressed() []byte {
// Check nil pointers
if e.p == nil {
e.p = &curvePoint{}
}
// Set the right flags
ret := make([]byte, G1UncompressedSize)
if e.p.IsInfinity() {
// Flag the encoding with the infinity flag
ret[0] |= serializationInfinity
return ret
}
// Marshal
marshal := e.Marshal()
// The encoding = flags || marshalledPoint
copy(ret[1:], marshal)
return ret
}
// Takes a MontEncoded x and finds the corresponding y (one of the two possible y's)
func getYFromMontEncodedX(x *gfP) (*gfP, error) {
// Check nil pointers
if x == nil {
return nil, errors.New("Cannot retrieve the y-coordinate form a nil pointer")
}
// Operations on montgomery encoded field elements
x2 := &gfP{}
gfpMul(x2, x, x)
x3 := &gfP{}
gfpMul(x3, x2, x)
rhs := &gfP{}
gfpAdd(rhs, x3, curveB) // curveB is MontEncoded, since it is create with newGFp
// Montgomery decode rhs
// Needed because when we create a GFp element
// with gfP{}, then it is not montEncoded. However
// if we create an element of GFp by using `newGFp()`
// then this field element is Montgomery encoded
// Above, we have been working on Montgomery encoded field elements
// here we solve the quad. resid. over F (not encoded)
// and then we encode back and return the encoded result
//
// Eg:
// - Px := &gfP{1} => 0000000000000000000000000000000000000000000000000000000000000001
// - PxNew := newGFp(1) => 0e0a77c19a07df2f666ea36f7879462c0a78eb28f5c70b3dd35d438dc58f0d9d
montDecode(rhs, rhs)
rhsBig, err := rhs.gFpToBigInt()
if err != nil {
return nil, err
}
// Note, if we use the ModSqrt method, we don't need the exponent, so we can comment these lines
yCoord := big.NewInt(0)
res := yCoord.ModSqrt(rhsBig, P)
if res == nil {
return nil, errors.New("not a square mod P")
}
yCoordGFp := newGFpFromBigInt(yCoord)
montEncode(yCoordGFp, yCoordGFp)
return yCoordGFp, nil
}
// DecodeCompressed decodes a point in the compressed form
// Take a point P encoded (ie: written in affine form where each coordinate is MontDecoded)
// and encodes it by going back to Jacobian coordinates and montEncode all coordinates
func (e *G1) DecodeCompressed(encoding []byte) error {
if len(encoding) != G1CompressedSize {
return errors.New("wrong encoded point size")
}
if encoding[0]&serializationCompressed == 0 { // Also test the length of the encoding to make sure it is 33bytes
return errors.New("point isn't compressed")
}
// Unmarshal the points and check their caps
if e.p == nil {
e.p = &curvePoint{}
}
{
e.p.x, e.p.y = gfP{0}, gfP{0}
e.p.z, e.p.t = *newGFp(1), *newGFp(1)
}
// Removes the bits of the masking (This does a bitwise AND with `0001 1111`)
// And thus removes the first 3 bits corresponding to the masking
bin := make([]byte, G1CompressedSize)
copy(bin, encoding)
bin[0] &= serializationMask
// Decode the point at infinity in the compressed form
if encoding[0]&serializationInfinity != 0 {
if encoding[0]&serializationBigY != 0 {
return errors.New("high Y bit improperly set")
}
// Similar to `for i:=0; i<len(bin); i++ {}`
for i := range bin {
// Makes sense to check that all bytes of bin are 0x0 since we removed the masking above
if bin[i] != 0 {
return errors.New("invalid infinity encoding")
}
}
e.p.SetInfinity()
//panic("point is infinity")
return nil
}
// Decompress the point P (P =/= ∞)
var err error
if err = e.p.x.Unmarshal(bin[1:]); err != nil {
return err
}
// MontEncode our field elements for fast finite field arithmetic
// Needs to be done since the z and t coordinates are also encoded (ie: created with newGFp)
montEncode(&e.p.x, &e.p.x)
y, err := getYFromMontEncodedX(&e.p.x)
if err != nil {
return err
}
e.p.y = *y
// The flag serializationBigY is set (so the point pt with the higher Y is encoded)
// but the point e retrieved from the `getYFromX` is NOT the higher, then we inverse
if !e.IsHigherY() {
if encoding[0]&serializationBigY != 0 {
e.Neg(e)
}
} else {
if encoding[0]&serializationBigY == 0 { // The point given by getYFromX is the higher but the mask is not set for higher y
e.Neg(e)
}
}
// No need to check that the point e.p is on the curve
// since we retrieved y from x by using the curve equation.
// Adding it would be redundant
return nil
}
// DecodeUncompressed decodes a point in the uncompressed form
// Take a point P encoded (ie: written in affine form where each coordinate is MontDecoded)
// and encodes it by going back to Jacobian coordinates and montEncode all coordinates
/*
func (e *G1) DecodeUncompressed(encoding []byte) error {
if len(encoding) != G1UncompressedSize {
return errors.New("wrong encoded point size")
}
if encoding[0]&serializationCompressed != 0 { // Also test the length of the encoding to make sure it is 65bytes
return errors.New("point is compressed")
}
if encoding[0]&serializationBigY != 0 { // Also test that the bigY flag if not set
return errors.New("bigY flag should not be set")
}
// Unmarshal the points and check their caps
if e.p == nil {
e.p = &curvePoint{}
} else {
e.p.x, e.p.y = gfP{0}, gfP{0}
e.p.z, e.p.t = *newGFp(1), *newGFp(1)
}
// Removes the bits of the masking (This does a bitwise AND with `0001 1111`)
// And thus removes the first 3 bits corresponding to the masking
// Useless for now because in bn256, we added a full byte to enable masking
// However, this is needed if we work over BLS12 and its underlying field
bin := make([]byte, G1UncompressedSize)
copy(bin, encoding)
bin[0] &= serializationMask
// Decode the point at infinity in the compressed form
if encoding[0]&serializationInfinity != 0 {
// Makes sense to check that all bytes of bin are 0x0 since we removed the masking above}
for i := range bin {
if bin[i] != 0 {
return errors.New("invalid infinity encoding")
}
}
e.p.SetInfinity()
return nil
}
// Decode the point P (P =/= ∞)
var err error
// Decode the x-coordinate
if err = e.p.x.Unmarshal(bin[1:33]); err != nil {
return err
}
// Decode the y-coordinate
if err = e.p.y.Unmarshal(bin[33:]); err != nil {
return err
}
// MontEncode our field elements for fast finite field arithmetic
montEncode(&e.p.x, &e.p.x)
montEncode(&e.p.y, &e.p.y)
if !e.p.IsOnCurve() {
return errors.New("malformed point: Not on the curve")
}
return nil
}
*/
func (e *G1) DecodeUncompressed(encoding []byte) error {
if len(encoding) != G1UncompressedSize {
return errors.New("wrong encoded point size")
}
if encoding[0]&serializationCompressed != 0 { // Also test the length of the encoding to make sure it is 65bytes
return errors.New("point is compressed")
}
if encoding[0]&serializationBigY != 0 { // Also test that the bigY flag if not set
return errors.New("bigY flag should not be set")
}
// Unmarshal the points and check their caps
if e.p == nil {
e.p = &curvePoint{}
}
// Removes the bits of the masking (This does a bitwise AND with `0001 1111`)
// And thus removes the first 3 bits corresponding to the masking
// Useless for now because in bn256, we added a full byte to enable masking
// However, this is needed if we work over BLS12 and its underlying field
bin := make([]byte, G1UncompressedSize)
copy(bin, encoding)
bin[0] &= serializationMask
// Decode the point at infinity in the compressed form
if encoding[0]&serializationInfinity != 0 {
// Makes sense to check that all bytes of bin are 0x0 since we removed the masking above}
for i := range bin {
if bin[i] != 0 {
return errors.New("invalid infinity encoding")
}
}
e.p.SetInfinity()
return nil
}
// We remote the flags and unmarshall the data
_, err := e.Unmarshal(encoding[1:])
return err
}