Commit b10ba5c8 authored by suyanlong's avatar suyanlong

Add validator for wasm

parent 253080ef
Pipeline #8182 canceled with stages
......@@ -676,10 +676,6 @@ github.com/meshplus/bitxhub-kit v1.0.0/go.mod h1:7cWyhXWZfrQ3+EaxkRoXfuiG3Y5R9DX
github.com/meshplus/bitxhub-kit v1.1.1/go.mod h1:r4l4iqn0RPJreb/OmoYKfjCjQJrXpZX++6Qc31VG/1k=
github.com/meshplus/bitxhub-kit v1.2.1-0.20210524063043-9afae78ac098 h1:/E1isgGcUXQhTlKgVZ7GULTqw+0mj/WZ1a+Yd5N7Bmg=
github.com/meshplus/bitxhub-kit v1.2.1-0.20210524063043-9afae78ac098/go.mod h1:wrEdhHp1tktzdwcWb4bOxYsVc+KkcrYL18IYWYeumPQ=
github.com/meshplus/bitxhub-model v1.0.0-rc3/go.mod h1:ZCctQIYTlE3vJ8Lhkrgs9bWwNA+Dw4JzojOSIzLVU6E=
github.com/meshplus/bitxhub-model v1.1.1/go.mod h1:lUl9vPZXM9tP+B0ABRW/2eOW/6KCmjFTdoiTj5Vut/A=
github.com/meshplus/bitxhub-model v1.2.1-0.20210524063354-5d48e2fee178 h1:z8KGkI7DBGX9c4dUZNMT7kfBvUqZNqGNuI7uyKLZ5Tc=
github.com/meshplus/bitxhub-model v1.2.1-0.20210524063354-5d48e2fee178/go.mod h1:vwJ+sHPUyA2JELmUUDBol+7zA+7GcqutxzqXjsN0QLA=
github.com/meshplus/bitxid v0.0.0-20210412025850-e0eaf0f9063a h1:c4ESPDa60Jd4zfzZIGGTyzhfaVM3vKN+xV2G9BwIDGQ=
github.com/meshplus/bitxid v0.0.0-20210412025850-e0eaf0f9063a/go.mod h1:vAldSRfDe2Qo7exsSTbchVmZWXPY7fhWQrRw18QJHho=
github.com/meshplus/go-lightp2p v0.0.0-20200817105923-6b3aee40fa54 h1:5Ip5AB7SxxQHg5SRtf2cCOI2wy1p75MQB12soPtPyf8=
......
......@@ -3,9 +3,10 @@ package rulemgr
import (
"sync"
"github.com/meshplus/bitxhub-core/validator"
"github.com/meshplus/bitxhub-kit/storage"
"github.com/sirupsen/logrus"
"gitlab.33.cn/link33/sidecar/pkg/validator"
)
type Rule struct {
......
package validator
import (
"crypto/ecdsa"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"fmt"
"math/big"
"sync"
"github.com/sirupsen/logrus"
"gitlab.33.cn/link33/sidecar/pkg/validator/validatorlib"
)
// Validator is the instance that can use wasm to verify transaction validity
type FabSimValidator struct {
logger logrus.FieldLogger
pkMap *sync.Map
}
// New a validator instance
func NewFabSimValidator(logger logrus.FieldLogger) *FabSimValidator {
return &FabSimValidator{
logger: logger,
pkMap: &sync.Map{},
}
}
// Verify will check whether the transaction info is valid
func (vlt *FabSimValidator) Verify(address, from string, proof, payload []byte, validators string) (bool, error) {
artifact, err := validatorlib.PreCheck(proof, payload, "broker")
if err != nil {
return false, err
}
signatureSet := validatorlib.GetSignatureSet(artifact)
var pk *ecdsa.PublicKey
raw, ok := vlt.pkMap.Load(from)
if !ok {
pemCert, _ := pem.Decode([]byte(validators))
cert, err := x509.ParseCertificate(pemCert.Bytes)
if err != nil {
return false, err
}
pk = cert.PublicKey.(*ecdsa.PublicKey)
vlt.pkMap.Store(from, pk)
} else {
pk = raw.(*ecdsa.PublicKey)
}
r, s, err := unmarshalECDSASignature(signatureSet[0].Signature)
if err != nil {
return false, err
}
h := sha256.New()
_, err = h.Write(signatureSet[0].Data)
if err != nil {
return false, err
}
ret := h.Sum(nil)
isValid := ecdsa.Verify(pk, ret, r, s)
return isValid, nil
}
type ECDSASignature struct {
R, S *big.Int
}
func unmarshalECDSASignature(raw []byte) (*big.Int, *big.Int, error) {
sig := new(ECDSASignature)
_, err := asn1.Unmarshal(raw, sig)
if err != nil {
return nil, nil, fmt.Errorf("failed unmashalling signature [%s]", err)
}
// Validate sig
if sig.R == nil {
return nil, nil, fmt.Errorf("invalid signature, r must be different from nil")
}
if sig.S == nil {
return nil, nil, fmt.Errorf("invalid signature, s must be different from nil")
}
if sig.R.Sign() != 1 {
return nil, nil, fmt.Errorf("invalid signature, r must be larger than zero")
}
if sig.S.Sign() != 1 {
return nil, nil, fmt.Errorf("invalid signature, s must be larger than zero")
}
return sig.R, sig.S, nil
}
package validator
import (
"sort"
"sync"
"github.com/hyperledger/fabric/common/cauthdsl"
"github.com/hyperledger/fabric/common/policies"
mspi "github.com/hyperledger/fabric/msp"
"github.com/hyperledger/fabric/protoutil"
"github.com/sirupsen/logrus"
"gitlab.33.cn/link33/sidecar/pkg/validator/validatorlib"
)
// Validator is the instance that can use wasm to verify transaction validity
type FabV14Validator struct {
logger logrus.FieldLogger
evMap *sync.Map
peMap *sync.Map
ppMap *sync.Map
idMap *sync.Map
keyMap *sync.Map
}
// New a validator instance
func NewFabV14Validator(logger logrus.FieldLogger) *FabV14Validator {
return &FabV14Validator{
logger: logger,
evMap: &sync.Map{},
peMap: &sync.Map{},
ppMap: &sync.Map{},
idMap: &sync.Map{},
keyMap: &sync.Map{},
}
}
// Verify will check whether the transaction info is valid
func (vlt *FabV14Validator) Verify(address, from string, proof, payload []byte, validators string) (bool, error) {
var (
vInfo *validatorlib.ValidatorInfo
policy policies.Policy
err error
)
raw, ok := vlt.evMap.Load(from)
if !ok {
vInfo, err = validatorlib.UnmarshalValidatorInfo([]byte(validators))
if err != nil {
return false, err
}
vlt.evMap.Store(from, vInfo)
} else {
vInfo = raw.(*validatorlib.ValidatorInfo)
}
// Get the validation artifacts that help validate the chaincodeID and policy
artifact, err := validatorlib.PreCheck(proof, payload, vInfo.Cid)
if err != nil {
return false, err
}
signatureSet := validatorlib.GetSignatureSet(artifact)
raw, ok = vlt.ppMap.Load(vInfo.ChainId)
if !ok {
pe, err := validatorlib.NewPolicyEvaluator(vInfo.ConfByte)
if err != nil {
return false, err
}
pp := cauthdsl.NewPolicyProvider(pe.IdentityDeserializer)
policy, _, err = pp.NewPolicy([]byte(vInfo.Policy))
if err != nil {
return false, err
}
vlt.ppMap.Store(vInfo.ChainId, policy)
vlt.peMap.Store(vInfo.ChainId, pe)
} else {
policy = raw.(policies.Policy)
}
pe, _ := vlt.peMap.Load(vInfo.ChainId)
return vlt.EvaluateSignedData(signatureSet, pe.(*validatorlib.PolicyEvaluator).IdentityDeserializer, policy)
}
func (vlt *FabV14Validator) EvaluateSignedData(signedData []*protoutil.SignedData, identityDeserializer mspi.IdentityDeserializer, policy policies.Policy) (bool, error) {
idMap := map[string]struct{}{}
identities := make([]mspi.Identity, 0, len(signedData))
ids := make([]string, 0, len(signedData))
for _, sd := range signedData {
var (
identity mspi.Identity
err error
)
raw, ok := vlt.idMap.Load(string(sd.Identity))
if !ok {
identity, err = identityDeserializer.DeserializeIdentity(sd.Identity)
if err != nil {
continue
}
vlt.idMap.Store(string(sd.Identity), identity)
} else {
identity = raw.(mspi.Identity)
}
key := identity.GetIdentifier().Mspid + identity.GetIdentifier().Id
if _, ok := idMap[key]; ok {
continue
}
err = identity.Verify(sd.Data, sd.Signature)
if err != nil {
continue
}
idMap[key] = struct{}{}
ids = append(ids, key)
identities = append(identities, identity)
}
nids := sort.StringSlice(ids)
var idStr string
for _, id := range nids {
idStr = idStr + id
}
_, ok := vlt.keyMap.Load(idStr)
if !ok {
if err := policy.EvaluateIdentities(identities); err != nil {
return false, err
}
vlt.keyMap.Store(idStr, struct{}{})
return true, nil
}
return true, nil
}
// Code generated by MockGen. DO NOT EDIT.
// Source: validator.go
// Package mock_validator is a generated GoMock package.
package mock_validator
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
types "github.com/meshplus/bitxhub-kit/types"
)
// MockEngine is a mock of Engine interface.
type MockEngine struct {
ctrl *gomock.Controller
recorder *MockEngineMockRecorder
}
// MockEngineMockRecorder is the mock recorder for MockEngine.
type MockEngineMockRecorder struct {
mock *MockEngine
}
// NewMockEngine creates a new mock instance.
func NewMockEngine(ctrl *gomock.Controller) *MockEngine {
mock := &MockEngine{ctrl: ctrl}
mock.recorder = &MockEngineMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockEngine) EXPECT() *MockEngineMockRecorder {
return m.recorder
}
// Validate mocks base method.
func (m *MockEngine) Validate(address, from string, proof, payload []byte, validators string) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Validate", address, from, proof, payload, validators)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Validate indicates an expected call of Validate.
func (mr *MockEngineMockRecorder) Validate(address, from, proof, payload, validators interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockEngine)(nil).Validate), address, from, proof, payload, validators)
}
// MockValidator is a mock of Validator interface.
type MockValidator struct {
ctrl *gomock.Controller
recorder *MockValidatorMockRecorder
}
// MockValidatorMockRecorder is the mock recorder for MockValidator.
type MockValidatorMockRecorder struct {
mock *MockValidator
}
// NewMockValidator creates a new mock instance.
func NewMockValidator(ctrl *gomock.Controller) *MockValidator {
mock := &MockValidator{ctrl: ctrl}
mock.recorder = &MockValidatorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockValidator) EXPECT() *MockValidatorMockRecorder {
return m.recorder
}
// Verify mocks base method.
func (m *MockValidator) Verify(address, from string, proof, payload []byte, validators string) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Verify", address, from, proof, payload, validators)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Verify indicates an expected call of Verify.
func (mr *MockValidatorMockRecorder) Verify(address, from, proof, payload, validators interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockValidator)(nil).Verify), address, from, proof, payload, validators)
}
// MockLedger is a mock of Ledger interface.
type MockLedger struct {
ctrl *gomock.Controller
recorder *MockLedgerMockRecorder
}
// MockLedgerMockRecorder is the mock recorder for MockLedger.
type MockLedgerMockRecorder struct {
mock *MockLedger
}
// NewMockLedger creates a new mock instance.
func NewMockLedger(ctrl *gomock.Controller) *MockLedger {
mock := &MockLedger{ctrl: ctrl}
mock.recorder = &MockLedgerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockLedger) EXPECT() *MockLedgerMockRecorder {
return m.recorder
}
// GetCode mocks base method.
func (m *MockLedger) GetCode(arg0 *types.Address) []byte {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetCode", arg0)
ret0, _ := ret[0].([]byte)
return ret0
}
// GetCode indicates an expected call of GetCode.
func (mr *MockLedgerMockRecorder) GetCode(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCode", reflect.TypeOf((*MockLedger)(nil).GetCode), arg0)
}
package validator
import (
"sync"
"github.com/sirupsen/logrus"
)
const (
FabricRuleAddr = "0x00000000000000000000000000000000000000a0"
SimFabricRuleAddr = "0x00000000000000000000000000000000000000a1"
)
// Validator is the instance that can use wasm to verify transaction validity
type ValidationEngine struct {
instances *sync.Map
fabValidator Validator
simFabValidator Validator
ledger Ledger
logger logrus.FieldLogger
}
// New a validator instance
func NewValidationEngine(ledger Ledger, instances *sync.Map, logger logrus.FieldLogger) *ValidationEngine {
return &ValidationEngine{
ledger: ledger,
logger: logger,
fabValidator: NewFabV14Validator(logger),
simFabValidator: NewFabSimValidator(logger),
instances: instances,
}
}
// Verify will check whether the transaction info is valid
func (ve *ValidationEngine) Validate(address, from string, proof, payload []byte, validators string) (bool, error) {
vlt := ve.getValidator(address)
return vlt.Verify(address, from, proof, payload, validators)
}
func (ve *ValidationEngine) getValidator(address string) Validator {
if address == FabricRuleAddr {
return ve.fabValidator
}
if address == SimFabricRuleAddr {
return ve.simFabValidator
}
if ve.instances == nil {
ve.instances = &sync.Map{}
}
return NewWasmValidator(ve.ledger, ve.logger, ve.instances)
}
package validator
import (
"fmt"
"io/ioutil"
"testing"
"github.com/meshplus/bitxhub-kit/log"
"github.com/stretchr/testify/require"
"gitlab.33.cn/link33/sidecar/model/pb"
)
func TestFabV14ValidatorWasm_Verify(t *testing.T) {
logger := log.NewWithModule("validator")
v := NewValidationEngine(nil, nil, logger)
proof, err := ioutil.ReadFile("./testdata/proof")
require.Nil(t, err)
validators, err := ioutil.ReadFile("./testdata/validators")
require.Nil(t, err)
content := &pb.Content{
SrcContractId: "mychannel&transfer",
DstContractId: "0x668a209Dc6562707469374B8235e37b8eC25db08",
Func: "get",
Args: [][]byte{[]byte("Alice"), []byte("Alice"), []byte("1")},
Callback: "interchainConfirm",
}
bytes, err := content.Marshal()
require.Nil(t, err)
payload := &pb.Payload{
Encrypted: false,
Content: bytes,
}
body, err := payload.Marshal()
require.Nil(t, err)
ok, err := v.Validate(FabricRuleAddr, "0xe02d8fdacd59020d7f292ab3278d13674f5c404d", proof, body, string(validators))
require.NotNil(t, err)
require.False(t, ok)
}
func TestFabV14Validator_Verify(t *testing.T) {
logger := log.NewWithModule("validator")
v := NewValidationEngine(nil, nil, logger)
proof, err := ioutil.ReadFile("./testdata/proof")
require.Nil(t, err)
validators, err := ioutil.ReadFile("./testdata/validators")
require.Nil(t, err)
content := &pb.Content{
SrcContractId: "mychannel&transfer",
DstContractId: "0x668a209Dc6562707469374B8235e37b8eC25db08",
Func: "get",
Args: [][]byte{[]byte("Alice"), []byte("Alice"), []byte("1")},
Callback: "interchainConfirm",
}
bytes, err := content.Marshal()
require.Nil(t, err)
payload := &pb.Payload{
Encrypted: false,
Content: bytes,
}
body, err := payload.Marshal()
require.Nil(t, err)
ok, err := v.Validate(FabricRuleAddr, "0xe02d8fdacd59020d7f292ab3278d13674f5c404d", proof, body, string(validators))
require.NotNil(t, err)
require.False(t, ok)
}
func TestFabSimValidator_Verify(t *testing.T) {
logger := log.NewWithModule("validator")
v := NewValidationEngine(nil, nil, logger)
proof, err := ioutil.ReadFile("./testdata/proof_1.0.0_rc")
require.Nil(t, err)
validators, err := ioutil.ReadFile("./testdata/single_validator")
require.Nil(t, err)
content := &pb.Content{
SrcContractId: "mychannel&transfer",
DstContractId: "mychannel&transfer",
Func: "get",
Args: [][]byte{[]byte("Alice"), []byte("Alice"), []byte("1")},
Callback: "interchainConfirm",
}
bytes, err := content.Marshal()
require.Nil(t, err)
payload := &pb.Payload{
Encrypted: false,
Content: bytes,
}
body, err := payload.Marshal()
require.Nil(t, err)
ok, err := v.Validate(SimFabricRuleAddr, "0xe02d8fdacd59020d7f292ab3278d13674f5c404d", proof, body, string(validators))
require.Nil(t, err)
fmt.Println(ok)
}
func BenchmarkFabV14Validator_Verify(b *testing.B) {
logger := log.NewWithModule("validator")
proof, err := ioutil.ReadFile("./testdata/proof_1.0.0_rc")
require.Nil(b, err)
validators, err := ioutil.ReadFile("./testdata/validator_1.0.0_rc")
require.Nil(b, err)
content := &pb.Content{
SrcContractId: "mychannel&transfer",
DstContractId: "mychannel&transfer",
Func: "interchainCharge",
Args: [][]byte{[]byte("Alice"), []byte("Alice"), []byte("1")},
Callback: "interchainConfirm",
}
bytes, err := content.Marshal()
require.Nil(b, err)
payload := &pb.Payload{
Encrypted: false,
Content: bytes,
}
body, err := payload.Marshal()
require.Nil(b, err)
v := NewValidationEngine(nil, nil, logger)
ok, err := v.Validate(FabricRuleAddr, "0xe02d8fdacd59020d7f292ab3278d13674f5c404d", proof, body, string(validators))
require.Nil(b, err)
require.True(b, ok)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ok, err := v.Validate(FabricRuleAddr, "0xe02d8fdacd59020d7f292ab3278d13674f5c404d", proof, body, string(validators))
require.Nil(b, err)
require.True(b, ok)
}
}
func BenchmarkFabSimValidator_Verify(b *testing.B) {
logger := log.NewWithModule("validator")
proof, err := ioutil.ReadFile("./testdata/proof_1.0.0_rc")
require.Nil(b, err)
validators, err := ioutil.ReadFile("./testdata/single_validator")
require.Nil(b, err)
content := &pb.Content{
SrcContractId: "mychannel&transfer",
DstContractId: "mychannel&transfer",
Func: "interchainCharge",
Args: [][]byte{[]byte("Alice"), []byte("Alice"), []byte("1")},
Callback: "interchainConfirm",
}
bytes, err := content.Marshal()
require.Nil(b, err)
payload := &pb.Payload{
Encrypted: false,
Content: bytes,
}
body, err := payload.Marshal()
require.Nil(b, err)
v := NewValidationEngine(nil, nil, logger)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ok, err := v.Validate(SimFabricRuleAddr, "0xe02d8fdacd59020d7f292ab3278d13674f5c404d", proof, body, string(validators))
require.Nil(b, err)
require.True(b, ok)
}
}
func BenchmarkFabComplexValidator_Verify(b *testing.B) {
logger := log.NewWithModule("validator")
proof, err := ioutil.ReadFile("./testdata/proof_1.0.0_rc_complex")
require.Nil(b, err)
validators, err := ioutil.ReadFile("./testdata/validator_1.0.0_rc_complex")
require.Nil(b, err)
content := &pb.Content{
SrcContractId: "mychannel&transfer",
DstContractId: "mychannel&transfer",
Func: "interchainCharge",
Args: [][]byte{[]byte("Alice"), []byte("Alice"), []byte("1")},
Callback: "interchainConfirm",
}
bytes, err := content.Marshal()
require.Nil(b, err)
payload := &pb.Payload{
Encrypted: false,
Content: bytes,
}
body, err := payload.Marshal()
require.Nil(b, err)
v := NewValidationEngine(nil, nil, logger)
ok, err := v.Validate(FabricRuleAddr, "0xe02d8fdacd59020d7f292ab3278d13674f5c404d", proof, body, string(validators))
require.Nil(b, err)
require.True(b, ok)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ok, err := v.Validate(FabricRuleAddr, "0xe02d8fdacd59020d7f292ab3278d13674f5c404d", proof, body, string(validators))
require.Nil(b, err)
require.True(b, ok)
}
}
package validator
import "github.com/meshplus/bitxhub-kit/types"
// Engine runs for validation
//go:generate mockgen -destination mock_validator/mock_engine.go -package mock_validator -source validator.go
type Engine interface {
Validate(address, from string, proof, payload []byte, validators string) (bool, error)
}
// Validator chooses specific method to verify transaction
type Validator interface {
Verify(address, from string, proof, payload []byte, validators string) (bool, error)
}
type Ledger interface {
// GetCode
GetCode(*types.Address) []byte
}
package validatorlib
// #include <stdlib.h>
//
// extern int32_t fabric_validate_v14(void *context, long long proof_ptr, long long validator_ptr, long long payload_ptr);
import "C"
import (
"unsafe"
"github.com/wasmerio/go-ext-wasm/wasmer"
)
//export fabric_validate_v14
func fabric_validate_v14(context unsafe.Pointer, proof_ptr int64, validator_ptr int64, payload_ptr int64) int32 {
ctx := wasmer.IntoInstanceContext(context)
ctxMap := ctx.Data().(map[string]interface{})
data := ctxMap["argmap"].(map[int]int)
memory := ctx.Memory()
proof := memory.Data()[proof_ptr : proof_ptr+int64(data[int(proof_ptr)])]
validator := memory.Data()[validator_ptr : validator_ptr+int64(data[int(validator_ptr)])]
payload := memory.Data()[payload_ptr : payload_ptr+int64(data[int(payload_ptr)])]
vInfo, err := UnmarshalValidatorInfo(validator)
if err != nil {
return 0
}
artifact, err := extractValidationArtifacts(proof)
if err != nil {
return 0
}
if err := ValidateChainCodeID(artifact.prp, vInfo.Cid); err != nil {
return 0
}
if err := ValidatePayload(artifact.payload, payload); err != nil {
return 0
}
signatureSet := GetSignatureSet(artifact)
pe, ok := ctxMap[FABRIC_EVALUATOR].(*PolicyEvaluator)
if !ok {
pe, err = NewPolicyEvaluator(vInfo.ConfByte)
if err != nil {
return 0
}
ctxMap[FABRIC_EVALUATOR] = pe
}
if err = pe.Evaluate([]byte(vInfo.Policy), signatureSet); err != nil {
return 0
}
return 1
}
func (im *Imports) importFabricV14() {
var err error
im.imports, err = im.imports.Append("fabric_validate_v14", fabric_validate_v14, C.fabric_validate_v14)
if err != nil {
return
}
}
package validatorlib
// #include <stdlib.h>
//
// extern int32_t ecdsa_verify(void *context, long long sig_ptr, long long digest_ptr, long long pubkey_ptr, int32_t opt);
import "C"
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"fmt"
"math/big"
"unsafe"
"github.com/wasmerio/go-ext-wasm/wasmer"
)
type AlgorithmOption string
const (
// Secp256r1 secp256r1 algorithm
Secp256r1 AlgorithmOption = "Secp256r1"
)
type PrivateKey struct {
K *ecdsa.PrivateKey
}
// PublicKey ECDSA public key.
// never new(PublicKey), use NewPublicKey()
type PublicKey struct {
k *ecdsa.PublicKey
}
type ECDSASignature struct {
R, S *big.Int
}
//export ecdsa_verify
func ecdsa_verify(context unsafe.Pointer, sig_ptr int64, digest_ptr int64, pubkey_ptr int64, opt int32) int32 {
ctx := wasmer.IntoInstanceContext(context)
data := ctx.Data().(map[int]int)
memory := ctx.Memory()
signature := memory.Data()[sig_ptr : sig_ptr+70]
digest := memory.Data()[digest_ptr : digest_ptr+32]
pubkey := memory.Data()[pubkey_ptr : pubkey_ptr+int64(data[int(pubkey_ptr)])]
pemCert, _ := pem.Decode(pubkey)
var cert *x509.Certificate
cert, err := x509.ParseCertificate(pemCert.Bytes)
if err != nil {
return 0
}
pk := cert.PublicKey
r, s, err := unmarshalECDSASignature(signature)
if err != nil {
return 0
}
isValid := ecdsa.Verify(pk.(*ecdsa.PublicKey), digest, r, s)
if isValid {
return 1
} else {
return 0
}
}
func unmarshalECDSASignature(raw []byte) (*big.Int, *big.Int, error) {
sig := new(ECDSASignature)
_, err := asn1.Unmarshal(raw, sig)
if err != nil {
return nil, nil, fmt.Errorf("failed unmashalling signature [%s]", err)
}
// Validate sig
if sig.R == nil {
return nil, nil, fmt.Errorf("invalid signature, r must be different from nil")
}
if sig.S == nil {
return nil, nil, fmt.Errorf("invalid signature, s must be different from nil")
}
if sig.R.Sign() != 1 {
return nil, nil, fmt.Errorf("invalid signature, r must be larger than zero")
}
if sig.S.Sign() != 1 {
return nil, nil, fmt.Errorf("invalid signature, s must be larger than zero")
}
return sig.R, sig.S, nil
}
func (im *Imports) importECDSA() {
var err error
im.imports, err = im.imports.Append("ecdsa_verify", ecdsa_verify, C.ecdsa_verify)
if err != nil {
return
}
}
// Bytes returns a serialized, storable representation of this key
func (priv *PrivateKey) Bytes() ([]byte, error) {
if priv.K == nil {
return nil, fmt.Errorf("ECDSAPrivateKey.K is nil, please invoke FromBytes()")
}
r := make([]byte, 32)
a := priv.K.D.Bytes()
copy(r[32-len(a):], a)
return r, nil
}
func (pub *PublicKey) Bytes() ([]byte, error) {
x := pub.k.X.Bytes()
y := pub.k.Y.Bytes()
return bytes.Join(
[][]byte{
{0x04},
make([]byte, 32-len(x)), x, // padding to 32 bytes
make([]byte, 32-len(y)), y,
}, nil), nil
}
func UnmarshalPrivateKey(data []byte, opt AlgorithmOption) (crypto.PrivateKey, error) {
if len(data) == 0 {
return nil, fmt.Errorf("empty private key data")
}
key := &PrivateKey{K: new(ecdsa.PrivateKey)}
key.K.D = big.NewInt(0)
key.K.D.SetBytes(data)
switch opt {
case Secp256r1:
key.K.Curve = elliptic.P256()
default:
return nil, fmt.Errorf("unsupported algorithm option")
}
key.K.PublicKey.X, key.K.PublicKey.Y = key.K.Curve.ScalarBaseMult(data)
return key, nil
}
func UnmarshalPublicKey(data []byte, opt AlgorithmOption) (crypto.PublicKey, error) {
if len(data) == 0 {
return nil, fmt.Errorf("empty public key data")
}
key := &PublicKey{k: new(ecdsa.PublicKey)}
key.k.X = big.NewInt(0)
key.k.Y = big.NewInt(0)
if len(data) != 65 {
return nil, fmt.Errorf("public key data length is not 65")
}
key.k.X.SetBytes(data[1:33])
key.k.Y.SetBytes(data[33:])
switch opt {
case Secp256r1:
key.k.Curve = elliptic.P256()
}
return key, nil
}
package validatorlib
import (
"encoding/json"
"fmt"
"strings"
"github.com/gogo/protobuf/proto"
mb "github.com/hyperledger/fabric-protos-go/msp"
"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/bccsp/factory"
"github.com/hyperledger/fabric/common/cauthdsl"
"github.com/hyperledger/fabric/msp"
"github.com/hyperledger/fabric/protoutil"
"gitlab.33.cn/link33/sidecar/model/pb"
)
const (
FABRIC_EVALUATOR = "fabric_evaluator"
)
var evaluatorMap map[string]*PolicyEvaluator
type valiadationArtifacts struct {
rwset []byte
prp []byte
endorsements []*peer.Endorsement
cap *peer.ChaincodeActionPayload
payload payloadInfo
}
type ValidatorInfo struct {
ChainId string `json:"chain_id"`
ConfByte []string `json:"conf_byte"`
Policy string `json:"policy"`
Cid string `json:"cid"`
}
type payloadInfo struct {
Index int `json:"index"`
DstChainId string `json:"dst_chain_id"`
SrcContractId string `json:"src_contract_id"`
DstContractId string `json:"dst_contract_id"`
Func string `json:"func"`
Args string `json:args`
Callback string `json:callback`
}
func GetPolicyEnvelope(policy string) ([]byte, error) {
policyEnv, err := cauthdsl.FromString(policy)
if err != nil {
return nil, err
}
policyBytes, err := proto.Marshal(policyEnv)
if err != nil {
return nil, err
}
return policyBytes, nil
}
func UnmarshalValidatorInfo(validatorBytes []byte) (*ValidatorInfo, error) {
vInfo := &ValidatorInfo{}
if err := json.Unmarshal(validatorBytes, vInfo); err != nil {
return nil, err
}
return vInfo, nil
}
func ExtractValidationArtifacts(proof []byte) (*valiadationArtifacts, error) {
return extractValidationArtifacts(proof)
}
func extractValidationArtifacts(proof []byte) (*valiadationArtifacts, error) {
cap, err := protoutil.UnmarshalChaincodeActionPayload(proof)
if err != nil {
return nil, err
}
pRespPayload, err := protoutil.UnmarshalProposalResponsePayload(cap.Action.ProposalResponsePayload)
if err != nil {
err = fmt.Errorf("GetProposalResponsePayload error %s", err)
return nil, err
}
if pRespPayload.Extension == nil {
err = fmt.Errorf("nil pRespPayload.Extension")
return nil, err
}
respPayload, err := protoutil.UnmarshalChaincodeAction(pRespPayload.Extension)
if err != nil {
err = fmt.Errorf("GetChaincodeAction error %s", err)
return nil, err
}
var payloadArray []payloadInfo
err = json.Unmarshal(respPayload.Response.Payload, &payloadArray)
if err != nil {
return nil, err
}
return &valiadationArtifacts{
rwset: respPayload.Results,
prp: cap.Action.ProposalResponsePayload,
endorsements: cap.Action.Endorsements,
cap: cap,
payload: payloadArray[len(payloadArray)-1],
}, nil
}
func PreCheck(proof, payload []byte, cid string) (*valiadationArtifacts, error) {
// Get the validation artifacts that help validate the chaincodeID and policy
artifact, err := extractValidationArtifacts(proof)
if err != nil {
return nil, err
}
err = ValidateChainCodeID(artifact.prp, cid)
if err != nil {
return nil, err
}
err = ValidatePayload(artifact.payload, payload)
if err != nil {
return nil, err
}
return artifact, nil
}
func ValidateV14(proof, payload, policyBytes []byte, confByte []string, cid, from string) error {
// Get the validation artifacts that help validate the chaincodeID and policy
artifact, err := extractValidationArtifacts(proof)
if err != nil {
return err
}
err = ValidateChainCodeID(artifact.prp, cid)
if err != nil {
return err
}
err = ValidatePayload(artifact.payload, payload)
if err != nil {
return err
}
signatureSet := GetSignatureSet(artifact)
pe, err := NewPolicyEvaluator(confByte)
if err != nil {
return err
}
return pe.Evaluate(policyBytes, signatureSet)
}
func ValidateChainCodeID(prp []byte, name string) error {
payload, err := protoutil.UnmarshalProposalResponsePayload(prp)
if err != nil {
err = fmt.Errorf("GetProposalResponsePayload error %s", err)
return err
}
chaincodeAct, err := protoutil.UnmarshalChaincodeAction(payload.Extension)
if err != nil {
err = fmt.Errorf("GetChaincodeAction error %s", err)
return err
}
if name != chaincodeAct.ChaincodeId.Name {
return fmt.Errorf("chaincode id does not match")
}
return nil
}
func ValidatePayload(info payloadInfo, payloadByte []byte) error {
payload := &pb.Payload{}
if err := payload.Unmarshal(payloadByte); err != nil {
return err
}
if payload.Encrypted {
return nil
}
content := &pb.Content{}
if err := content.Unmarshal(payload.Content); err != nil {
return fmt.Errorf("unmarshal ibtp payload content: %w", err)
}
if info.DstContractId != content.DstContractId {
return fmt.Errorf("dst contrct id not correct")
}
if info.SrcContractId != content.SrcContractId {
return fmt.Errorf("src contrct id not correct")
}
if info.Callback != content.Callback {
return fmt.Errorf("callback not correct")
}
args := strings.Split(info.Args, ",")
for index, arg := range args {
if arg != string(content.Args[index]) {
return fmt.Errorf("args not correct")
}
}
return nil
}
type PolicyEvaluator struct {
msp.IdentityDeserializer
}
func NewPolicyEvaluator(confBytes []string) (*PolicyEvaluator, error) {
mspList := make([]msp.MSP, len(confBytes))
for i, confByte := range confBytes {
tempBccsp, err := msp.New(
&msp.BCCSPNewOpts{NewBaseOpts: msp.NewBaseOpts{Version: msp.MSPv1_3}},
factory.GetDefault(),
)
if err != nil {
return nil, err
}
conf := &mb.MSPConfig{}
if err := proto.UnmarshalText(confByte, conf); err != nil {
return nil, err
}
err = tempBccsp.Setup(conf)
if err != nil {
return nil, err
}
mspList[i] = tempBccsp
}
manager := msp.NewMSPManager()
err := manager.Setup(mspList)
if err != nil {
return nil, err
}
deserializer := &dynamicDeserializer{mspm: manager}
pe := &PolicyEvaluator{IdentityDeserializer: deserializer}
return pe, nil
}
func (id *PolicyEvaluator) Evaluate(policyBytes []byte, signatureSet []*protoutil.SignedData) error {
pp := cauthdsl.NewPolicyProvider(id.IdentityDeserializer)
policy, _, err := pp.NewPolicy(policyBytes)
if err != nil {
return err
}
return policy.EvaluateSignedData(signatureSet)
}
func GetSignatureSet(artifact *valiadationArtifacts) []*protoutil.SignedData {
signatureSet := []*protoutil.SignedData{}
for _, endorsement := range artifact.endorsements {
data := make([]byte, len(artifact.prp)+len(endorsement.Endorser))
copy(data, artifact.prp)
copy(data[len(artifact.prp):], endorsement.Endorser)
signatureSet = append(signatureSet, &protoutil.SignedData{
// set the data that is signed; concatenation of proposal response bytes and endorser ID
Data: data,
// set the identity that signs the message: it's the endorser
Identity: endorsement.Endorser,
// set the signature
Signature: endorsement.Signature,
})
}
return signatureSet
}
type dynamicDeserializer struct {
mspm msp.MSPManager
}
func (ds *dynamicDeserializer) DeserializeIdentity(serializedIdentity []byte) (msp.Identity, error) {
return ds.mspm.DeserializeIdentity(serializedIdentity)
}
func (ds *dynamicDeserializer) IsWellFormed(identity *mb.SerializedIdentity) error {
return ds.mspm.IsWellFormed(identity)
}
package validatorlib
// #include <stdlib.h>
//
// extern int32_t fabric_validate_v13(void *context, long long proof_ptr, long long validator_ptr);
import "C"
import "unsafe"
//export fabric_validate_v13
func fabric_validate_v13(context unsafe.Pointer, proof_ptr int64, validator_ptr int64) int32 {
return 1
}
func (im *Imports) importFabricV13() {
var err error
im.imports, err = im.imports.Append("fabric_validate_v13", fabric_validate_v13, C.fabric_validate_v13)
if err != nil {
return
}
}
{"Proposal":{"TxnID":"8d4a15dbcf8f5130331e06bf2eacdcb62375ce0f3b20fea3409b3d91e7695323","header":"CmgIAxoLCOOC4fQFEIjRwTkiCW15Y2hhbm5lbCpAOGQ0YTE1ZGJjZjhmNTEzMDMzMWUwNmJmMmVhY2RjYjYyMzc1Y2UwZjNiMjBmZWEzNDA5YjNkOTFlNzY5NTMyMzoKEggSBmJyb2tlchLTBgq2BgoHT3JnMk1TUBKqBi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlDS1RDQ0FkQ2dBd0lCQWdJUkFLbEl1NkVxVGdJN3B1cGpVUGM3bGNFd0NnWUlLb1pJemowRUF3SXdjekVMCk1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkcKY21GdVkybHpZMjh4R1RBWEJnTlZCQW9URUc5eVp6SXVaWGhoYlhCc1pTNWpiMjB4SERBYUJnTlZCQU1URTJOaApMbTl5WnpJdVpYaGhiWEJzWlM1amIyMHdIaGNOTWpBd05ERTJNRE15TkRBd1doY05NekF3TkRFME1ETXlOREF3CldqQnJNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU4KVTJGdUlFWnlZVzVqYVhOamJ6RU9NQXdHQTFVRUN4TUZZV1J0YVc0eEh6QWRCZ05WQkFNTUZrRmtiV2x1UUc5eQpaekl1WlhoaGJYQnNaUzVqYjIwd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFRUFRyZ0g0NU4vCnBxL0NCZXl3WFFIZk0zeDdLc0hnUUd3bDBHSzZxR0dReFpUTW5MZ3J1QWpoSURwWXViVitlODVQSEEwbm03dTcKOVo1UUsyTFFEc3p5bzAwd1N6QU9CZ05WSFE4QkFmOEVCQU1DQjRBd0RBWURWUjBUQVFIL0JBSXdBREFyQmdOVgpIU01FSkRBaWdDQXBIWGtscFEva205TVpPdHBCek5aaTN2R1MxNDl2Y3NGbERMdDkrbkc0T1RBS0JnZ3Foa2pPClBRUURBZ05IQURCRUFpQVowZXMwQUNhSDVyV2E2MFJONlFJTDZOZ2NTTGFmNFhMblpqZDh1czVISVFJZ0xWMk0KM08wdmMwREF4QldmN3VBSjBXU0xyR3hMMVBEa3hXLy9Kd2UzWWdVPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tChIYCPnx6/3GjKjk3FMlOKUaQnbAuYjit5z9","payload":"CkIKQAgBEggSBmJyb2tlchoyCg1pbnRlcmNoYWluR2V0CgIxMgoBMQoUY2hhbm5lbCZkYXRhX3N3YXBwZXIKBHBhdGg="},"Responses":[{"Endorser":"localhost:10051","Status":200,"ChaincodeStatus":200,"version":1,"response":{"status":200,"payload":"eyJvayI6ZmFsc2UsIm1lc3NhZ2UiOiJpbmNvcnJlY3QgaW5kZXgsIGV4cGVjdCAyIiwiZGF0YSI6bnVsbH0="},"payload":"CiA9aI7qyaL5DGtSjUCojZbtl75jes5EeoGHauROvrYCRBKeAQpJEi8KBmJyb2tlchIlChAKCmFkbWluLWxpc3QSAggDChEKCmlubmVyLW1ldGESAwiGBRIWCgRsc2NjEg4KDAoGYnJva2VyEgIIAxpDCMgBGj57Im9rIjpmYWxzZSwibWVzc2FnZSI6ImluY29ycmVjdCBpbmRleCwgZXhwZWN0IDIiLCJkYXRhIjpudWxsfSIMEgZicm9rZXIaAnYw","endorsement":{"endorser":"CgdPcmcyTVNQEqoGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLRENDQWM2Z0F3SUJBZ0lRY0pncURPNWJ0WFBHRUdxZ3hMVGF2ekFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NaTVsZUdGdGNHeGxMbU52YlRBZUZ3MHlNREEwTVRZd016STBNREJhRncwek1EQTBNVFF3TXpJME1EQmEKTUdveEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVEwd0N3WURWUVFMRXdSd1pXVnlNUjh3SFFZRFZRUURFeFp3WldWeU1TNXZjbWN5CkxtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUU4cTZWZnArRkNNVm8KZFl2anUyUnpwUnJHb2N3QTdrYmd5SmtHOW1hS2h0UXhyRHlqQi9QY2liVExhZm10NXMraGt5MVAzVmlMQ2tObQpLMnVuMmtXOEY2Tk5NRXN3RGdZRFZSMFBBUUgvQkFRREFnZUFNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqCkJDUXdJb0FnS1IxNUphVVA1SnZUR1RyYVFjeldZdDd4a3RlUGIzTEJaUXk3ZmZweHVEa3dDZ1lJS29aSXpqMEUKQXdJRFNBQXdSUUloQVB2b0dZU1I5amE4QlUrYmcvb0g1MEptL2NGbGhqdzhQYjdQRS9yU1MyeUFBaUFHLzZhRQpkWkNpQjlSRmprVklqZEVLQURFUUIvcmtaWnU3Q29lODFtOStUQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","signature":"MEUCIQDidRlKTnmFAqNYlkjTMAAwiDYD2XInPoZSWfUBf46a2gIgfHbPWHqkwFhRoSoRLuSGa0d0Yb8LgXpZ6hQhvcFSs0s="}}],"TransactionID":"8d4a15dbcf8f5130331e06bf2eacdcb62375ce0f3b20fea3409b3d91e7695323","TxValidationCode":0,"ChaincodeStatus":200,"Payload":"eyJvayI6ZmFsc2UsIm1lc3NhZ2UiOiJpbmNvcnJlY3QgaW5kZXgsIGV4cGVjdCAyIiwiZGF0YSI6bnVsbH0="}
\ No newline at end of file
package validatorlib
import (
"github.com/wasmerio/go-ext-wasm/wasmer"
)
type Imports struct {
imports *wasmer.Imports
}
func New() (*wasmer.Imports, error) {
imports := &Imports{
imports: wasmer.NewImports(),
}
imports.importECDSA()
imports.importFabricV14()
imports.importFabricV13()
return imports.imports, nil
}
package validator
import (
"encoding/json"
"fmt"
"strconv"
"sync"
"github.com/gogo/protobuf/proto"
"github.com/meshplus/bitxhub-kit/types"
"github.com/sirupsen/logrus"
"gitlab.33.cn/link33/sidecar/model/pb"
"gitlab.33.cn/link33/sidecar/pkg/validator/validatorlib"
"gitlab.33.cn/link33/sidecar/pkg/wasm"
)
// Validator is the instance that can use wasm to verify transaction validity
type WasmValidator struct {
wasm *wasm.Wasm
input []byte
ledger Ledger
logger logrus.FieldLogger
instances *sync.Map
}
// New a validator instance
func NewWasmValidator(ledger Ledger, logger logrus.FieldLogger, instances *sync.Map) *WasmValidator {
return &WasmValidator{
ledger: ledger,
logger: logger,
instances: instances,
}
}
// Verify will check whether the transaction info is valid
func (vlt *WasmValidator) Verify(address, from string, proof, payload []byte, validators string) (bool, error) {
ruleHash, err := vlt.initRule(address, from, proof, payload, validators)
if err != nil {
return false, err
}
ret, err := vlt.wasm.Execute(vlt.input)
if err != nil {
return false, err
}
// put wasm instance into pool
v, ok := vlt.instances.Load(ruleHash)
if !ok {
return false, fmt.Errorf("load wasm instance failed")
}
v.(*sync.Pool).Put(vlt.wasm.Instance)
// check execution status
result, err := strconv.Atoi(string(ret))
if err != nil {
return false, err
}
if result == 0 {
return false, nil
}
return true, nil
}
// InitRule can import a specific rule for validator to verify the transaction
func (vlt *WasmValidator) initRule(address, from string, proof, payload []byte, validators string) (string, error) {
err := vlt.setTransaction(address, from, proof, validators, payload)
if err != nil {
return "", err
}
imports, err := validatorlib.New()
if err != nil {
return "", err
}
contractByte := vlt.ledger.GetCode(types.NewAddressByStr(address))
if contractByte == nil {
return "", fmt.Errorf("this rule address does not exist")
}
wasmInstance, err := wasm.New(contractByte, imports, vlt.instances)
if err != nil {
return "", err
}
vlt.wasm = wasmInstance
contract := &wasm.Contract{}
if err := json.Unmarshal(contractByte, contract); err != nil {
return "", fmt.Errorf("contract byte not correct")
}
return contract.Hash.String(), nil
}
func (vlt *WasmValidator) setTransaction(address, from string, proof []byte, validators string, payload []byte) error {
invokePayload := &pb.InvokePayload{
Method: "start_verify",
Args: []*pb.Arg{
{Type: pb.Arg_Bytes, Value: proof},
{Type: pb.Arg_Bytes, Value: []byte(validators)},
{Type: pb.Arg_Bytes, Value: payload},
},
}
input, _ := proto.Marshal(invokePayload)
vlt.input = input
return nil
}
package validator
import (
"encoding/json"
"fmt"
"io/ioutil"
"strconv"
"sync"
"testing"
"github.com/stretchr/testify/require"
"gitlab.33.cn/link33/sidecar/model/pb"
"gitlab.33.cn/link33/sidecar/pkg/validator/validatorlib"
"gitlab.33.cn/link33/sidecar/pkg/wasm"
)
func TestWasmValidator(t *testing.T) {
wasmBytes, err := ioutil.ReadFile("./testdata/hpc_demo.wasm")
require.Nil(t, err)
proof, err := ioutil.ReadFile("./testdata/proof_1.0.0_rc")
require.Nil(t, err)
validators, err := ioutil.ReadFile("./testdata/validator_1.0.0_rc")
require.Nil(t, err)
content := &pb.Content{
SrcContractId: "mychannel&transfer",
DstContractId: "mychannel&transfer",
Func: "interchainCharge",
Args: [][]byte{[]byte("Alice"), []byte("Alice"), []byte("1")},
Callback: "interchainConfirm",
}
bytes, err := content.Marshal()
require.Nil(t, err)
payload := &pb.Payload{
Encrypted: false,
Content: bytes,
}
body, err := payload.Marshal()
require.Nil(t, err)
validator := &WasmValidator{
instances: &sync.Map{},
}
wasmCode := &wasm.Contract{
Code: wasmBytes,
}
contractBytes, err := json.Marshal(wasmCode)
require.Nil(t, err)
imports, err := validatorlib.New()
require.Nil(t, err)
wasm, err := wasm.New(contractBytes, imports, validator.instances)
require.Nil(t, err)
validator.wasm = wasm
err = validator.setTransaction("", "0xe02d8fdacd59020d7f292ab3278d13674f5c404d", proof, string(validators), body)
require.Nil(t, err)
ret, err := validator.wasm.Execute(validator.input)
require.Nil(t, err)
result, err := strconv.Atoi(string(ret))
require.Nil(t, err)
fmt.Println(result)
}
func BenchmarkHpcWasm_Verify(b *testing.B) {
wasmBytes, err := ioutil.ReadFile("./testdata/hpc_demo.wasm")
require.Nil(b, err)
// proof, err := ioutil.ReadFile("./testdata/proof_1.0.0_rc")
// require.Nil(b, err)
// validators, err := ioutil.ReadFile("./testdata/validator_1.0.0_rc")
// require.Nil(b, err)
content := &pb.Content{
SrcContractId: "mychannel&transfer",
DstContractId: "mychannel&transfer",
Func: "interchainCharge",
Args: [][]byte{[]byte("Alice"), []byte("Alice"), []byte("1")},
Callback: "interchainConfirm",
}
bytes, err := content.Marshal()
require.Nil(b, err)
payload := &pb.Payload{
Encrypted: false,
Content: bytes,
}
body, err := payload.Marshal()
require.Nil(b, err)
validator := &WasmValidator{
instances: &sync.Map{},
}
wasmCode := &wasm.Contract{
Code: wasmBytes,
}
contractBytes, err := json.Marshal(wasmCode)
require.Nil(b, err)
imports, err := validatorlib.New()
require.Nil(b, err)
wasm, err := wasm.New(contractBytes, imports, validator.instances)
require.Nil(b, err)
validator.wasm = wasm
err = validator.setTransaction("", "0xe02d8fdacd59020d7f292ab3278d13674f5c404d", []byte("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"), "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", body)
require.Nil(b, err)
// for i := 0; i < 400000; i++ {
// _, err := validator.wasm.Execute(validator.input)
// require.Nil(b, err)
// }
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validator.wasm.Execute(validator.input)
require.Nil(b, err)
}
}
package wasm
import (
"fmt"
)
// SetString set the string type arg for wasm
func (w *Wasm) SetString(str string) (int32, error) {
alloc := w.Instance.Exports["allocate"]
if alloc == nil {
return 0, fmt.Errorf("not found allocate method")
}
lengthOfStr := len(str)
allocResult, err := alloc(lengthOfStr)
if err != nil {
return 0, err
}
inputPointer := allocResult.ToI32()
memory := w.Instance.Memory.Data()[inputPointer:]
var i int
for i = 0; i < lengthOfStr; i++ {
memory[i] = str[i]
}
memory[i] = 0
w.argMap[int(inputPointer)] = len(str)
return inputPointer, nil
}
// SetBytes set bytes type arg for wasm
func (w *Wasm) SetBytes(b []byte) (int32, error) {
alloc := w.Instance.Exports["allocate"]
if alloc == nil {
return 0, fmt.Errorf("not found allocate method")
}
lengthOfBytes := len(b)
allocResult, err := alloc(lengthOfBytes)
if err != nil {
return 0, err
}
inputPointer := allocResult.ToI32()
memory := w.Instance.Memory.Data()[inputPointer:]
var i int
for i = 0; i < lengthOfBytes; i++ {
memory[i] = b[i]
}
memory[i] = 0
w.argMap[int(inputPointer)] = len(b)
return inputPointer, nil
}
// FreeString free the string type arg for wasm
func (w *Wasm) FreeString(inputPointer interface{}, str string) error {
dealloc := w.Instance.Exports["deallocate"]
if dealloc == nil {
return fmt.Errorf("not found allocate method")
}
lengthOfStr := len(str)
_, err := dealloc(inputPointer, lengthOfStr)
if err != nil {
return err
}
delete(w.argMap, int(inputPointer.(int32)))
return nil
}
// FreeBytes free the bytes type arg for wasm
func (w *Wasm) FreeBytes(inputPointer interface{}, b []byte) error {
dealloc := w.Instance.Exports["deallocate"]
if dealloc == nil {
return fmt.Errorf("not found allocate method")
}
lengthOfBytes := len(b)
_, err := dealloc(inputPointer, lengthOfBytes)
if err != nil {
return err
}
delete(w.argMap, int(inputPointer.(int32)))
return nil
}
package wasm
import (
"encoding/json"
"fmt"
"strconv"
"sync"
"github.com/gogo/protobuf/proto"
"github.com/meshplus/bitxhub-kit/types"
"github.com/wasmerio/go-ext-wasm/wasmer"
"gitlab.33.cn/link33/sidecar/model/pb"
)
const (
CONTEXT_ARGMAP = "argmap"
CONTEXT_INTERFACE = "interface"
ACCOUNT = "account"
ALLOC_MEM = "allocate"
)
var errorLackOfMethod = fmt.Errorf("wasm execute: lack of method name")
func getInstance(contract *Contract, imports *wasmer.Imports, instances *sync.Map) (wasmer.Instance, error) {
var (
instance wasmer.Instance
err error
pool *sync.Pool
)
v, ok := instances.Load(contract.Hash.String())
if !ok {
v = &sync.Pool{
New: func() interface{} {
instance, _ := wasmer.NewInstanceWithImports(contract.Code, imports)
return instance
},
}
instances.Store(contract.Hash.String(), v)
}
pool = v.(*sync.Pool)
rawInstance := pool.Get()
if rawInstance == nil {
instance, err = wasmer.NewInstanceWithImports(contract.Code, imports)
if err != nil {
return wasmer.Instance{}, err
}
} else {
instance = rawInstance.(wasmer.Instance)
}
return instance, nil
}
// Wasm represents the wasm vm in BitXHub
type Wasm struct {
// wasm instance
Instance wasmer.Instance
context map[string]interface{}
argMap map[int]int
sync.RWMutex
}
// Contract represents the smart contract structure used in the wasm vm
type Contract struct {
// contract byte
Code []byte
// contract hash
Hash *types.Hash
}
// New creates a wasm vm instance
func New(contractByte []byte, imports *wasmer.Imports, instances *sync.Map) (*Wasm, error) {
wasm := &Wasm{}
contract := &Contract{}
if err := json.Unmarshal(contractByte, contract); err != nil {
return wasm, fmt.Errorf("contract byte not correct")
}
if len(contract.Code) == 0 {
return wasm, fmt.Errorf("contract byte is empty")
}
instance, err := getInstance(contract, imports, instances)
if err != nil {
return nil, err
}
wasm.Instance = instance
wasm.argMap = make(map[int]int)
wasm.context = make(map[string]interface{})
return wasm, nil
}
func EmptyImports() (*wasmer.Imports, error) {
return wasmer.NewImports(), nil
}
func (w *Wasm) Execute(input []byte) ([]byte, error) {
payload := &pb.InvokePayload{}
if err := proto.Unmarshal(input, payload); err != nil {
return nil, err
}
if payload.Method == "" {
return nil, errorLackOfMethod
}
alloc := w.Instance.Exports[ALLOC_MEM]
if alloc == nil {
return nil, fmt.Errorf("not found allocate method")
}
w.context[ALLOC_MEM] = alloc
methodName, ok := w.Instance.Exports[payload.Method]
if !ok {
return nil, fmt.Errorf("wrong rule contract")
}
slice := make([]interface{}, len(payload.Args))
for i := range slice {
arg := payload.Args[i]
switch arg.Type {
case pb.Arg_I32:
temp, err := strconv.Atoi(string(arg.Value))
if err != nil {
return nil, err
}
slice[i] = temp
case pb.Arg_I64:
temp, err := strconv.ParseInt(string(arg.Value), 10, 64)
if err != nil {
return nil, err
}
slice[i] = temp
case pb.Arg_F32:
temp, err := strconv.ParseFloat(string(arg.Value), 32)
if err != nil {
return nil, err
}
slice[i] = temp
case pb.Arg_F64:
temp, err := strconv.ParseFloat(string(arg.Value), 64)
if err != nil {
return nil, err
}
slice[i] = temp
case pb.Arg_String:
inputPointer, err := w.SetString(string(arg.Value))
if err != nil {
return nil, err
}
slice[i] = inputPointer
case pb.Arg_Bytes:
inputPointer, err := w.SetBytes(arg.Value)
if err != nil {
return nil, err
}
slice[i] = inputPointer
case pb.Arg_Bool:
inputPointer, err := strconv.Atoi(string(arg.Value))
if err != nil {
return nil, err
}
slice[i] = inputPointer
default:
return nil, fmt.Errorf("input type not support")
}
}
w.context[CONTEXT_ARGMAP] = w.argMap
w.Instance.SetContextData(w.context)
result, err := methodName(slice...)
if err != nil {
return nil, err
}
for i := range slice {
arg := payload.Args[i]
switch arg.Type {
case pb.Arg_String:
if err := w.FreeString(slice[i], string(arg.Value)); err != nil {
return nil, err
}
case pb.Arg_Bytes:
if err := w.FreeBytes(slice[i], arg.Value); err != nil {
return nil, err
}
}
}
return []byte(result.String()), err
}
func (w *Wasm) SetContext(key string, value interface{}) {
w.Lock()
defer w.Unlock()
w.context[key] = value
}
func (w *Wasm) GetContext(key string) interface{} {
w.Lock()
defer w.Unlock()
return w.context[key]
}
package wasm
import (
"encoding/json"
"fmt"
"io/ioutil"
"sync"
"testing"
"github.com/meshplus/bitxhub-kit/types"
"github.com/stretchr/testify/assert"
"github.com/wasmerio/go-ext-wasm/wasmer"
"gitlab.33.cn/link33/sidecar/model/pb"
"gitlab.33.cn/link33/sidecar/pkg/wasm/wasmlib"
)
func TestExecute(t *testing.T) {
data, err := ioutil.ReadFile("./testdata/wasm_test.wasm")
assert.Nil(t, err)
contract := &Contract{
Code: data,
Hash: &types.Hash{},
}
bytes, err := json.Marshal(contract)
assert.Nil(t, err)
imports := wasmer.NewImports()
instances := &sync.Map{}
wasm, err := New(bytes, imports, instances)
assert.Nil(t, err)
input := &pb.InvokePayload{
Method: "a",
Args: []*pb.Arg{
{Type: pb.Arg_I32, Value: []byte(fmt.Sprintf("%d", 1))},
{Type: pb.Arg_I32, Value: []byte(fmt.Sprintf("%d", 2))},
},
}
inputBytes, err := input.Marshal()
assert.Nil(t, err)
ret, err := wasm.Execute(inputBytes)
assert.Nil(t, err)
fmt.Println(string(ret))
}
func TestImportExecute(t *testing.T) {
data, err := ioutil.ReadFile("./testdata/test_demo.wasm")
assert.Nil(t, err)
hello := "hello world"
contract := &Contract{
Code: data,
Hash: &types.Hash{},
}
bytes, err := json.Marshal(contract)
assert.Nil(t, err)
imports, err := wasmlib.New()
assert.Nil(t, err)
instances := &sync.Map{}
wasm, err := New(bytes, imports, instances)
assert.Nil(t, err)
input := &pb.InvokePayload{
Method: "start_verify",
Args: []*pb.Arg{
{Type: pb.Arg_Bytes, Value: []byte(fmt.Sprintf("%d", 1))},
{Type: pb.Arg_Bytes, Value: []byte(fmt.Sprintf("%d", 2))},
{Type: pb.Arg_Bytes, Value: []byte(fmt.Sprintf("%d", 2))},
},
}
inputBytes, err := input.Marshal()
assert.Nil(t, err)
wasm.SetContext("hello", hello)
ret, err := wasm.Execute(inputBytes)
assert.Nil(t, err)
fmt.Println(string(ret))
}
func BenchmarkImportExecute(b *testing.B) {
data, err := ioutil.ReadFile("./testdata/test_demo.wasm")
assert.Nil(b, err)
hello := "hello world"
contract := &Contract{
Code: data,
Hash: &types.Hash{},
}
bytes, err := json.Marshal(contract)
assert.Nil(b, err)
imports, err := wasmlib.New()
assert.Nil(b, err)
instances := &sync.Map{}
wasm, err := New(bytes, imports, instances)
assert.Nil(b, err)
input := &pb.InvokePayload{
Method: "start_verify",
Args: []*pb.Arg{
{Type: pb.Arg_Bytes, Value: []byte("1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")},
{Type: pb.Arg_Bytes, Value: []byte("1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")},
{Type: pb.Arg_Bytes, Value: []byte(fmt.Sprintf("%d", 2))},
},
}
inputBytes, err := input.Marshal()
assert.Nil(b, err)
wasm.SetContext("hello", hello)
for i := 0; i < 200000; i++ {
_, err := wasm.Execute(inputBytes)
assert.Nil(b, err)
// fmt.Println(string(ret))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := wasm.Execute(inputBytes)
assert.Nil(b, err)
// fmt.Println(string(ret))
}
}
package wasmlib
import (
"github.com/wasmerio/go-ext-wasm/wasmer"
)
type Imports struct {
imports *wasmer.Imports
}
func New() (*wasmer.Imports, error) {
imports := &Imports{
imports: wasmer.NewImports(),
}
imports.importWasmLib()
return imports.imports, nil
}
package wasmlib
// #include <stdlib.h>
//
// extern int32_t test_verify(void *context, long long proof_ptr, long long validator_ptr, long long payload_ptr);
import (
"unsafe"
"github.com/wasmerio/go-ext-wasm/wasmer"
)
//export test_verify
func test_verify(context unsafe.Pointer, proof_ptr int64, validator_ptr int64, payload_ptr int64) int32 {
ctx := wasmer.IntoInstanceContext(context)
ctxMap := ctx.Data().(map[string]interface{})
_ = ctxMap["argmap"].(map[int]int)
_ = ctx.Memory()
// proof := memory.Data()[proof_ptr : proof_ptr+int64(data[int(proof_ptr)])]
// validator := memory.Data()[validator_ptr : validator_ptr+int64(data[int(validator_ptr)])]
// payload := memory.Data()[payload_ptr : payload_ptr+int64(data[int(payload_ptr)])]
// fmt.Println(proof)
// fmt.Println(validator)
// fmt.Println(payload)
// fmt.Println(ctxMap["hello"].(string))
return 1
}
func (im *Imports) importWasmLib() {
var err error
im.imports, err = im.imports.Append("test_verify", test_verify, C.test_verify)
if err != nil {
return
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment