examples: add compatibility example for Groth16 veification
Port the C# contract provided in the https://github.com/neo-project/neo/issues/2647#issuecomment-1129849870 and add an integration test for it. Part of the #3002. Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
parent
4598f3d3c2
commit
0a3260c22c
8 changed files with 135 additions and 0 deletions
|
@ -1011,6 +1011,10 @@ func TestCompileExamples(t *testing.T) {
|
||||||
// there are also a couple of files inside the `/examples` which doesn't need to be compiled
|
// there are also a couple of files inside the `/examples` which doesn't need to be compiled
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if info.Name() == "zkp" {
|
||||||
|
// A set of special ZKP-related examples, they have their own tests.
|
||||||
|
continue
|
||||||
|
}
|
||||||
t.Run(info.Name(), func(t *testing.T) {
|
t.Run(info.Name(), func(t *testing.T) {
|
||||||
infos, err := os.ReadDir(filepath.Join(examplePath, info.Name()))
|
infos, err := os.ReadDir(filepath.Join(examplePath, info.Name()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
5
examples/zkp/xor_compat/go.mod
Normal file
5
examples/zkp/xor_compat/go.mod
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module github.com/nspcc-dev/neo-go/examples/zkp/xor
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20230606150208-a2daad6ba614
|
2
examples/zkp/xor_compat/go.sum
Normal file
2
examples/zkp/xor_compat/go.sum
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20230606150208-a2daad6ba614 h1:MiDBj73HNgPUbJRpXWLXrsGvX4rkYVDrdSmfOwivGR8=
|
||||||
|
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20230606150208-a2daad6ba614/go.mod h1:ZUuXOkdtHZgaC13za/zMgXfQFncZ0jLzfQTe+OsDOtg=
|
84
examples/zkp/xor_compat/verify.go
Normal file
84
examples/zkp/xor_compat/verify.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Package xor contains an example of smart contract that uses BLS12-381
|
||||||
|
// curves interoperability functionality to verify provided proof against provided
|
||||||
|
// public input. This example is a full copy of C# smart contract presented in
|
||||||
|
// https://github.com/neo-project/neo/issues/2647#issuecomment-1129849870 and is
|
||||||
|
// aimed to check the compatibility of BLS12-381 interoperability functionality
|
||||||
|
// between the NeoC# and NeoGo nodes. This example is circuit-specific and can
|
||||||
|
// not be deployed into chain to verify other circuits.
|
||||||
|
//
|
||||||
|
// This example is constructed to prove that a&b=0 where a and b are private input.
|
||||||
|
package xor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants needed for verification should be obtained via MPC process.
|
||||||
|
var (
|
||||||
|
// G1 Affine.
|
||||||
|
alpha = []byte{160, 106, 167, 155, 164, 170, 67, 158, 237, 78, 91, 7, 243, 191, 186, 221, 27, 97, 6, 190, 193, 204, 85, 206, 83, 56, 3, 209, 132, 249, 221, 94, 124, 20, 245, 113, 143, 70, 245, 159, 104, 213, 37, 151, 209, 125, 160, 143}
|
||||||
|
// G2 Affine.
|
||||||
|
beta = []byte{163, 91, 30, 20, 61, 202, 142, 33, 164, 33, 215, 106, 219, 39, 136, 96, 112, 254, 117, 55, 156, 44, 55, 125, 240, 63, 166, 206, 157, 17, 201, 11, 33, 172, 226, 58, 254, 202, 46, 128, 2, 179, 227, 37, 230, 127, 121, 118, 6, 59, 84, 145, 104, 196, 68, 37, 209, 54, 86, 148, 155, 251, 36, 110, 127, 190, 205, 52, 100, 136, 226, 196, 249, 172, 122, 215, 230, 42, 92, 175, 190, 120, 19, 80, 56, 148, 236, 157, 108, 74, 45, 29, 157, 243, 96, 94}
|
||||||
|
// G2 Affine.
|
||||||
|
gamma = []byte{183, 69, 47, 108, 115, 173, 254, 203, 89, 67, 183, 224, 176, 26, 127, 132, 89, 162, 99, 241, 66, 228, 177, 17, 57, 85, 3, 13, 148, 88, 162, 54, 220, 189, 33, 172, 38, 192, 116, 236, 13, 115, 219, 201, 51, 166, 253, 240, 12, 32, 77, 82, 161, 189, 240, 198, 148, 184, 17, 92, 162, 145, 166, 55, 252, 245, 194, 95, 71, 208, 215, 23, 19, 95, 138, 147, 149, 26, 35, 108, 141, 25, 139, 103, 59, 48, 189, 88, 204, 100, 255, 116, 194, 229, 157, 5}
|
||||||
|
// G2 Affine.
|
||||||
|
delta = []byte{129, 78, 83, 175, 159, 103, 127, 217, 80, 213, 0, 194, 108, 30, 210, 241, 138, 209, 0, 164, 117, 32, 68, 102, 121, 36, 40, 65, 89, 205, 198, 1, 14, 144, 196, 236, 176, 214, 119, 139, 225, 118, 215, 185, 36, 216, 183, 27, 22, 126, 193, 21, 173, 212, 250, 104, 25, 69, 107, 40, 199, 160, 228, 239, 112, 102, 144, 85, 58, 109, 122, 73, 221, 170, 145, 188, 60, 9, 228, 178, 36, 227, 175, 140, 40, 181, 158, 175, 91, 189, 92, 169, 90, 90, 30, 153}
|
||||||
|
// A set of G1 Affine points.
|
||||||
|
ic = [][]byte{
|
||||||
|
{174, 152, 253, 159, 101, 142, 227, 5, 166, 71, 152, 207, 32, 152, 56, 172, 191, 43, 184, 28, 148, 40, 224, 42, 135, 137, 181, 215, 96, 34, 200, 127, 77, 151, 165, 11, 130, 57, 91, 83, 71, 38, 253, 159, 103, 191, 139, 120},
|
||||||
|
{177, 158, 199, 19, 137, 211, 161, 248, 118, 149, 250, 145, 46, 221, 160, 86, 40, 165, 110, 198, 160, 203, 188, 84, 210, 83, 159, 176, 113, 111, 10, 235, 192, 243, 242, 110, 188, 210, 98, 199, 74, 66, 118, 251, 3, 188, 58, 84},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// VerifyProof verifies the given proof represented as three BLS12-381 points
|
||||||
|
// against the public information represented as a list of serialized 32-bytes
|
||||||
|
// field elements in the LE form. The verification process follows the GROTH-16
|
||||||
|
// proving system and is taken from the
|
||||||
|
// https://github.com/neo-project/neo/issues/2647#issuecomment-1002893109 without
|
||||||
|
// changes. The verification process checks the following equality:
|
||||||
|
// A * B = alpha * beta + sum(pub_input[i] * (beta * u_i(x) + alpha * v_i(x) + w_i(x)) / gamma) * gamma + C * delta
|
||||||
|
func VerifyProof(a []byte, b []byte, c []byte, publicInput [][]byte) bool {
|
||||||
|
alphaPoint := crypto.Bls12381Deserialize(alpha)
|
||||||
|
betaPoint := crypto.Bls12381Deserialize(beta)
|
||||||
|
gammaInversePoint := crypto.Bls12381Deserialize(gamma)
|
||||||
|
deltaPoint := crypto.Bls12381Deserialize(delta)
|
||||||
|
|
||||||
|
aPoint := crypto.Bls12381Deserialize(a)
|
||||||
|
bPoint := crypto.Bls12381Deserialize(b)
|
||||||
|
cPoint := crypto.Bls12381Deserialize(c)
|
||||||
|
|
||||||
|
// Equation left1: A*B
|
||||||
|
lt := crypto.Bls12381Pairing(aPoint, bPoint)
|
||||||
|
|
||||||
|
// Equation right1: alpha*beta
|
||||||
|
rt1 := crypto.Bls12381Pairing(alphaPoint, betaPoint)
|
||||||
|
|
||||||
|
// Equation right2: sum(pub_input[i]*(beta*u_i(x)+alpha*v_i(x)+w_i(x))/gamma)*gamma
|
||||||
|
inputlen := len(publicInput)
|
||||||
|
iclen := len(ic)
|
||||||
|
|
||||||
|
if iclen != inputlen+1 {
|
||||||
|
panic("error: inputlen or iclen")
|
||||||
|
}
|
||||||
|
icPoints := make([]crypto.Bls12381Point, iclen)
|
||||||
|
for i := 0; i < iclen; i++ {
|
||||||
|
icPoints[i] = crypto.Bls12381Deserialize(ic[i])
|
||||||
|
}
|
||||||
|
acc := icPoints[0]
|
||||||
|
for i := 0; i < inputlen; i++ {
|
||||||
|
scalar := publicInput[i] // 32-bytes LE field element.
|
||||||
|
temp := crypto.Bls12381Mul(icPoints[i+1], scalar, false)
|
||||||
|
acc = crypto.Bls12381Add(acc, temp)
|
||||||
|
}
|
||||||
|
rt2 := crypto.Bls12381Pairing(acc, gammaInversePoint)
|
||||||
|
|
||||||
|
// Equation right3: C*delta
|
||||||
|
rt3 := crypto.Bls12381Pairing(cPoint, deltaPoint)
|
||||||
|
|
||||||
|
// Check equality.
|
||||||
|
t1 := crypto.Bls12381Add(rt1, rt2)
|
||||||
|
t2 := crypto.Bls12381Add(t1, rt3)
|
||||||
|
|
||||||
|
return util.Equals(lt, t2)
|
||||||
|
}
|
3
examples/zkp/xor_compat/verify.yml
Normal file
3
examples/zkp/xor_compat/verify.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
name: "Groth16 proof compatibility example"
|
||||||
|
sourceurl: https://github.com/nspcc-dev/neo-go/
|
||||||
|
supportedstandards: []
|
|
@ -57,6 +57,10 @@ func TestCompiler(t *testing.T) {
|
||||||
// there is also a couple of files inside the `examplePath` which don't need to be compiled
|
// there is also a couple of files inside the `examplePath` which don't need to be compiled
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if info.Name() == "zkp" {
|
||||||
|
// A set of special ZKP-related examples, they have their own tests.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
targetPath := filepath.Join(examplePath, info.Name())
|
targetPath := filepath.Join(examplePath, info.Name())
|
||||||
require.NoError(t, compileFile(targetPath), info.Name())
|
require.NoError(t, compileFile(targetPath), info.Name())
|
||||||
|
|
|
@ -231,6 +231,8 @@ func scalarFromBytes(bytes []byte, neg bool) (*fr.Element, error) {
|
||||||
}
|
}
|
||||||
// The input bytes are in the LE form, so we can't use fr.Element.SetBytesCanonical as far
|
// The input bytes are in the LE form, so we can't use fr.Element.SetBytesCanonical as far
|
||||||
// as it accepts BE.
|
// as it accepts BE.
|
||||||
|
// TODO: ensure that LE is really used in the C# node. More common way is to use BE form, and these
|
||||||
|
// lines of code are here only because that's the way how C# example works (https://github.com/neo-project/neo/issues/2647#issuecomment-1129849870).
|
||||||
v, err := fr.LittleEndian.Element((*[fr.Bytes]byte)(bytes))
|
v, err := fr.LittleEndian.Element((*[fr.Bytes]byte)(bytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid multiplier: failed to decode scalar: %w", err)
|
return nil, fmt.Errorf("invalid multiplier: failed to decode scalar: %w", err)
|
||||||
|
|
|
@ -6,10 +6,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
|
||||||
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
|
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
@ -420,3 +422,32 @@ func TestCryptolib_Bls12381Equal_GT(t *testing.T) {
|
||||||
require.Equal(t, stackitem.BooleanT, itm.Type())
|
require.Equal(t, stackitem.BooleanT, itm.Type())
|
||||||
require.True(t, itm.Value().(bool))
|
require.True(t, itm.Value().(bool))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerifyGroth16Proof(t *testing.T) {
|
||||||
|
bc, committee := chain.NewSingle(t)
|
||||||
|
e := neotest.NewExecutor(t, bc, committee, committee)
|
||||||
|
|
||||||
|
c := neotest.CompileFile(t, e.Validator.ScriptHash(), "../../../../examples/zkp/xor_compat/verify.go", "../../../../examples/zkp/xor_compat/verify.yml")
|
||||||
|
e.DeployContract(t, c, nil)
|
||||||
|
|
||||||
|
validatorInvoker := e.ValidatorInvoker(c.Hash)
|
||||||
|
|
||||||
|
// These arguments are compressed representation of points provided in the original example:
|
||||||
|
argA := []byte{130, 88, 157, 112, 250, 72, 149, 167, 140, 203, 82, 224, 80, 178, 25, 167, 81, 78, 135, 22, 195, 186, 98, 114, 127, 47, 79, 56, 198, 115, 4, 214, 73, 171, 97, 187, 13, 111, 1, 183, 215, 199, 12, 128, 78, 187, 113, 209}
|
||||||
|
argB := []byte{167, 246, 242, 75, 174, 218, 111, 64, 63, 27, 36, 177, 240, 72, 121, 94, 39, 63, 55, 176, 199, 165, 152, 27, 179, 54, 3, 3, 9, 133, 97, 181, 16, 235, 109, 82, 127, 169, 66, 174, 106, 16, 71, 106, 131, 175, 30, 175, 13, 144, 171, 46, 215, 21, 184, 238, 78, 159, 15, 185, 177, 13, 48, 231, 51, 143, 203, 173, 213, 154, 160, 1, 242, 227, 2, 164, 124, 144, 199, 98, 167, 175, 122, 114, 102, 137, 12, 48, 84, 131, 0, 169, 158, 55, 128, 230}
|
||||||
|
argC := []byte{128, 213, 12, 150, 48, 255, 49, 226, 134, 136, 162, 136, 138, 49, 33, 226, 130, 57, 233, 218, 189, 99, 250, 116, 33, 92, 163, 118, 177, 216, 143, 174, 221, 225, 73, 100, 94, 84, 243, 228, 142, 40, 246, 162, 241, 48, 175, 173}
|
||||||
|
|
||||||
|
require.Equal(t, bls12381.SizeOfG1AffineCompressed, len(argA))
|
||||||
|
require.Equal(t, bls12381.SizeOfG2AffineCompressed, len(argB))
|
||||||
|
require.Equal(t, bls12381.SizeOfG1AffineCompressed, len(argC))
|
||||||
|
|
||||||
|
// This example is constructed to prove that a&b=0 where a and b are private input.
|
||||||
|
// The only public input we have is `0` from the right sight of this equation,
|
||||||
|
// but we can't pass `0` as an integer value, because CryptoLib's `bls12381Mul`
|
||||||
|
// operation that uses public witnesses requires LE bytes representation of the
|
||||||
|
// field element.
|
||||||
|
publicWitness := make([]byte, fr.Bytes)
|
||||||
|
|
||||||
|
// Verify.
|
||||||
|
validatorInvoker.Invoke(t, true, "verifyProof", argA, argB, argC, []interface{}{publicWitness})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue