package native import ( "crypto/elliptic" "encoding/binary" "errors" "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/interop" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/twmb/murmur3" ) // Crypto represents CryptoLib contract. type Crypto struct { interop.ContractMD } // NamedCurve identifies named elliptic curves. type NamedCurve byte // Various named elliptic curves. const ( Secp256k1 NamedCurve = 22 Secp256r1 NamedCurve = 23 ) const cryptoContractID = -3 func newCrypto() *Crypto { c := &Crypto{ContractMD: *interop.NewContractMD(nativenames.CryptoLib, cryptoContractID)} defer c.UpdateHash() desc := newDescriptor("sha256", smartcontract.ByteArrayType, manifest.NewParameter("data", smartcontract.ByteArrayType)) md := newMethodAndPrice(c.sha256, 1<<15, callflag.NoneFlag) c.AddMethod(md, desc) desc = newDescriptor("ripemd160", smartcontract.ByteArrayType, manifest.NewParameter("data", smartcontract.ByteArrayType)) md = newMethodAndPrice(c.ripemd160, 1<<15, callflag.NoneFlag) c.AddMethod(md, desc) desc = newDescriptor("murmur32", smartcontract.ByteArrayType, manifest.NewParameter("data", smartcontract.ByteArrayType), manifest.NewParameter("seed", smartcontract.IntegerType)) md = newMethodAndPrice(c.murmur32, 1<<13, callflag.NoneFlag) c.AddMethod(md, desc) desc = newDescriptor("verifyWithECDsa", smartcontract.BoolType, manifest.NewParameter("message", smartcontract.ByteArrayType), manifest.NewParameter("pubkey", smartcontract.ByteArrayType), manifest.NewParameter("signature", smartcontract.ByteArrayType), manifest.NewParameter("curve", smartcontract.IntegerType)) md = newMethodAndPrice(c.verifyWithECDsa, 1<<15, callflag.NoneFlag) c.AddMethod(md, desc) desc = newDescriptor("bls12381Serialize", smartcontract.ByteArrayType, manifest.NewParameter("g", smartcontract.InteropInterfaceType)) md = newMethodAndPrice(c.bls12381Serialize, 1<<19, callflag.NoneFlag) c.AddMethod(md, desc) desc = newDescriptor("bls12381Deserialize", smartcontract.InteropInterfaceType, manifest.NewParameter("data", smartcontract.ByteArrayType)) md = newMethodAndPrice(c.bls12381Deserialize, 1<<19, callflag.NoneFlag) c.AddMethod(md, desc) desc = newDescriptor("bls12381Equal", smartcontract.BoolType, manifest.NewParameter("x", smartcontract.InteropInterfaceType), manifest.NewParameter("y", smartcontract.InteropInterfaceType)) md = newMethodAndPrice(c.bls12381Equal, 1<<5, callflag.NoneFlag) c.AddMethod(md, desc) desc = newDescriptor("bls12381Add", smartcontract.InteropInterfaceType, manifest.NewParameter("x", smartcontract.InteropInterfaceType), manifest.NewParameter("y", smartcontract.InteropInterfaceType)) md = newMethodAndPrice(c.bls12381Add, 1<<19, callflag.NoneFlag) c.AddMethod(md, desc) desc = newDescriptor("bls12381Mul", smartcontract.InteropInterfaceType, manifest.NewParameter("x", smartcontract.InteropInterfaceType), manifest.NewParameter("mul", smartcontract.ByteArrayType), manifest.NewParameter("neg", smartcontract.BoolType)) md = newMethodAndPrice(c.bls12381Mul, 1<<21, callflag.NoneFlag) c.AddMethod(md, desc) desc = newDescriptor("bls12381Pairing", smartcontract.InteropInterfaceType, manifest.NewParameter("g1", smartcontract.InteropInterfaceType), manifest.NewParameter("g2", smartcontract.InteropInterfaceType)) md = newMethodAndPrice(c.bls12381Pairing, 1<<23, callflag.NoneFlag) c.AddMethod(md, desc) return c } func (c *Crypto) sha256(_ *interop.Context, args []stackitem.Item) stackitem.Item { bs, err := args[0].TryBytes() if err != nil { panic(err) } return stackitem.NewByteArray(hash.Sha256(bs).BytesBE()) } func (c *Crypto) ripemd160(_ *interop.Context, args []stackitem.Item) stackitem.Item { bs, err := args[0].TryBytes() if err != nil { panic(err) } return stackitem.NewByteArray(hash.RipeMD160(bs).BytesBE()) } func (c *Crypto) murmur32(_ *interop.Context, args []stackitem.Item) stackitem.Item { bs, err := args[0].TryBytes() if err != nil { panic(err) } seed := toUint32(args[1]) h := murmur3.SeedSum32(seed, bs) result := make([]byte, 4) binary.LittleEndian.PutUint32(result, h) return stackitem.NewByteArray(result) } func (c *Crypto) verifyWithECDsa(_ *interop.Context, args []stackitem.Item) stackitem.Item { msg, err := args[0].TryBytes() if err != nil { panic(fmt.Errorf("invalid message stackitem: %w", err)) } hashToCheck := hash.Sha256(msg) pubkey, err := args[1].TryBytes() if err != nil { panic(fmt.Errorf("invalid pubkey stackitem: %w", err)) } signature, err := args[2].TryBytes() if err != nil { panic(fmt.Errorf("invalid signature stackitem: %w", err)) } curve, err := curveFromStackitem(args[3]) if err != nil { panic(fmt.Errorf("invalid curve stackitem: %w", err)) } pkey, err := keys.NewPublicKeyFromBytes(pubkey, curve) if err != nil { panic(fmt.Errorf("failed to decode pubkey: %w", err)) } res := pkey.Verify(signature, hashToCheck.BytesBE()) return stackitem.NewBool(res) } func curveFromStackitem(si stackitem.Item) (elliptic.Curve, error) { curve, err := si.TryInteger() if err != nil { return nil, err } if !curve.IsInt64() { return nil, errors.New("not an int64") } c := curve.Int64() switch c { case int64(Secp256k1): return secp256k1.S256(), nil case int64(Secp256r1): return elliptic.P256(), nil default: return nil, errors.New("unsupported curve type") } } func (c *Crypto) bls12381Serialize(_ *interop.Context, args []stackitem.Item) stackitem.Item { val := args[0].(*stackitem.Interop).Value() var res []byte switch p := val.(type) { case *bls12381.G1Affine: compressed := p.Bytes() res = compressed[:] case *bls12381.G1Jac: g1Affine := new(bls12381.G1Affine) g1Affine.FromJacobian(p) compressed := g1Affine.Bytes() res = compressed[:] case *bls12381.G2Affine: compressed := p.Bytes() res = compressed[:] case *bls12381.G2Jac: g2Affine := new(bls12381.G2Affine) g2Affine.FromJacobian(p) compressed := g2Affine.Bytes() res = compressed[:] case *bls12381.GT: compressed := p.Bytes() res = compressed[:] default: panic(errors.New("unknown bls12381 point type")) } return stackitem.NewByteArray(res) } func (c *Crypto) bls12381Deserialize(_ *interop.Context, args []stackitem.Item) stackitem.Item { buf, err := args[0].TryBytes() 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 } return stackitem.NewInterop(res) } func (c *Crypto) bls12381Equal(_ *interop.Context, args []stackitem.Item) stackitem.Item { a := args[0].(*stackitem.Interop).Value() b := args[1].(*stackitem.Interop).Value() var res bool switch x := a.(type) { case *bls12381.G1Affine: y, ok := b.(*bls12381.G1Affine) if !ok { panic(errors.New("y is not bls12381 G1Affine point")) } res = x.Equal(y) case *bls12381.G1Jac: y, ok := b.(*bls12381.G1Jac) if !ok { panic(errors.New("y is not bls12381 G1Jac point")) } res = x.Equal(y) case *bls12381.G2Affine: y, ok := b.(*bls12381.G2Affine) if !ok { panic(errors.New("y is not bls12381 G2Affine point")) } res = x.Equal(y) case *bls12381.G2Jac: y, ok := b.(*bls12381.G2Jac) if !ok { panic(errors.New("y is not bls12381 G2Jac point")) } res = x.Equal(y) default: panic(fmt.Errorf("unexpected x bls12381 point type: %T", x)) } return stackitem.NewBool(res) } func (c *Crypto) bls12381Add(_ *interop.Context, args []stackitem.Item) stackitem.Item { a := args[0].(*stackitem.Interop).Value() b := args[1].(*stackitem.Interop).Value() var res interface{} switch x := a.(type) { case *bls12381.G1Affine: switch y := b.(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("inconsistent point types") } case *bls12381.G1Jac: resJac := new(bls12381.G1Jac) resJac.Set(x) switch y := b.(type) { case *bls12381.G1Affine: resJac.AddMixed(y) case *bls12381.G1Jac: resJac.AddAssign(y) default: panic("inconsistent") } res = resJac case *bls12381.G2Affine: switch y := b.(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("inconsistent") } case *bls12381.G2Jac: resJac := new(bls12381.G2Jac) resJac.Set(x) switch y := b.(type) { case *bls12381.G2Affine: resJac.AddMixed(y) case *bls12381.G2Jac: resJac.AddAssign(y) default: panic("invalid") } res = resJac case *bls12381.GT: resGT := new(bls12381.GT) resGT.Set(x) switch y := b.(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("invalid") } res = resGT default: panic(fmt.Errorf("unexpected bls12381 point type: %T", x)) } return stackitem.NewInterop(res) } func scalarFromBytes(bytes []byte, neg bool) (*fr.Element, error) { alpha := new(fr.Element) if len(bytes) != fr.Bytes { return nil, fmt.Errorf("invalid multiplier: 32-bytes scalar is expected, got %d", len(bytes)) } // The input bytes are in the LE form, so we can't use fr.Element.SetBytesCanonical as far // as it accepts BE. v, err := fr.LittleEndian.Element((*[fr.Bytes]byte)(bytes)) if err != nil { return nil, fmt.Errorf("invalid multiplier: failed to decode scalar: %w", err) } *alpha = v if neg { alpha.Neg(alpha) } return alpha, nil } func (c *Crypto) bls12381Mul(_ *interop.Context, args []stackitem.Item) stackitem.Item { a := args[0].(*stackitem.Interop).Value() mulBytes, err := args[1].TryBytes() if err != nil { panic(fmt.Errorf("invalid multiplier: %w", err)) } neg, err := args[2].TryBool() if err != nil { panic(fmt.Errorf("invalid negative argument: %w", err)) } alpha, err := scalarFromBytes(mulBytes, neg) if err != nil { panic(err) } alphaBi := new(big.Int) alpha.BigInt(alphaBi) var res interface{} switch x := a.(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("unexpected bls12381 point type: %T", x)) } return stackitem.NewInterop(res) } func (c *Crypto) bls12381Pairing(_ *interop.Context, args []stackitem.Item) stackitem.Item { a := args[0].(*stackitem.Interop).Value() b := args[1].(*stackitem.Interop).Value() var ( x *bls12381.G1Affine y *bls12381.G2Affine ) switch p := a.(type) { case *bls12381.G1Affine: x = p case *bls12381.G1Jac: x = new(bls12381.G1Affine) x.FromJacobian(p) default: panic(fmt.Errorf("unexpected bls12381 point type (g1): %T", x)) } switch p := b.(type) { case *bls12381.G2Affine: y = p case *bls12381.G2Jac: y = new(bls12381.G2Affine) y.FromJacobian(p) default: panic(fmt.Errorf("unexpected bls12381 point type (g2): %T", x)) } gt, err := bls12381.Pair([]bls12381.G1Affine{*x}, []bls12381.G2Affine{*y}) if err != nil { panic(fmt.Errorf("failed to perform pairing operation")) } return stackitem.NewInterop(interface{}(>)) } // Metadata implements the Contract interface. func (c *Crypto) Metadata() *interop.ContractMD { return &c.ContractMD } // Initialize implements the Contract interface. func (c *Crypto) Initialize(ic *interop.Context) error { return nil } // OnPersist implements the Contract interface. func (c *Crypto) OnPersist(ic *interop.Context) error { return nil } // PostPersist implements the Contract interface. func (c *Crypto) PostPersist(ic *interop.Context) error { return nil }