Skip to content

Commit 8d8b9e4

Browse files
author
Tural Devrishev
committed
core: add BN254 curve support
A part of #4029. Signed-off-by: Tural Devrishev <[email protected]>
1 parent 3d162dc commit 8d8b9e4

File tree

8 files changed

+368
-4
lines changed

8 files changed

+368
-4
lines changed

docs/node-configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ in development and can change in an incompatible way.
582582
| `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 |
583583
| `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 |
584584
| `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 |
585-
| `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 |
585+
| `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 |
586586

587587
## DB compatibility
588588

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,5 @@ require (
7272
google.golang.org/grpc v1.75.1 // indirect
7373
google.golang.org/protobuf v1.36.9 // indirect
7474
)
75+
76+
replace github.com/nspcc-dev/neo-go/pkg/interop => ./pkg/interop

pkg/config/hardfork.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ const (
5252
HFEchidna // Echidna
5353
// HFFaun represents hard-fork introduced in #3931, #4004 (ported from
5454
// https://github.com/neo-project/neo/pull/4147,
55-
// https://github.com/neo-project/neo/pull/4150).
55+
// https://github.com/neo-project/neo/pull/4150), #4032 (ported from
56+
// https://github.com/neo-project/neo/pull/4185).
5657
HFFaun // Faun
5758
// hfLast denotes the end of hardforks enum. Consider adding new hardforks
5859
// before hfLast.

pkg/core/native/crypto.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"math/big"
1010

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

50+
const (
51+
FieldElementLength = 32
52+
G1EncodedLength = 64
53+
PairInputLength = 192
54+
)
55+
4956
func newCrypto() *Crypto {
5057
c := &Crypto{ContractMD: *interop.NewContractMD(nativenames.CryptoLib, nativeids.CryptoLib)}
5158
defer c.BuildHFSpecificMD(c.ActiveIn())
@@ -134,6 +141,22 @@ func newCrypto() *Crypto {
134141
manifest.NewParameter("signature", smartcontract.ByteArrayType))
135142
md = NewMethodAndPrice(c.recoverSecp256K1, 1<<15, callflag.NoneFlag, config.HFEchidna)
136143
c.AddMethod(md, desc)
144+
145+
desc = NewDescriptor("bn254Add", smartcontract.ByteArrayType,
146+
manifest.NewParameter("input", smartcontract.ByteArrayType))
147+
md = NewMethodAndPrice(c.bn254Add, 1<<19, callflag.NoneFlag, config.HFFaun)
148+
c.AddMethod(md, desc)
149+
150+
desc = NewDescriptor("bn254Mul", smartcontract.ByteArrayType,
151+
manifest.NewParameter("input", smartcontract.ByteArrayType))
152+
md = NewMethodAndPrice(c.bn254Mul, 1<<19, callflag.NoneFlag, config.HFFaun)
153+
c.AddMethod(md, desc)
154+
155+
desc = NewDescriptor("bn254Pairing", smartcontract.ByteArrayType,
156+
manifest.NewParameter("input", smartcontract.ByteArrayType))
157+
md = NewMethodAndPrice(c.bn254Pairing, 1<<19, callflag.NoneFlag, config.HFFaun)
158+
c.AddMethod(md, desc)
159+
137160
return c
138161
}
139162

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

304+
func (c *Crypto) bn254Add(_ *interop.Context, args []stackitem.Item) stackitem.Item {
305+
input, err := args[0].TryBytes()
306+
if err != nil {
307+
panic(fmt.Errorf("invalid input argument: %w", err))
308+
}
309+
if len(input) != 2*G1EncodedLength {
310+
panic(fmt.Errorf("invalid BN254 add input length: want %d, got %d", 2*G1EncodedLength, len(input)))
311+
}
312+
var res, first, second bn254.G1Affine
313+
if _, err = first.SetBytes(input[:G1EncodedLength]); err != nil {
314+
return stackitem.NewByteArray(make([]byte, G1EncodedLength))
315+
}
316+
if _, err = second.SetBytes(input[G1EncodedLength:]); err != nil {
317+
return stackitem.NewByteArray(make([]byte, G1EncodedLength))
318+
}
319+
res.Add(&first, &second)
320+
bytes := res.RawBytes()
321+
return stackitem.NewByteArray(bytes[:])
322+
}
323+
324+
func (c *Crypto) bn254Mul(_ *interop.Context, args []stackitem.Item) stackitem.Item {
325+
input, err := args[0].TryBytes()
326+
if err != nil {
327+
panic(fmt.Errorf("invalid input argument: %w", err))
328+
}
329+
if len(input) != G1EncodedLength+FieldElementLength {
330+
panic(fmt.Errorf("invalid BN254 mul input length: want %d, got %d", G1EncodedLength+FieldElementLength, len(input)))
331+
}
332+
var (
333+
res, basePoint bn254.G1Affine
334+
scalar fr.Element
335+
)
336+
if _, err = basePoint.SetBytes(input[:G1EncodedLength]); err != nil {
337+
return stackitem.NewByteArray(make([]byte, G1EncodedLength))
338+
}
339+
scalar.SetBytes(input[G1EncodedLength:])
340+
res.ScalarMultiplication(&basePoint, scalar.BigInt(new(big.Int)))
341+
bytes := res.RawBytes()
342+
return stackitem.NewByteArray(bytes[:])
343+
}
344+
345+
func (c *Crypto) bn254Pairing(_ *interop.Context, args []stackitem.Item) stackitem.Item {
346+
input, err := args[0].TryBytes()
347+
if err != nil {
348+
panic(fmt.Errorf("invalid input argument: %w", err))
349+
}
350+
if len(input)%PairInputLength != 0 {
351+
panic(errors.New("invalid BN254 pairing input length"))
352+
}
353+
var (
354+
pairCount = len(input) / PairInputLength
355+
P = make([]bn254.G1Affine, 0, pairCount)
356+
Q = make([]bn254.G2Affine, 0, pairCount)
357+
g1 bn254.G1Affine
358+
g2 bn254.G2Affine
359+
)
360+
for i := range pairCount {
361+
offset := i * PairInputLength
362+
if _, err = g1.SetBytes(input[offset : offset+G1EncodedLength]); err != nil {
363+
return stackitem.NewByteArray(make([]byte, FieldElementLength))
364+
}
365+
if _, err = g2.SetBytes(input[offset+G1EncodedLength : offset+3*G1EncodedLength]); err != nil {
366+
return stackitem.NewByteArray(make([]byte, FieldElementLength))
367+
}
368+
if g1.IsInfinity() || g2.IsInfinity() {
369+
continue
370+
}
371+
P = append(P, g1)
372+
Q = append(Q, g2)
373+
}
374+
successWord := [FieldElementLength]byte{FieldElementLength - 1: 1}
375+
if len(P) == 0 {
376+
return stackitem.NewByteArray(successWord[:])
377+
}
378+
if ok, err := bn254.PairingCheck(P, Q); err != nil || !ok {
379+
return stackitem.NewByteArray(make([]byte, FieldElementLength))
380+
}
381+
return stackitem.NewByteArray(successWord[:])
382+
}
383+
281384
// koblitzSigToCanonical converts compact Secp256K1 signature representation
282385
// (https://eips.ethereum.org/EIPS/eip-2098#specification) to canonical
283386
// form (https://www.secg.org/sec1-v2.pdf, section 4.1.6) and moves key recovery

pkg/core/native/crypto_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"math"
88
"math/big"
99
"slices"
10+
"strings"
1011
"testing"
1112

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

394395
require.Equal(t, expected, actual)
395396
}
397+
398+
func TestBn254(t *testing.T) {
399+
const (
400+
Bn254G1X = "1"
401+
Bn254G1Y = "2"
402+
Bn254DoubleX = "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd3"
403+
Bn254DoubleY = "15ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4"
404+
Bn254G2XIm = "1800deef121f1e7641a819fe67140f7f8f87b140996fbbd1ba87fb145641f404"
405+
Bn254G2XRe = "198e9393920d483a7260bfb731fb5db382322bc5b47fbf6c80f6321231df581"
406+
Bn254G2YIm = "12c85ea5db8c6deb43baf7868f1c5341fd8ed84a82f89ed36e80b6a4a8dd22e1"
407+
Bn254G2YRe = "090689d0585ff0756c27a122072274f89d4d1c6d2f9d3af03d86c6b29b53e2b"
408+
Bn254PairingPositive = "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e104316c97997c17267a1bb67365523b4388e1306d66ea6e4d8f4a4a4b65f5c7d06e286b49c56f6293b2cea30764f0d5eabe5817905468a41f09b77588f692e8b081070efe3d4913dde35bba2513c426d065dee815c478700cef07180fb6146182432428b1490a4f25053d4c20c8723a73de6f0681bd3a8fca41008a6c3c288252d50f18403272e96c10135f96db0f8d0aec25033ebdffb88d2e7956c9bb198ec072462211ebc0a2f042f993d5bd76caf4adb5e99610dcf7c1d992595e6976aa3"
409+
Bn254PairingNegative = "0x142c9123c08a0d7f66d95f3ad637a06b95700bc525073b75610884ef45416e1610104c796f40bfeef3588e996c040d2a88c0b4b85afd2578327b99413c6fe820198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d"
410+
Bn254PairingInvalidG1 = "0x00000000000000000000000000000000000000000000000000000000000000000000000000be00be00bebebebebebe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
411+
)
412+
crypto := newCrypto()
413+
414+
t.Run("Add", func(t *testing.T) {
415+
input := make([]byte, 128)
416+
writeBn254Field(t, Bn254G1X, input, 0)
417+
writeBn254Field(t, Bn254G1Y, input, 32)
418+
writeBn254Field(t, Bn254G1X, input, 64)
419+
writeBn254Field(t, Bn254G1Y, input, 96)
420+
421+
resItem := crypto.bn254Add(nil, []stackitem.Item{stackitem.NewByteArray(input)})
422+
resBytes, err := resItem.TryBytes()
423+
require.NoError(t, err)
424+
425+
expected := make([]byte, 64)
426+
writeBn254Field(t, Bn254DoubleX, expected, 0)
427+
writeBn254Field(t, Bn254DoubleY, expected, 32)
428+
require.Equal(t, expected, resBytes)
429+
})
430+
431+
t.Run("Mul", func(t *testing.T) {
432+
input := make([]byte, 96)
433+
writeBn254Field(t, Bn254G1X, input, 0)
434+
writeBn254Field(t, Bn254G1Y, input, 32)
435+
writeBn254Field(t, "2", input, 64)
436+
437+
resItem := crypto.bn254Mul(nil, []stackitem.Item{stackitem.NewByteArray(input)})
438+
resBytes, err := resItem.TryBytes()
439+
require.NoError(t, err)
440+
441+
expected := make([]byte, 64)
442+
writeBn254Field(t, Bn254DoubleX, expected, 0)
443+
writeBn254Field(t, Bn254DoubleY, expected, 32)
444+
require.Equal(t, expected, resBytes)
445+
})
446+
447+
t.Run("PairingGenerator", func(t *testing.T) {
448+
input := make([]byte, 192)
449+
writeBn254Field(t, Bn254G1X, input, 0)
450+
writeBn254Field(t, Bn254G1Y, input, 32)
451+
writeBn254Field(t, Bn254G2XIm, input, 64)
452+
writeBn254Field(t, Bn254G2XRe, input, 96)
453+
writeBn254Field(t, Bn254G2YIm, input, 128)
454+
writeBn254Field(t, Bn254G2YRe, input, 160)
455+
456+
resItem := crypto.bn254Pairing(nil, []stackitem.Item{stackitem.NewByteArray(input)})
457+
resBytes, err := resItem.TryBytes()
458+
require.NoError(t, err)
459+
require.Equal(t, make([]byte, FieldElementLength), resBytes)
460+
})
461+
462+
t.Run("PairingEmpty", func(t *testing.T) {
463+
resItem := crypto.bn254Pairing(nil, []stackitem.Item{stackitem.NewByteArray(nil)})
464+
resBytes, err := resItem.TryBytes()
465+
require.NoError(t, err)
466+
expected := [FieldElementLength]byte{FieldElementLength - 1: 1}
467+
require.Equal(t, expected[:], resBytes)
468+
})
469+
470+
t.Run("PairingVectors", func(t *testing.T) {
471+
testCases := []struct {
472+
hexStr string
473+
lastByte byte
474+
}{
475+
{Bn254PairingPositive, 1},
476+
{Bn254PairingNegative, 0},
477+
{Bn254PairingInvalidG1, 0},
478+
}
479+
480+
hexToBytes := func(hexStr string) []byte {
481+
if strings.HasPrefix(strings.ToLower(hexStr), "0x") {
482+
hexStr = hexStr[2:]
483+
}
484+
b, err := hex.DecodeString(hexStr)
485+
require.NoError(t, err)
486+
return b
487+
}
488+
489+
for _, tc := range testCases {
490+
input := hexToBytes(tc.hexStr)
491+
492+
resItem := crypto.bn254Pairing(nil, []stackitem.Item{stackitem.NewByteArray(input)})
493+
resBytes, err := resItem.TryBytes()
494+
require.NoError(t, err)
495+
496+
expected := [FieldElementLength]byte{FieldElementLength - 1: tc.lastByte}
497+
require.Equal(t, expected[:], resBytes)
498+
}
499+
})
500+
501+
t.Run("InvalidInputs", func(t *testing.T) {
502+
require.Panics(t, func() {
503+
crypto.bn254Add(nil, []stackitem.Item{stackitem.NewByteArray(nil)})
504+
})
505+
require.Panics(t, func() {
506+
crypto.bn254Mul(nil, []stackitem.Item{stackitem.NewByteArray(nil)})
507+
})
508+
require.Panics(t, func() {
509+
crypto.bn254Pairing(nil, []stackitem.Item{stackitem.NewByteArray([]byte{0})})
510+
})
511+
})
512+
}
513+
514+
func writeBn254Field(t *testing.T, hexStr string, buf []byte, offset int) {
515+
if strings.HasPrefix(strings.ToLower(hexStr), "0x") {
516+
hexStr = hexStr[2:]
517+
}
518+
require.LessOrEqual(t, len(hexStr), 2*FieldElementLength)
519+
hexStr = strings.Repeat("0", 2*FieldElementLength-len(hexStr)) + hexStr
520+
b, err := hex.DecodeString(hexStr)
521+
require.NoError(t, err)
522+
copy(buf[offset:offset+FieldElementLength], b)
523+
}

0 commit comments

Comments
 (0)