Skip to content

Commit 0d9946b

Browse files
author
Tural Devrishev
committed
core: add Ethereum-compatible aliases for BLS12-381
Close #4041. Signed-off-by: Tural Devrishev <tural@nspcc.ru>
1 parent 9e9e8e1 commit 0d9946b

File tree

8 files changed

+519
-0
lines changed

8 files changed

+519
-0
lines changed

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/compiler/native_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ func TestNativeHelpersCompile(t *testing.T) {
251251
{"bls12381Mul", []string{"crypto.Bls12381Point{}", "[]byte{1, 2, 3}", "true"}},
252252
{"bls12381Pairing", []string{"crypto.Bls12381Point{}", "crypto.Bls12381Point{}"}},
253253
{"keccak256", []string{"[]byte{1, 2, 3}"}},
254+
{"bls12381MultiExp", []string{"[]crypto.Bls12381Pair{}"}},
254255
})
255256
runNativeTestCases(t, *cs.ByName(nativenames.StdLib).Metadata(), "std", []nativeTestCase{
256257
{"serialize", []string{"[]byte{1, 2, 3}"}},

pkg/config/hardfork.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const (
5454
// https://github.com/neo-project/neo/pull/4147,
5555
// https://github.com/neo-project/neo/pull/4150), #4057 (ported from
5656
// https://github.com/neo-project/neo/pull/4278).
57+
// https://github.com/neo-project/neo/pull/4150), #4050 (ported from
58+
// https://github.com/neo-project/neo/pull/4186).
5759
HFFaun // Faun
5860
// hfLast denotes the end of hardforks enum. Consider adding new hardforks
5961
// before hfLast.

pkg/core/native/crypto.go

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66
"encoding/binary"
77
"errors"
88
"fmt"
9+
"github.com/consensys/gnark-crypto/ecc/bls12-381/fp"
910
"math/big"
1011

12+
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
1113
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
1214
"github.com/decred/dcrd/dcrec/secp256k1/v4"
1315
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
@@ -46,6 +48,16 @@ const (
4648
Secp256r1Keccak256 NamedCurveHash = 123
4749
)
4850

51+
const (
52+
Bls12FieldElementLength = 64
53+
Bls12G1EncodedLength = 2 * Bls12FieldElementLength
54+
55+
Bls12G2EncodedLength = 4 * Bls12FieldElementLength
56+
// Bls12381MultiExpMaxPairs is the maximum number of (point, scalar) pairs
57+
// accepted by the Bls12381MultiExp native contract.
58+
Bls12381MultiExpMaxPairs = 128
59+
)
60+
4961
func newCrypto() *Crypto {
5062
c := &Crypto{ContractMD: *interop.NewContractMD(nativenames.CryptoLib, nativeids.CryptoLib)}
5163
defer c.BuildHFSpecificMD(c.ActiveIn())
@@ -134,6 +146,11 @@ func newCrypto() *Crypto {
134146
manifest.NewParameter("signature", smartcontract.ByteArrayType))
135147
md = NewMethodAndPrice(c.recoverSecp256K1, 1<<15, callflag.NoneFlag, config.HFEchidna)
136148
c.AddMethod(md, desc)
149+
150+
desc = NewDescriptor("bls12381MultiExp", smartcontract.InteropInterfaceType,
151+
manifest.NewParameter("pairs", smartcontract.ArrayType))
152+
md = NewMethodAndPrice(c.bls12381MultiExp, 1<<23, callflag.NoneFlag, config.HFFaun)
153+
c.AddMethod(md, desc)
137154
return c
138155
}
139156

@@ -335,6 +352,185 @@ func (c *Crypto) bls12381Deserialize(_ *interop.Context, args []stackitem.Item)
335352
return stackitem.NewInterop(*p)
336353
}
337354

355+
func (c *Crypto) bls12381EthereumPointSerialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
356+
return serializeEthereumPoint(args[0])
357+
}
358+
359+
func (c *Crypto) bls12381EthereumPointDeserialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
360+
return deserializeEthereumPoint(args[0])
361+
}
362+
363+
func (c *Crypto) bls12381EthereumPointsSerialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
364+
points, ok := args[0].Value().([]stackitem.Item)
365+
if !ok {
366+
panic("points must be with array type")
367+
}
368+
res := make([]stackitem.Item, 0, len(points))
369+
for _, p := range points {
370+
res = append(res, serializeEthereumPoint(p))
371+
}
372+
return stackitem.NewArray(res)
373+
}
374+
375+
func (c *Crypto) bls12381EthereumPointsDeserialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
376+
points, ok := args[0].Value().([]stackitem.Item)
377+
if !ok {
378+
panic("points must be with array type")
379+
}
380+
res := make([]stackitem.Item, 0, len(points))
381+
for _, p := range points {
382+
res = append(res, deserializeEthereumPoint(p))
383+
}
384+
return stackitem.NewArray(res)
385+
}
386+
387+
func (c *Crypto) bls12381EthereumPointsAndScalarsSerialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
388+
pairs, ok := args[0].Value().([]stackitem.Item)
389+
if !ok {
390+
panic("points must be with array type")
391+
}
392+
res := make([]stackitem.Item, 0, len(pairs))
393+
for _, si := range pairs {
394+
if si.Type() != stackitem.ArrayT && si.Type() != stackitem.StructT {
395+
panic("pair must be Array or Struct")
396+
}
397+
pair := si.Value().([]stackitem.Item)
398+
if len(pair) != 2 {
399+
panic("pair must contain point and scalar")
400+
}
401+
scalarLE, err := pair[1].TryBytes()
402+
if err != nil {
403+
panic(fmt.Errorf("can't get scalar bytes: %w", err))
404+
}
405+
scalarBytes := make([]byte, fr.Bytes)
406+
copy(scalarBytes, scalarLE)
407+
res = append(res, stackitem.NewArray([]stackitem.Item{
408+
serializeEthereumPoint(pair[0]),
409+
stackitem.NewByteArray(scalarBytes),
410+
}))
411+
}
412+
return stackitem.NewArray(res)
413+
}
414+
415+
func (c *Crypto) bls12381EthereumPointsAndScalarsDeserialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
416+
pairs, ok := args[0].Value().([]stackitem.Item)
417+
if !ok {
418+
panic("points must be with array type")
419+
}
420+
res := make([]stackitem.Item, 0, len(pairs))
421+
for _, si := range pairs {
422+
if si.Type() != stackitem.ArrayT && si.Type() != stackitem.StructT {
423+
panic("pair must be Array or Struct")
424+
}
425+
pair := si.Value().([]stackitem.Item)
426+
if len(pair) != 2 {
427+
panic("pair must contain point and scalar")
428+
}
429+
scalarBytes, err := pair[1].TryBytes()
430+
if err != nil {
431+
panic(fmt.Errorf("invalid multiplier: %w", err))
432+
}
433+
scalar, err := scalarFromBytes(scalarBytes, false)
434+
if err != nil {
435+
panic(fmt.Errorf("can't get scalar from bytes: %w", err))
436+
}
437+
res = append(res, stackitem.NewArray([]stackitem.Item{
438+
deserializeEthereumPoint(pair[0]),
439+
stackitem.NewBigInteger(scalar.BigInt(new(big.Int))),
440+
}))
441+
}
442+
return stackitem.NewArray(res)
443+
}
444+
445+
func serializeEthereumPoint(point stackitem.Item) stackitem.Item {
446+
if point.Type() != stackitem.InteropT {
447+
panic(fmt.Errorf("point type must be an %s, got %s", stackitem.InteropT, point.Type()))
448+
}
449+
p, ok := point.Value().(blsPoint)
450+
if !ok {
451+
panic("serialized item is not a bls12381 point")
452+
}
453+
var (
454+
g1 *bls12381.G1Affine
455+
g2 *bls12381.G2Affine
456+
)
457+
switch p := p.point.(type) {
458+
case *bls12381.G1Affine:
459+
g1 = p
460+
case *bls12381.G1Jac:
461+
g1 = new(bls12381.G1Affine).FromJacobian(p)
462+
case *bls12381.G2Affine:
463+
g2 = p
464+
case *bls12381.G2Jac:
465+
g2 = new(bls12381.G2Affine).FromJacobian(p)
466+
default:
467+
panic("invalid point type")
468+
}
469+
if g1 != nil {
470+
if g1.IsInfinity() {
471+
return stackitem.NewByteArray(make([]byte, Bls12G1EncodedLength))
472+
}
473+
bytes := g1.RawBytes()
474+
return stackitem.NewByteArray(toEthereum(bytes[:]))
475+
}
476+
if g2.IsInfinity() {
477+
return stackitem.NewByteArray(make([]byte, Bls12G2EncodedLength))
478+
}
479+
bytes := g2.RawBytes()
480+
return stackitem.NewByteArray(toEthereum(bytes[:]))
481+
}
482+
483+
func deserializeEthereumPoint(point stackitem.Item) stackitem.Item {
484+
buf, err := point.TryBytes()
485+
if err != nil {
486+
panic(fmt.Errorf("invalid serialized ethereum point: %w", err))
487+
}
488+
if l := len(buf); l != Bls12G1EncodedLength && l != Bls12G2EncodedLength {
489+
panic(fmt.Errorf("ethereum point must be with length %d or %d bytes, got %d bytes", Bls12G1EncodedLength, Bls12G2EncodedLength, l))
490+
}
491+
var p any
492+
if len(buf) == Bls12G1EncodedLength {
493+
g1 := &bls12381.G1Affine{}
494+
_, err = g1.SetBytes(fromEthereum(buf))
495+
p = g1
496+
} else {
497+
g2 := &bls12381.G2Affine{}
498+
_, err = g2.SetBytes(fromEthereum(buf))
499+
p = g2
500+
}
501+
if err != nil {
502+
panic(err)
503+
}
504+
return stackitem.NewInterop(blsPoint{point: p})
505+
}
506+
507+
func fromEthereum(data []byte) []byte {
508+
var (
509+
count = len(data) / Bls12FieldElementLength
510+
res = make([]byte, count*fp.Bytes)
511+
)
512+
for i := range count {
513+
for _, b := range data[i*Bls12FieldElementLength : (i+1)*Bls12FieldElementLength-fp.Bytes] {
514+
if b != 0 {
515+
panic("bls12-381 field element overflow")
516+
}
517+
}
518+
copy(res[i*fp.Bytes:(i+1)*fp.Bytes], data[(i+1)*Bls12FieldElementLength-fp.Bytes:(i+1)*Bls12FieldElementLength])
519+
}
520+
return res
521+
}
522+
523+
func toEthereum(data []byte) []byte {
524+
var (
525+
count = len(data) / fp.Bytes
526+
res = make([]byte, count*Bls12FieldElementLength)
527+
)
528+
for i := range count {
529+
copy(res[(i+1)*Bls12FieldElementLength-fp.Bytes:(i+1)*Bls12FieldElementLength], data[i*fp.Bytes:(i+1)*fp.Bytes])
530+
}
531+
return res
532+
}
533+
338534
func (c *Crypto) bls12381Equal(_ *interop.Context, args []stackitem.Item) stackitem.Item {
339535
a, okA := args[0].(*stackitem.Interop).Value().(blsPoint)
340536
b, okB := args[1].(*stackitem.Interop).Value().(blsPoint)
@@ -362,6 +558,78 @@ func (c *Crypto) bls12381Add(_ *interop.Context, args []stackitem.Item) stackite
362558
return stackitem.NewInterop(p)
363559
}
364560

561+
func (c *Crypto) bls12381MultiExp(_ *interop.Context, args []stackitem.Item) stackitem.Item {
562+
pairs := args[0].Value().([]stackitem.Item)
563+
if len(pairs) == 0 {
564+
panic("BLS12-381 multi exponent requires at least one pair")
565+
}
566+
if len(pairs) > Bls12381MultiExpMaxPairs {
567+
panic(fmt.Sprintf("BLS12-381 multi exponent supports at most %d pairs", Bls12381MultiExpMaxPairs))
568+
}
569+
var (
570+
useG2 int // 1 => use G2
571+
accumulator blsPoint
572+
)
573+
for _, si := range pairs {
574+
if si.Type() != stackitem.ArrayT && si.Type() != stackitem.StructT {
575+
panic("BLS12-381 multi exponent pair must be Array or Struct")
576+
}
577+
pair := si.Value().([]stackitem.Item)
578+
if len(pair) != 2 {
579+
panic("BLS12-381 multi exponent pair must contain point and scalar")
580+
}
581+
if pair[0].Type() != stackitem.InteropT {
582+
panic("BLS12-381 multi exponent requires interop points")
583+
}
584+
point, ok := pair[0].Value().(blsPoint)
585+
if !ok {
586+
panic("BLS12-381 multi exponent interop must contain blsPoint")
587+
}
588+
switch point.point.(type) {
589+
case *bls12381.G1Jac, *bls12381.G1Affine:
590+
useG2 = ensureGroupType(useG2, -1)
591+
case *bls12381.G2Jac, *bls12381.G2Affine:
592+
useG2 = ensureGroupType(useG2, 1)
593+
default:
594+
panic("BLS12-381 type mismatch")
595+
}
596+
mulBytes, err := pair[1].TryBytes()
597+
if err != nil {
598+
panic(fmt.Errorf("invalid multiplier: %w", err))
599+
}
600+
alpha, err := scalarFromBytes(mulBytes, false)
601+
if err != nil {
602+
panic(err)
603+
}
604+
if alpha.BigInt(new(big.Int)).Sign() == 0 {
605+
continue
606+
}
607+
res, err := blsPointMul(point, alpha.BigInt(new(big.Int)))
608+
if err != nil {
609+
panic(err)
610+
}
611+
if accumulator.point == nil {
612+
accumulator.point = res.point
613+
} else if accumulator, err = blsPointAdd(accumulator, res); err != nil {
614+
panic(err)
615+
}
616+
}
617+
if useG2 == 0 {
618+
panic("BLS12-381 multi exponent requires at least one valid pair")
619+
}
620+
return stackitem.NewInterop(accumulator)
621+
}
622+
623+
func ensureGroupType(useG2, isG2 int) int {
624+
if useG2 == 0 {
625+
return isG2
626+
}
627+
if useG2 != isG2 {
628+
panic("BLS12-381 multi exponent cannot mix groups")
629+
}
630+
return isG2
631+
}
632+
365633
func scalarFromBytes(bytes []byte, neg bool) (*fr.Element, error) {
366634
alpha := new(fr.Element)
367635
if len(bytes) != fr.Bytes {

0 commit comments

Comments
 (0)