Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/node-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ in development and can change in an incompatible way.
| `Cockatrice` | Introduces the ability to update native contracts. Includes a couple of new native smart contract APIs: `keccak256` of native CryptoLib contract and `getCommitteeAddress` of native NeoToken contract. | https://github.com/nspcc-dev/neo-go/pull/3402 <br> https://github.com/neo-project/neo/pull/2942 <br> https://github.com/nspcc-dev/neo-go/pull/3301 <br> https://github.com/neo-project/neo/pull/2925 <br> https://github.com/nspcc-dev/neo-go/pull/3362 <br> https://github.com/neo-project/neo/pull/3154 |
| `Domovoi` | Makes node use executing contract state for the contract call permissions check instead of the state stored in the native Management contract. In C# also makes System.Runtime.GetNotifications interop properly count stack references of notification parameters which prevents users from creating objects that exceed MaxStackSize constraint, but NeoGo has never had this bug, thus proper behaviour is preserved even before HFDomovoi. It results in the fact that some T5 testnet transactions have different ApplicationLogs compared to the C# node, but the node states match. | https://github.com/nspcc-dev/neo-go/pull/3476 <br> https://github.com/neo-project/neo/pull/3290 <br> https://github.com/nspcc-dev/neo-go/pull/3473 <br> https://github.com/neo-project/neo/pull/3290 <br> https://github.com/neo-project/neo/pull/3301 <br> https://github.com/nspcc-dev/neo-go/pull/3485 |
| `Echidna` | Introduces `Designation` event extension with `Old` and `New` roles data to native RoleManagement contract. Adds support for `base64UrlEncode` and `base64UrlDecode` methods to native StdLib contract. Extends the list of required call flags for `registerCandidate`, `unregisterCandidate`and `vote` methods of native NeoToken contract with AllowNotify flag. Enables `onNEP17Payment` method of NEO contract for candidate registration. Introduces constraint for maximum number of execution notifications. Adds support for `recoverSecp256K1` method of native CryptoLib contract. Introduces `setMillisecondsPerBlock` and `getMillisecondsPerBlock` methods of native Policy contract. Introduces support for NotaryAssisted transaction attribute and native Notary contract. | https://github.com/nspcc-dev/neo-go/pull/3554 <br> https://github.com/nspcc-dev/neo-go/pull/3761 <br> https://github.com/nspcc-dev/neo-go/pull/3554 <br> https://github.com/neo-project/neo/pull/3597 <br> https://github.com/nspcc-dev/neo-go/pull/3700 <br> https://github.com/nspcc-dev/neo-go/pull/3640 <br> https://github.com/neo-project/neo/pull/3548 <br> https://github.com/nspcc-dev/neo-go/pull/3863 <br> https://github.com/neo-project/neo/pull/3696 <br> https://github.com/neo-project/neo/pull/3895 <br> https://github.com/nspcc-dev/neo-go/pull/3835 <br> https://github.com/nspcc-dev/neo-go/pull/3854 <br> https://github.com/neo-project/neo/pull/3175 <br> https://github.com/nspcc-dev/neo-go/pull/3478 <br> https://github.com/neo-project/neo/pull/3178 |
| `Faun` | Adds `getBlockedAccounts` method to native Policy contract. Adds `hexEncode` and `hexDecode` methods to native StdLib contract. | https://github.com/nspcc-dev/neo-go/pull/3932 <br> https://github.com/nspcc-dev/neo-go/pull/4004 <br> https://github.com/neo-project/neo/pull/4147 <br> https://github.com/neo-project/neo/pull/4150 |
| `Faun` | Adds `getBlockedAccounts` method to native Policy contract. Adds `hexEncode` and `hexDecode` methods to native StdLib contract. Adds `bn254Add`, `bn254Mul` and `bn254Pairing` methods to native CryptoLib contract. | https://github.com/nspcc-dev/neo-go/pull/3932 <br> https://github.com/nspcc-dev/neo-go/pull/4004 <br> https://github.com/neo-project/neo/pull/4147 <br> https://github.com/neo-project/neo/pull/4150 <br> https://github.com/nspcc-dev/neo-go/pull/4032 <br> https://github.com/neo-project/neo/pull/4185 |

## DB compatibility

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,5 @@ require (
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.9 // indirect
)

replace github.com/nspcc-dev/neo-go/pkg/interop => ./pkg/interop
3 changes: 3 additions & 0 deletions pkg/compiler/native_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ func TestNativeHelpersCompile(t *testing.T) {
{"bls12381Mul", []string{"crypto.Bls12381Point{}", "[]byte{1, 2, 3}", "true"}},
{"bls12381Pairing", []string{"crypto.Bls12381Point{}", "crypto.Bls12381Point{}"}},
{"keccak256", []string{"[]byte{1, 2, 3}"}},
{"bn254Add", []string{"[]byte{1, 2, 3}"}},
{"bn254Mul", []string{"[]byte{1, 2, 3}"}},
{"bn254Pairing", []string{"[]byte{1, 2, 3}"}},
})
runNativeTestCases(t, *cs.ByName(nativenames.StdLib).Metadata(), "std", []nativeTestCase{
{"serialize", []string{"[]byte{1, 2, 3}"}},
Expand Down
3 changes: 2 additions & 1 deletion pkg/config/hardfork.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ const (
HFEchidna // Echidna
// HFFaun represents hard-fork introduced in #3931, #4004 (ported from
// https://github.com/neo-project/neo/pull/4147,
// https://github.com/neo-project/neo/pull/4150).
// https://github.com/neo-project/neo/pull/4150), #4032 (ported from
// https://github.com/neo-project/neo/pull/4185).
HFFaun // Faun
// hfLast denotes the end of hardforks enum. Consider adding new hardforks
// before hfLast.
Expand Down
103 changes: 103 additions & 0 deletions pkg/core/native/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"math/big"

"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
"github.com/consensys/gnark-crypto/ecc/bn254"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
"github.com/nspcc-dev/neo-go/pkg/config"
Expand Down Expand Up @@ -46,6 +47,12 @@ const (
Secp256r1Keccak256 NamedCurveHash = 123
)

const (
FieldElementLength = 32
G1EncodedLength = 64
PairInputLength = 192
)

func newCrypto() *Crypto {
c := &Crypto{ContractMD: *interop.NewContractMD(nativenames.CryptoLib, nativeids.CryptoLib)}
defer c.BuildHFSpecificMD(c.ActiveIn())
Expand Down Expand Up @@ -134,6 +141,22 @@ func newCrypto() *Crypto {
manifest.NewParameter("signature", smartcontract.ByteArrayType))
md = NewMethodAndPrice(c.recoverSecp256K1, 1<<15, callflag.NoneFlag, config.HFEchidna)
c.AddMethod(md, desc)

desc = NewDescriptor("bn254Add", smartcontract.ByteArrayType,
manifest.NewParameter("input", smartcontract.ByteArrayType))
md = NewMethodAndPrice(c.bn254Add, 1<<19, callflag.NoneFlag, config.HFFaun)
c.AddMethod(md, desc)

desc = NewDescriptor("bn254Mul", smartcontract.ByteArrayType,
manifest.NewParameter("input", smartcontract.ByteArrayType))
md = NewMethodAndPrice(c.bn254Mul, 1<<19, callflag.NoneFlag, config.HFFaun)
c.AddMethod(md, desc)

desc = NewDescriptor("bn254Pairing", smartcontract.ByteArrayType,
manifest.NewParameter("input", smartcontract.ByteArrayType))
md = NewMethodAndPrice(c.bn254Pairing, 1<<19, callflag.NoneFlag, config.HFFaun)
c.AddMethod(md, desc)

return c
}

Expand Down Expand Up @@ -278,6 +301,86 @@ func (c *Crypto) recoverSecp256K1(_ *interop.Context, args []stackitem.Item) sta
return stackitem.NewByteArray(pub.SerializeCompressed())
}

func (c *Crypto) bn254Add(_ *interop.Context, args []stackitem.Item) stackitem.Item {
input, err := args[0].TryBytes()
if err != nil {
panic(fmt.Errorf("invalid input argument: %w", err))
}
if len(input) != 2*G1EncodedLength {
panic(fmt.Errorf("invalid BN254 add input length: want %d, got %d", 2*G1EncodedLength, len(input)))
}
var res, first, second bn254.G1Affine
if _, err = first.SetBytes(input[:G1EncodedLength]); err != nil {
return stackitem.NewByteArray(make([]byte, G1EncodedLength))
}
if _, err = second.SetBytes(input[G1EncodedLength:]); err != nil {
return stackitem.NewByteArray(make([]byte, G1EncodedLength))
}
res.Add(&first, &second)
bytes := res.RawBytes()
return stackitem.NewByteArray(bytes[:])
}

func (c *Crypto) bn254Mul(_ *interop.Context, args []stackitem.Item) stackitem.Item {
input, err := args[0].TryBytes()
if err != nil {
panic(fmt.Errorf("invalid input argument: %w", err))
}
if len(input) != G1EncodedLength+FieldElementLength {
panic(fmt.Errorf("invalid BN254 mul input length: want %d, got %d", G1EncodedLength+FieldElementLength, len(input)))
}
var (
res, basePoint bn254.G1Affine
scalar fr.Element
)
if _, err = basePoint.SetBytes(input[:G1EncodedLength]); err != nil {
return stackitem.NewByteArray(make([]byte, G1EncodedLength))
}
scalar.SetBytes(input[G1EncodedLength:])
res.ScalarMultiplication(&basePoint, scalar.BigInt(new(big.Int)))
bytes := res.RawBytes()
return stackitem.NewByteArray(bytes[:])
}

func (c *Crypto) bn254Pairing(_ *interop.Context, args []stackitem.Item) stackitem.Item {
input, err := args[0].TryBytes()
if err != nil {
panic(fmt.Errorf("invalid input argument: %w", err))
}
if len(input)%PairInputLength != 0 {
panic(errors.New("invalid BN254 pairing input length"))
}
var (
pairCount = len(input) / PairInputLength
P = make([]bn254.G1Affine, 0, pairCount)
Q = make([]bn254.G2Affine, 0, pairCount)
g1 bn254.G1Affine
g2 bn254.G2Affine
)
for i := range pairCount {
offset := i * PairInputLength
if _, err = g1.SetBytes(input[offset : offset+G1EncodedLength]); err != nil {
return stackitem.NewByteArray(make([]byte, FieldElementLength))
}
if _, err = g2.SetBytes(input[offset+G1EncodedLength : offset+3*G1EncodedLength]); err != nil {
return stackitem.NewByteArray(make([]byte, FieldElementLength))
}
if g1.IsInfinity() || g2.IsInfinity() {
continue
}
P = append(P, g1)
Q = append(Q, g2)
}
successWord := [FieldElementLength]byte{FieldElementLength - 1: 1}
if len(P) == 0 {
return stackitem.NewByteArray(successWord[:])
}
if ok, err := bn254.PairingCheck(P, Q); err != nil || !ok {
return stackitem.NewByteArray(make([]byte, FieldElementLength))
}
return stackitem.NewByteArray(successWord[:])
}

// koblitzSigToCanonical converts compact Secp256K1 signature representation
// (https://eips.ethereum.org/EIPS/eip-2098#specification) to canonical
// form (https://www.secg.org/sec1-v2.pdf, section 4.1.6) and moves key recovery
Expand Down
128 changes: 128 additions & 0 deletions pkg/core/native/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math"
"math/big"
"slices"
"strings"
"testing"

"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
Expand Down Expand Up @@ -393,3 +394,130 @@ func TestKeccak256(t *testing.T) {

require.Equal(t, expected, actual)
}

func TestBn254(t *testing.T) {
const (
Bn254G1X = "1"
Bn254G1Y = "2"
Bn254DoubleX = "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd3"
Bn254DoubleY = "15ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4"
Bn254G2XIm = "1800deef121f1e7641a819fe67140f7f8f87b140996fbbd1ba87fb145641f404"
Bn254G2XRe = "198e9393920d483a7260bfb731fb5db382322bc5b47fbf6c80f6321231df581"
Bn254G2YIm = "12c85ea5db8c6deb43baf7868f1c5341fd8ed84a82f89ed36e80b6a4a8dd22e1"
Bn254G2YRe = "090689d0585ff0756c27a122072274f89d4d1c6d2f9d3af03d86c6b29b53e2b"
Bn254PairingPositive = "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e104316c97997c17267a1bb67365523b4388e1306d66ea6e4d8f4a4a4b65f5c7d06e286b49c56f6293b2cea30764f0d5eabe5817905468a41f09b77588f692e8b081070efe3d4913dde35bba2513c426d065dee815c478700cef07180fb6146182432428b1490a4f25053d4c20c8723a73de6f0681bd3a8fca41008a6c3c288252d50f18403272e96c10135f96db0f8d0aec25033ebdffb88d2e7956c9bb198ec072462211ebc0a2f042f993d5bd76caf4adb5e99610dcf7c1d992595e6976aa3"
Bn254PairingNegative = "0x142c9123c08a0d7f66d95f3ad637a06b95700bc525073b75610884ef45416e1610104c796f40bfeef3588e996c040d2a88c0b4b85afd2578327b99413c6fe820198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d"
Bn254PairingInvalidG1 = "0x00000000000000000000000000000000000000000000000000000000000000000000000000be00be00bebebebebebe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
)
crypto := newCrypto()

t.Run("Add", func(t *testing.T) {
input := make([]byte, 128)
writeBn254Field(t, Bn254G1X, input, 0)
writeBn254Field(t, Bn254G1Y, input, 32)
writeBn254Field(t, Bn254G1X, input, 64)
writeBn254Field(t, Bn254G1Y, input, 96)

resItem := crypto.bn254Add(nil, []stackitem.Item{stackitem.NewByteArray(input)})
resBytes, err := resItem.TryBytes()
require.NoError(t, err)

expected := make([]byte, 64)
writeBn254Field(t, Bn254DoubleX, expected, 0)
writeBn254Field(t, Bn254DoubleY, expected, 32)
require.Equal(t, expected, resBytes)
})

t.Run("Mul", func(t *testing.T) {
input := make([]byte, 96)
writeBn254Field(t, Bn254G1X, input, 0)
writeBn254Field(t, Bn254G1Y, input, 32)
writeBn254Field(t, "2", input, 64)

resItem := crypto.bn254Mul(nil, []stackitem.Item{stackitem.NewByteArray(input)})
resBytes, err := resItem.TryBytes()
require.NoError(t, err)

expected := make([]byte, 64)
writeBn254Field(t, Bn254DoubleX, expected, 0)
writeBn254Field(t, Bn254DoubleY, expected, 32)
require.Equal(t, expected, resBytes)
})

t.Run("PairingGenerator", func(t *testing.T) {
input := make([]byte, 192)
writeBn254Field(t, Bn254G1X, input, 0)
writeBn254Field(t, Bn254G1Y, input, 32)
writeBn254Field(t, Bn254G2XIm, input, 64)
writeBn254Field(t, Bn254G2XRe, input, 96)
writeBn254Field(t, Bn254G2YIm, input, 128)
writeBn254Field(t, Bn254G2YRe, input, 160)

resItem := crypto.bn254Pairing(nil, []stackitem.Item{stackitem.NewByteArray(input)})
resBytes, err := resItem.TryBytes()
require.NoError(t, err)
require.Equal(t, make([]byte, FieldElementLength), resBytes)
})

t.Run("PairingEmpty", func(t *testing.T) {
resItem := crypto.bn254Pairing(nil, []stackitem.Item{stackitem.NewByteArray(nil)})
resBytes, err := resItem.TryBytes()
require.NoError(t, err)
expected := [FieldElementLength]byte{FieldElementLength - 1: 1}
require.Equal(t, expected[:], resBytes)
})

t.Run("PairingVectors", func(t *testing.T) {
testCases := []struct {
hexStr string
lastByte byte
}{
{Bn254PairingPositive, 1},
{Bn254PairingNegative, 0},
{Bn254PairingInvalidG1, 0},
}

hexToBytes := func(hexStr string) []byte {
if strings.HasPrefix(strings.ToLower(hexStr), "0x") {
hexStr = hexStr[2:]
}
b, err := hex.DecodeString(hexStr)
require.NoError(t, err)
return b
}

for _, tc := range testCases {
input := hexToBytes(tc.hexStr)

resItem := crypto.bn254Pairing(nil, []stackitem.Item{stackitem.NewByteArray(input)})
resBytes, err := resItem.TryBytes()
require.NoError(t, err)

expected := [FieldElementLength]byte{FieldElementLength - 1: tc.lastByte}
require.Equal(t, expected[:], resBytes)
}
})

t.Run("InvalidInputs", func(t *testing.T) {
require.Panics(t, func() {
crypto.bn254Add(nil, []stackitem.Item{stackitem.NewByteArray(nil)})
})
require.Panics(t, func() {
crypto.bn254Mul(nil, []stackitem.Item{stackitem.NewByteArray(nil)})
})
require.Panics(t, func() {
crypto.bn254Pairing(nil, []stackitem.Item{stackitem.NewByteArray([]byte{0})})
})
})
}

func writeBn254Field(t *testing.T, hexStr string, buf []byte, offset int) {
if strings.HasPrefix(strings.ToLower(hexStr), "0x") {
hexStr = hexStr[2:]
}
require.LessOrEqual(t, len(hexStr), 2*FieldElementLength)
hexStr = strings.Repeat("0", 2*FieldElementLength-len(hexStr)) + hexStr
b, err := hex.DecodeString(hexStr)
require.NoError(t, err)
copy(buf[offset:offset+FieldElementLength], b)
}
Loading
Loading