diff --git a/pkg/core/native/crypto.go b/pkg/core/native/crypto.go index d736f2beb..b4da10fa2 100644 --- a/pkg/core/native/crypto.go +++ b/pkg/core/native/crypto.go @@ -7,7 +7,6 @@ import ( "fmt" "math/big" - bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/nspcc-dev/neo-go/pkg/core/dao" @@ -190,31 +189,12 @@ func (c *Crypto) bls12381Deserialize(_ *interop.Context, args []stackitem.Item) if err != nil { panic(fmt.Errorf("invalid serialized bls12381 point: %w", err)) } - var res interface{} - switch l := len(buf); l { - case bls12381.SizeOfG1AffineCompressed: - g1Affine := new(bls12381.G1Affine) - _, err = g1Affine.SetBytes(buf) - if err != nil { - panic(fmt.Errorf("failed to decode bls12381 G1Affine point: %w", err)) - } - res = g1Affine - case bls12381.SizeOfG2AffineCompressed: - g2Affine := new(bls12381.G2Affine) - _, err = g2Affine.SetBytes(buf) - if err != nil { - panic(fmt.Errorf("failed to decode bls12381 G2Affine point: %w", err)) - } - res = g2Affine - case bls12381.SizeOfGT: - gt := new(bls12381.GT) - err := gt.SetBytes(buf) - if err != nil { - panic(fmt.Errorf("failed to decode GT point: %w", err)) - } - res = gt + p := new(blsPoint) + err = p.FromBytes(buf) + if err != nil { + panic(err) } - return stackitem.NewInterop(blsPoint{point: res}) + return stackitem.NewInterop(*p) } func (c *Crypto) bls12381Equal(_ *interop.Context, args []stackitem.Item) stackitem.Item { @@ -236,77 +216,12 @@ func (c *Crypto) bls12381Add(_ *interop.Context, args []stackitem.Item) stackite if !(okA && okB) { panic("some of the arguments are not a bls12381 point") } - var res interface{} - switch x := a.point.(type) { - case *bls12381.G1Affine: - switch y := b.point.(type) { - case *bls12381.G1Affine: - xJac := new(bls12381.G1Jac) - xJac.FromAffine(x) - xJac.AddMixed(y) - res = xJac - case *bls12381.G1Jac: - yJac := new(bls12381.G1Jac) - yJac.Set(y) - yJac.AddMixed(x) - res = yJac - default: - panic(fmt.Errorf("add: inconsistent bls12381 point types: %T and %T", x, y)) - } - case *bls12381.G1Jac: - resJac := new(bls12381.G1Jac) - resJac.Set(x) - switch y := b.point.(type) { - case *bls12381.G1Affine: - resJac.AddMixed(y) - case *bls12381.G1Jac: - resJac.AddAssign(y) - default: - panic(fmt.Errorf("add: inconsistent bls12381 point types: %T and %T", x, y)) - } - res = resJac - case *bls12381.G2Affine: - switch y := b.point.(type) { - case *bls12381.G2Affine: - xJac := new(bls12381.G2Jac) - xJac.FromAffine(x) - xJac.AddMixed(y) - res = xJac - case *bls12381.G2Jac: - yJac := new(bls12381.G2Jac) - yJac.Set(y) - yJac.AddMixed(x) - res = yJac - default: - panic(fmt.Errorf("add: inconsistent bls12381 point types: %T and %T", x, y)) - } - case *bls12381.G2Jac: - resJac := new(bls12381.G2Jac) - resJac.Set(x) - switch y := b.point.(type) { - case *bls12381.G2Affine: - resJac.AddMixed(y) - case *bls12381.G2Jac: - resJac.AddAssign(y) - default: - panic(fmt.Errorf("add: inconsistent bls12381 point types: %T and %T", x, y)) - } - res = resJac - case *bls12381.GT: - resGT := new(bls12381.GT) - resGT.Set(x) - switch y := b.point.(type) { - case *bls12381.GT: - // It's multiplication, see https://github.com/neo-project/Neo.Cryptography.BLS12_381/issues/4. - resGT.Mul(x, y) - default: - panic(fmt.Errorf("add: inconsistent bls12381 point types: %T and %T", x, y)) - } - res = resGT - default: - panic(fmt.Errorf("add: unexpected bls12381 point type: %T", x)) + + p, err := blsPointAdd(a, b) + if err != nil { + panic(err) } - return stackitem.NewInterop(blsPoint{point: res}) + return stackitem.NewInterop(p) } func scalarFromBytes(bytes []byte, neg bool) (*fr.Element, error) { @@ -347,46 +262,11 @@ func (c *Crypto) bls12381Mul(_ *interop.Context, args []stackitem.Item) stackite alphaBi := new(big.Int) alpha.BigInt(alphaBi) - var res interface{} - switch x := a.point.(type) { - case *bls12381.G1Affine: - // The result is in Jacobian form in the reference implementation. - g1Jac := new(bls12381.G1Jac) - g1Jac.FromAffine(x) - g1Jac.ScalarMultiplication(g1Jac, alphaBi) - res = g1Jac - case *bls12381.G1Jac: - g1Jac := new(bls12381.G1Jac) - g1Jac.ScalarMultiplication(x, alphaBi) - res = g1Jac - case *bls12381.G2Affine: - // The result is in Jacobian form in the reference implementation. - g2Jac := new(bls12381.G2Jac) - g2Jac.FromAffine(x) - g2Jac.ScalarMultiplication(g2Jac, alphaBi) - res = g2Jac - case *bls12381.G2Jac: - g2Jac := new(bls12381.G2Jac) - g2Jac.ScalarMultiplication(x, alphaBi) - res = g2Jac - case *bls12381.GT: - gt := new(bls12381.GT) - - // C# implementation differs a bit from go's. They use double-and-add algorithm, see - // https://github.com/neo-project/Neo.Cryptography.BLS12_381/blob/844bc3a4f7d8ba2c545ace90ca124f8ada4c8d29/src/Neo.Cryptography.BLS12_381/Gt.cs#L102 - // and https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Double-and-add, - // Pay attention that C#'s Gt.Double() squares (not doubles!) the initial GT point. - // Thus.C#'s scalar multiplication operation over Gt and Scalar is effectively an exponent. - // Go's exponent algorithm differs a bit from the C#'s double-and-add in that go's one - // uses 2-bits windowed method for multiplication. However, the resulting GT point is - // absolutely the same between two implementations. - gt.Exp(*x, alphaBi) - - res = gt - default: - panic(fmt.Errorf("mul: unexpected bls12381 point type: %T", x)) + p, err := blsPointMul(a, alphaBi) + if err != nil { + panic(err) } - return stackitem.NewInterop(blsPoint{point: res}) + return stackitem.NewInterop(p) } func (c *Crypto) bls12381Pairing(_ *interop.Context, args []stackitem.Item) stackitem.Item { @@ -395,33 +275,12 @@ func (c *Crypto) bls12381Pairing(_ *interop.Context, args []stackitem.Item) stac if !(okA && okB) { panic("some of the arguments are not a bls12381 point") } - var ( - x *bls12381.G1Affine - y *bls12381.G2Affine - ) - switch p := a.point.(type) { - case *bls12381.G1Affine: - x = p - case *bls12381.G1Jac: - x = new(bls12381.G1Affine) - x.FromJacobian(p) - default: - panic(fmt.Errorf("pairing: unexpected bls12381 point type (g1): %T", x)) - } - switch p := b.point.(type) { - case *bls12381.G2Affine: - y = p - case *bls12381.G2Jac: - y = new(bls12381.G2Affine) - y.FromJacobian(p) - default: - panic(fmt.Errorf("pairing: unexpected bls12381 point type (g2): %T", x)) - } - gt, err := bls12381.Pair([]bls12381.G1Affine{*x}, []bls12381.G2Affine{*y}) + + p, err := blsPointPairing(a, b) if err != nil { - panic(fmt.Errorf("failed to perform pairing operation: %w", err)) + panic(err) } - return stackitem.NewInterop(blsPoint{>}) + return stackitem.NewInterop(p) } // Metadata implements the Contract interface. diff --git a/pkg/core/native/crypto_blspoints.go b/pkg/core/native/crypto_blspoints.go index f1ef4ceb9..317d9b795 100644 --- a/pkg/core/native/crypto_blspoints.go +++ b/pkg/core/native/crypto_blspoints.go @@ -3,6 +3,7 @@ package native import ( "errors" "fmt" + "math/big" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -102,3 +103,191 @@ func (p blsPoint) Bytes() []byte { panic(errors.New("unknown bls12381 point type")) } } + +// FromBytes deserializes BLS12-381 point from the given byte slice in compressed form. +func (p *blsPoint) FromBytes(buf []byte) error { + switch l := len(buf); l { + case bls12381.SizeOfG1AffineCompressed: + g1Affine := new(bls12381.G1Affine) + _, err := g1Affine.SetBytes(buf) + if err != nil { + return fmt.Errorf("failed to decode bls12381 G1Affine point: %w", err) + } + p.point = g1Affine + case bls12381.SizeOfG2AffineCompressed: + g2Affine := new(bls12381.G2Affine) + _, err := g2Affine.SetBytes(buf) + if err != nil { + return fmt.Errorf("failed to decode bls12381 G2Affine point: %w", err) + } + p.point = g2Affine + case bls12381.SizeOfGT: + gt := new(bls12381.GT) + err := gt.SetBytes(buf) + if err != nil { + return fmt.Errorf("failed to decode GT point: %w", err) + } + p.point = gt + } + + return nil +} + +// blsPointAdd performs addition of two BLS12-381 points. +func blsPointAdd(a, b blsPoint) (blsPoint, error) { + var ( + res any + err error + ) + switch x := a.point.(type) { + case *bls12381.G1Affine: + switch y := b.point.(type) { + case *bls12381.G1Affine: + xJac := new(bls12381.G1Jac) + xJac.FromAffine(x) + xJac.AddMixed(y) + res = xJac + case *bls12381.G1Jac: + yJac := new(bls12381.G1Jac) + yJac.Set(y) + yJac.AddMixed(x) + res = yJac + default: + err = fmt.Errorf("add: inconsistent bls12381 point types: %T and %T", x, y) + } + case *bls12381.G1Jac: + resJac := new(bls12381.G1Jac) + resJac.Set(x) + switch y := b.point.(type) { + case *bls12381.G1Affine: + resJac.AddMixed(y) + case *bls12381.G1Jac: + resJac.AddAssign(y) + default: + err = fmt.Errorf("add: inconsistent bls12381 point types: %T and %T", x, y) + } + res = resJac + case *bls12381.G2Affine: + switch y := b.point.(type) { + case *bls12381.G2Affine: + xJac := new(bls12381.G2Jac) + xJac.FromAffine(x) + xJac.AddMixed(y) + res = xJac + case *bls12381.G2Jac: + yJac := new(bls12381.G2Jac) + yJac.Set(y) + yJac.AddMixed(x) + res = yJac + default: + err = fmt.Errorf("add: inconsistent bls12381 point types: %T and %T", x, y) + } + case *bls12381.G2Jac: + resJac := new(bls12381.G2Jac) + resJac.Set(x) + switch y := b.point.(type) { + case *bls12381.G2Affine: + resJac.AddMixed(y) + case *bls12381.G2Jac: + resJac.AddAssign(y) + default: + err = fmt.Errorf("add: inconsistent bls12381 point types: %T and %T", x, y) + } + res = resJac + case *bls12381.GT: + resGT := new(bls12381.GT) + resGT.Set(x) + switch y := b.point.(type) { + case *bls12381.GT: + // It's multiplication, see https://github.com/neo-project/Neo.Cryptography.BLS12_381/issues/4. + resGT.Mul(x, y) + default: + err = fmt.Errorf("add: inconsistent bls12381 point types: %T and %T", x, y) + } + res = resGT + default: + err = fmt.Errorf("add: unexpected bls12381 point type: %T", x) + } + + return blsPoint{point: res}, err +} + +// blsPointAdd performs scalar multiplication of two BLS12-381 points. +func blsPointMul(a blsPoint, alphaBi *big.Int) (blsPoint, error) { + var ( + res any + err error + ) + switch x := a.point.(type) { + case *bls12381.G1Affine: + // The result is in Jacobian form in the reference implementation. + g1Jac := new(bls12381.G1Jac) + g1Jac.FromAffine(x) + g1Jac.ScalarMultiplication(g1Jac, alphaBi) + res = g1Jac + case *bls12381.G1Jac: + g1Jac := new(bls12381.G1Jac) + g1Jac.ScalarMultiplication(x, alphaBi) + res = g1Jac + case *bls12381.G2Affine: + // The result is in Jacobian form in the reference implementation. + g2Jac := new(bls12381.G2Jac) + g2Jac.FromAffine(x) + g2Jac.ScalarMultiplication(g2Jac, alphaBi) + res = g2Jac + case *bls12381.G2Jac: + g2Jac := new(bls12381.G2Jac) + g2Jac.ScalarMultiplication(x, alphaBi) + res = g2Jac + case *bls12381.GT: + gt := new(bls12381.GT) + + // C# implementation differs a bit from go's. They use double-and-add algorithm, see + // https://github.com/neo-project/Neo.Cryptography.BLS12_381/blob/844bc3a4f7d8ba2c545ace90ca124f8ada4c8d29/src/Neo.Cryptography.BLS12_381/Gt.cs#L102 + // and https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Double-and-add, + // Pay attention that C#'s Gt.Double() squares (not doubles!) the initial GT point. + // Thus.C#'s scalar multiplication operation over Gt and Scalar is effectively an exponent. + // Go's exponent algorithm differs a bit from the C#'s double-and-add in that go's one + // uses 2-bits windowed method for multiplication. However, the resulting GT point is + // absolutely the same between two implementations. + gt.Exp(*x, alphaBi) + + res = gt + default: + err = fmt.Errorf("mul: unexpected bls12381 point type: %T", x) + } + + return blsPoint{point: res}, err +} + +func blsPointPairing(a, b blsPoint) (blsPoint, error) { + var ( + x *bls12381.G1Affine + y *bls12381.G2Affine + ) + switch p := a.point.(type) { + case *bls12381.G1Affine: + x = p + case *bls12381.G1Jac: + x = new(bls12381.G1Affine) + x.FromJacobian(p) + default: + return blsPoint{}, fmt.Errorf("pairing: unexpected bls12381 point type (g1): %T", x) + } + switch p := b.point.(type) { + case *bls12381.G2Affine: + y = p + case *bls12381.G2Jac: + y = new(bls12381.G2Affine) + y.FromJacobian(p) + default: + return blsPoint{}, fmt.Errorf("pairing: unexpected bls12381 point type (g2): %T", x) + } + + gt, err := bls12381.Pair([]bls12381.G1Affine{*x}, []bls12381.G2Affine{*y}) + if err != nil { + return blsPoint{}, fmt.Errorf("failed to perform pairing operation: %w", err) + } + + return blsPoint{>}, nil +}