diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1e3e671d..15c7502b5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased](https://github.com/Finschia/finschia-sdk/compare/v0.48.1...HEAD) ### Features +* (x/zkauth) [\#1246](https://github.com/Finschia/finschia-sdk/pull/1246) add ZKAuthMsgDecorator anteHandler for zkauth ### Improvements * (x/zkauth) [\#1239](https://github.com/Finschia/finschia-sdk/pull/1239) add CalculateAllInputsHash in ZKAuthInputs diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 1c7383d3cd..d5a3705677 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -9201,7 +9201,7 @@ Contains the values needed to calculate "all inputs hash". | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | `zk_auth_inputs` | [ZKAuthInputs](#finschia.zkauth.v1beta1.ZKAuthInputs) | | | -| `max_block_height` | [uint64](#uint64) | | | +| `max_block_height` | [int64](#int64) | | | diff --git a/go.mod b/go.mod index d3a7ffc317..03d03151f4 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,8 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/hdevalence/ed25519consensus v0.1.0 github.com/iden3/go-iden3-crypto v0.0.15 + github.com/iden3/go-rapidsnark/types v0.0.3 + github.com/iden3/go-rapidsnark/verifier v0.0.5 github.com/improbable-eng/grpc-web v0.15.0 github.com/jhump/protoreflect v1.15.3 github.com/magiconair/properties v1.8.7 diff --git a/go.sum b/go.sum index 882fd983c3..99836bf8a1 100644 --- a/go.sum +++ b/go.sum @@ -369,6 +369,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/iden3/go-iden3-crypto v0.0.15 h1:4MJYlrot1l31Fzlo2sF56u7EVFeHHJkxGXXZCtESgK4= github.com/iden3/go-iden3-crypto v0.0.15/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBeOT/3UEhXsEsP3E= +github.com/iden3/go-rapidsnark/types v0.0.3 h1:f0s1Qdut1qHe1O67+m+xUVRBPwSXnq5j0xSrBi0jqM4= +github.com/iden3/go-rapidsnark/types v0.0.3/go.mod h1:ApgcaUxKIgSRA6fAeFxK7p+lgXXfG4oA2HN5DhFlfF4= +github.com/iden3/go-rapidsnark/verifier v0.0.5 h1:J7y0ovrEjDQoWtZmlrp4tgGng1A9faMeYsQH4igAEqA= +github.com/iden3/go-rapidsnark/verifier v0.0.5/go.mod h1:KgL3Yr9NehlFDI4EIWVLE3UDUi8ulyjbp7HcXSBfiGI= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= diff --git a/proto/finschia/zkauth/v1beta1/tx.proto b/proto/finschia/zkauth/v1beta1/tx.proto index 19e6875441..20221f6531 100644 --- a/proto/finschia/zkauth/v1beta1/tx.proto +++ b/proto/finschia/zkauth/v1beta1/tx.proto @@ -28,5 +28,5 @@ message MsgExecutionResponse { message ZKAuthSignature { ZKAuthInputs zk_auth_inputs = 1; - uint64 max_block_height = 2; + int64 max_block_height = 2; } diff --git a/simapp/ante/ante.go b/simapp/ante/ante.go new file mode 100644 index 0000000000..754a166e3d --- /dev/null +++ b/simapp/ante/ante.go @@ -0,0 +1,55 @@ +package ante + +import ( + sdk "github.com/Finschia/finschia-sdk/types" + sdkerrors "github.com/Finschia/finschia-sdk/types/errors" + "github.com/Finschia/finschia-sdk/x/auth/ante" + zkauthante "github.com/Finschia/finschia-sdk/x/zkauth/ante" + zkauthtypes "github.com/Finschia/finschia-sdk/x/zkauth/types" +) + +type HandlerOptions struct { + ante.HandlerOptions + + ZKAuthKeeper zkauthtypes.ZKAuthKeeper +} + +func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) { + if opts.AccountKeeper == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder") + } + + if opts.BankKeeper == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for ante builder") + } + + if opts.SignModeHandler == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + } + + //sigGasConsumer := opts.SigGasConsumer + //if sigGasConsumer == nil { + // sigGasConsumer = ante.DefaultSigVerificationGasConsumer + //} + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + ante.NewRejectExtensionOptionsDecorator(), + ante.NewMempoolFeeDecorator(), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(opts.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(opts.AccountKeeper), + ante.NewDeductFeeDecorator(opts.AccountKeeper, opts.BankKeeper, opts.FeegrantKeeper), + // todo: SetPubKeyDecorator should modify for zkauth + //ante.NewSetPubKeyDecorator(opts.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewValidateSigCountDecorator(opts.AccountKeeper), + // todo: SigGasConsumeDecorator should modify for zkauth + //ante.NewSigGasConsumeDecorator(opts.AccountKeeper, sigGasConsumer), + //ante.NewSigVerificationDecorator(opts.AccountKeeper, opts.SignModeHandler), + zkauthante.NewZKAuthMsgDecorator(opts.ZKAuthKeeper), + ante.NewIncrementSequenceDecorator(opts.AccountKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/simapp/ante/ante_test.go b/simapp/ante/ante_test.go new file mode 100644 index 0000000000..a2cde8e4d3 --- /dev/null +++ b/simapp/ante/ante_test.go @@ -0,0 +1,41 @@ +package ante_test + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" + + appante "github.com/Finschia/finschia-sdk/simapp/ante" + "github.com/Finschia/finschia-sdk/x/auth/ante" + "github.com/Finschia/finschia-sdk/x/zkauth/testutil" +) + +func TestAnteHandler(t *testing.T) { + k := testutil.ZkAuthKeeper(t) + _, err := k.AddTestAccounts([]string{"link19qvkxwmln5kf0z59ecw744ue2gzsndlwcuz9uq2kav4evuerjlysxduwzj"}) + require.NoError(t, err) + + const sampleTxBase64 = "CtcICtQICiUvZmluc2NoaWEuemthdXRoLnYxYmV0YTEuTXNnRXhlY3V0aW9uEqoICo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KP2xpbmsxZzd1ZDYzZXFsbGo3emo0cTdma2NhNWg3czIyM2o3OHR5dnIwZTJjeHV3NHF5eWFhZjN1c2E2NGRxYxIrbGluazEwMDh3ZW5ncjI4ejVxdWF0MmR6cnBydDloOGV1YXY0aGVyZnl1bRKWBwqPBwqvBXsicGlfYSI6WyIzNjE1OTE2NzQ3NTQxMzkyMzMzOTA1MjYyNjkyNjQ3NTE1MjE2MDc2MTQzMTkwNzQwNDUyOTEyNjMzMzExMDAyMDc4NjYzNjEwNTU5IiwiMjAxMzg5MzA5NDk5MjEyMTA2Njk0NDYyMDA4ODAwNzI2MDg2MjUwOTM0MzQ5OTc3NDc2NDkyMTU2MDA5NDU0MTg4ODA1ODM2NzE5MDciLCIxIl0sInBpX2IiOltbIjE2MjY0ODM5OTg1NTE5MTY1MzM1MTQzMjk5MTczMzI0NjU1NDgwNjI1MjE2NDUyNDkzNzQzOTE0NDI3MTA0MDUzMDU0OTA2NzYwMTk1IiwiMjAyNTY3NzY4MTY5MjEzMjUzOTkwMjI4NjYyMTkyNzAzNTc5Mzc4Mzc4ODk2NjA2MDQ0OTU5ODk3Njg3MjczMjUzNzY1ODIyMzM0NTgiXSxbIjY2MzU2NzY4NzMxOTU4OTkyMDUxMDM3MjYwNDE4ODIyNTkxNjc3MDgwNDQwNzc4MzQ2MzM5NzA5ODIxMzc4NDIxNjkyNjg0OTU1OTYiLCI0ODc4ODc2OTQ4MTE1NTQwMDQ4NTE5MzE2MjI2NzE0OTQ3NDMxMDQ5ODc1Njg1MTcxNTg1Mzk3OTE4NDMzNjEwNzI5NTExMjcyNzM1Il0sWyIxIiwiMCJdXSwicGlfYyI6WyIxMTAzOTE3OTgzNjgwNTQyNTU5NzQzNDYzOTYxMzM0MzExOTI3MzAyNjk4NTc3ODQyMDIxMzEwNDEzMzMzNTU5NTEzOTI2Mzg2MjAzNyIsIjEwMjM4MzE5NjY1MjY0MDc0ODQwNTY3MTc5Njg1MjYzOTA1NDY5NTE4NDgzMTk4ODkzMzUwNDMxMDMwNjE3NTMwODY3NjA1NDczNjEzIiwiMSJdfRIkYUhSMGNITTZMeTloWTJOdmRXNTBjeTVuYjI5bmJHVXVZMjl0GmZleUpoYkdjaU9pSlNVekkxTmlJc0ltdHBaQ0k2SWpabU9UYzNOMkUyT0RVNU1EYzNPVGhsWmpjNU5EQTJNbU13TUdJMk5XUTJObU15TkRCaU1XSWlMQ0owZVhBaU9pSktWMVFpZlEiTTE1MDM1MTYxNTYwMTU5OTcxNjMzODAwOTgzNjE5OTMxNDk4Njk2MTUyNjMzNDI2NzY4MDE2OTY2MDU3NzcwNjQzMjYyMDIyMDk2MDczEISIBxJkCk4KRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC620v5dX6qHW1pxNtJNnq/I/T5a7hOOaJx/I9MtIFFcwSBAoCCAESEgoMCgRjb255EgQyMDAwEMCaDBpA6pSglqOdkufji1fUl1NBOBCimsduiA4GD/bd3OXFlBoi35CE2qNfdclGgF8ZO7WchVsnx1PuYnJalNq/RJw8RA==" + sampleTxBytes, err := base64.StdEncoding.DecodeString(sampleTxBase64) + require.NoError(t, err) + + tx, err := k.ClientCtx.TxConfig.TxDecoder()(sampleTxBytes) + require.NoError(t, err) + + anteHandler, err := appante.NewAnteHandler( + appante.HandlerOptions{ + HandlerOptions: ante.HandlerOptions{ + AccountKeeper: k.Simapp.AccountKeeper, + BankKeeper: k.Simapp.BankKeeper, + FeegrantKeeper: k.Simapp.FeeGrantKeeper, + SignModeHandler: k.ClientCtx.TxConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + ZKAuthKeeper: k.ZKAuthKeeper, + }, + ) + + _, err = anteHandler(k.Ctx, tx, false) + require.NoError(t, err) +} diff --git a/simapp/app.go b/simapp/app.go index c37163f9ef..dc2b7a52e1 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -103,6 +103,9 @@ import ( upgradeclient "github.com/Finschia/finschia-sdk/x/upgrade/client" upgradekeeper "github.com/Finschia/finschia-sdk/x/upgrade/keeper" upgradetypes "github.com/Finschia/finschia-sdk/x/upgrade/types" + "github.com/Finschia/finschia-sdk/x/zkauth" + zkauthkeeper "github.com/Finschia/finschia-sdk/x/zkauth/keeper" + zkauthtypes "github.com/Finschia/finschia-sdk/x/zkauth/types" // unnamed import of statik for swagger UI support _ "github.com/Finschia/finschia-sdk/client/docs/statik" @@ -143,6 +146,7 @@ var ( vesting.AppModuleBasic{}, tokenmodule.AppModuleBasic{}, collectionmodule.AppModuleBasic{}, + zkauth.AppModuleBasic{}, ) // module account permissions @@ -202,6 +206,7 @@ type SimApp struct { ClassKeeper classkeeper.Keeper TokenKeeper tokenkeeper.Keeper CollectionKeeper collectionkeeper.Keeper + ZKAuthKeeper zkauthkeeper.Keeper // the module manager mm *module.Manager @@ -357,6 +362,14 @@ func NewSimApp( // If evidence needs to be handled for the app, set routes in router here and seal app.EvidenceKeeper = *evidenceKeeper + // create zkauth keeper + jwKsMap := zkauthtypes.NewJWKs() + // todo: verification key should be loaded from file. + var verificationKey = []byte("{\n \"protocol\": \"groth16\",\n \"curve\": \"bn128\",\n \"nPublic\": 1,\n \"vk_alpha_1\": [\n \"20491192805390485299153009773594534940189261866228447918068658471970481763042\",\n \"9383485363053290200918347156157836566562967994039712273449902621266178545958\",\n \"1\"\n ],\n \"vk_beta_2\": [\n [\n \"6375614351688725206403948262868962793625744043794305715222011528459656738731\",\n \"4252822878758300859123897981450591353533073413197771768651442665752259397132\"\n ],\n [\n \"10505242626370262277552901082094356697409835680220590971873171140371331206856\",\n \"21847035105528745403288232691147584728191162732299865338377159692350059136679\"\n ],\n [\n \"1\",\n \"0\"\n ]\n ],\n \"vk_gamma_2\": [\n [\n \"10857046999023057135944570762232829481370756359578518086990519993285655852781\",\n \"11559732032986387107991004021392285783925812861821192530917403151452391805634\"\n ],\n [\n \"8495653923123431417604973247489272438418190587263600148770280649306958101930\",\n \"4082367875863433681332203403145435568316851327593401208105741076214120093531\"\n ],\n [\n \"1\",\n \"0\"\n ]\n ],\n \"vk_delta_2\": [\n [\n \"21349319915249622662700217004338779716430783387183352766870647565870141979289\",\n \"8213816744021090866451311756048660670381089332123677295675725952502733471420\"\n ],\n [\n \"4787213629490370557685854255230879988945206163033639129474026644007741911075\",\n \"20003855859301921415178037270191878217707285640767940877063768682564788786247\"\n ],\n [\n \"1\",\n \"0\"\n ]\n ],\n \"vk_alphabeta_12\": [\n [\n [\n \"2029413683389138792403550203267699914886160938906632433982220835551125967885\",\n \"21072700047562757817161031222997517981543347628379360635925549008442030252106\"\n ],\n [\n \"5940354580057074848093997050200682056184807770593307860589430076672439820312\",\n \"12156638873931618554171829126792193045421052652279363021382169897324752428276\"\n ],\n [\n \"7898200236362823042373859371574133993780991612861777490112507062703164551277\",\n \"7074218545237549455313236346927434013100842096812539264420499035217050630853\"\n ]\n ],\n [\n [\n \"7077479683546002997211712695946002074877511277312570035766170199895071832130\",\n \"10093483419865920389913245021038182291233451549023025229112148274109565435465\"\n ],\n [\n \"4595479056700221319381530156280926371456704509942304414423590385166031118820\",\n \"19831328484489333784475432780421641293929726139240675179672856274388269393268\"\n ],\n [\n \"11934129596455521040620786944827826205713621633706285934057045369193958244500\",\n \"8037395052364110730298837004334506829870972346962140206007064471173334027475\"\n ]\n ]\n ],\n \"IC\": [\n [\n \"801233197807402683764630185033839955156034586542543249813920835808534245147\",\n \"13286420793149616228297035344471157585445615731792629462934831296345279687002\",\n \"1\"\n ],\n [\n \"17608180544527043978731301492557909061209088433544687588079992534282036547698\",\n \"11240405619785894451348234456278767489162139374206168239508590931049712428392\",\n \"1\"\n ]\n ]\n}") + zkAuthVerifier := zkauthtypes.NewZKAuthVerifier(verificationKey) + zkauthKeeper := zkauthkeeper.NewKeeper(appCodec, keys[zkauthtypes.StoreKey], jwKsMap, zkAuthVerifier, app.MsgServiceRouter()) + app.ZKAuthKeeper = *zkauthKeeper + /**** Module Options ****/ // NOTE: we may consider parsing `appOpts` inside module constructors. For the moment @@ -388,6 +401,7 @@ func NewSimApp( tokenmodule.NewAppModule(appCodec, app.TokenKeeper), collectionmodule.NewAppModule(appCodec, app.CollectionKeeper), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + zkauth.NewAppModule(appCodec, app.ZKAuthKeeper, app.AccountKeeper, app.BankKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -415,6 +429,7 @@ func NewSimApp( vestingtypes.ModuleName, token.ModuleName, collection.ModuleName, + zkauthtypes.ModuleName, ) app.mm.SetOrderEndBlockers( crisistypes.ModuleName, @@ -436,6 +451,7 @@ func NewSimApp( foundation.ModuleName, token.ModuleName, collection.ModuleName, + zkauthtypes.ModuleName, ) // NOTE: The genutils module must occur after staking so that pools are @@ -463,6 +479,7 @@ func NewSimApp( vestingtypes.ModuleName, token.ModuleName, collection.ModuleName, + zkauthtypes.ModuleName, ) // Uncomment if you want to set a custom migration order here. @@ -496,6 +513,19 @@ func NewSimApp( app.SetInitChainer(app.InitChainer) app.SetBeginBlocker(app.BeginBlocker) + // todo: please use this code when integrate zkauth's anteHandler. + //anteHandler, err := appante.NewAnteHandler( + // appante.HandlerOptions{ + // HandlerOptions: ante.HandlerOptions{ + // AccountKeeper: app.AccountKeeper, + // BankKeeper: app.BankKeeper, + // FeegrantKeeper: app.FeeGrantKeeper, + // SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + // SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + // }, + // ZKAuthKeeper: app.ZKAuthKeeper, + // }, + //) anteHandler, err := ante.NewAnteHandler( ante.HandlerOptions{ AccountKeeper: app.AccountKeeper, diff --git a/x/zkauth/ante/zkauth.go b/x/zkauth/ante/zkauth.go new file mode 100644 index 0000000000..1f49b0d265 --- /dev/null +++ b/x/zkauth/ante/zkauth.go @@ -0,0 +1,51 @@ +package ante + +import ( + sdk "github.com/Finschia/finschia-sdk/types" + sdkerrors "github.com/Finschia/finschia-sdk/types/errors" + authsigning "github.com/Finschia/finschia-sdk/x/auth/signing" + zkauthtypes "github.com/Finschia/finschia-sdk/x/zkauth/types" +) + +type ZKAuthMsgDecorator struct { + zk zkauthtypes.ZKAuthKeeper +} + +func NewZKAuthMsgDecorator(zk zkauthtypes.ZKAuthKeeper) ZKAuthMsgDecorator { + return ZKAuthMsgDecorator{zk: zk} +} + +func (zka ZKAuthMsgDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + /* + todo: + If there are multiple msg, the order of the pubKey of the signer and signature must be the same. + - If msg is zkauth, use zkauth signature verification. + - If msg is a general tx, it is verified by general signature verification. + (In this implementation, it is assumed that there is only zkauth msg.) + + If the number of msg and the number of pubKey are not the same, how should matching be done? + Basically, in the case of zkauth msg, ephPubKey must be idempotent for each msg. + */ + + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + } + + msgs := sigTx.GetMsgs() + pubKeys, err := sigTx.GetPubKeys() + if err != nil { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "invalid public key, %s", err) + } + + for i, msg := range msgs { + if zkMsg, ok := msg.(*zkauthtypes.MsgExecution); ok { + // verify ZKAuth signature + if err := zkauthtypes.VerifyZKAuthSignature(ctx, zka.zk, pubKeys[i].Bytes(), zkMsg); err != nil { + return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "invalid zkauth signature") + } + } + } + + return next(ctx, tx, simulate) +} diff --git a/x/zkauth/ante/zkauth_test.go b/x/zkauth/ante/zkauth_test.go new file mode 100644 index 0000000000..e53470e1cc --- /dev/null +++ b/x/zkauth/ante/zkauth_test.go @@ -0,0 +1,58 @@ +package ante_test + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + codectypes "github.com/Finschia/finschia-sdk/codec/types" + "github.com/Finschia/finschia-sdk/crypto/keys/secp256k1" + types2 "github.com/Finschia/finschia-sdk/crypto/types" + sdk "github.com/Finschia/finschia-sdk/types" + banktype "github.com/Finschia/finschia-sdk/x/bank/types" + "github.com/Finschia/finschia-sdk/x/zkauth/ante" + "github.com/Finschia/finschia-sdk/x/zkauth/testutil" + "github.com/Finschia/finschia-sdk/x/zkauth/types" +) + +func TestNewZKAuthMsgDecorator(t *testing.T) { + f := testutil.ZkAuthKeeper(t) + decorator := ante.NewZKAuthMsgDecorator(f.ZKAuthKeeper) + accounts, err := f.CreateTestAccounts(2) + require.NoError(t, err) + + // bank msg + subMsg := &banktype.MsgSend{ + FromAddress: accounts[0].String(), + ToAddress: accounts[1].String(), + Amount: sdk.Coins{sdk.NewInt64Coin("cony", 1000000)}, + } + any, err := codectypes.NewAnyWithValue(subMsg) + const proofStr = "{\n \"pi_a\": [\n \"7575287679446209007446416020137456670042570578978230730578011103770415897062\",\n \"20469978368515629364541212704109752583692706286549284712208570249653184893207\",\n \"1\"\n ],\n \"pi_b\": [\n [\n \"4001119070037193619600086014535210556571209449080681376392853276923728808564\",\n \"18475391841797083641468254159150812922259839776046448499150732610021959794558\"\n ],\n [\n \"19781252109528278034156073207688818205850783935629584279449144780221040670063\",\n \"5873714313814830719712095806732872482213125567325442209795797618441438990229\"\n ],\n [\n \"1\",\n \"0\"\n ]\n ],\n \"pi_c\": [\n \"18920522434978516095250248740518039198650690968720755259416280639852277665022\",\n \"1945774583580804632084048753815901730674007769630810705050114062476636502591\",\n \"1\"\n ],\n \"protocol\": \"groth16\",\n \"curve\": \"bn128\"\n}" + msg := &types.MsgExecution{ + Msgs: []*codectypes.Any{any}, + ZkAuthSignature: types.ZKAuthSignature{ + ZkAuthInputs: &types.ZKAuthInputs{ + ProofPoints: []byte(proofStr), + IssF: "aHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29t", + HeaderBase64: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjU1YzE4OGE4MzU0NmZjMTg4ZTUxNTc2YmE3MjgzNmUwNjAwZThiNzMiLCJ0eXAiOiJKV1QifQ", + AddressSeed: "15035161560159971633800983619931498696152633426768016966057770643262022096073", + }, + MaxBlockHeight: 32754, + }, + } + err = f.TxBuilder.SetMsgs(msg) + require.NoError(t, err) + + ephPubKey, ok := new(big.Int).SetString("18948426102457371978524559226152399917062673825697601263047735920285791872240", 10) + require.True(t, ok) + pub := secp256k1.PubKey{Key: ephPubKey.Bytes()} + tx, err := f.CreateTestTx([]types2.PubKey{&pub}, []uint64{uint64(0)}) + require.NoError(t, err) + + _, err = decorator.AnteHandle(f.Ctx, tx, false, func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + return ctx, nil + }) + require.NoError(t, err) +} diff --git a/x/zkauth/client/cli/tx.go b/x/zkauth/client/cli/tx.go index 50f0b8b8a0..af2e6ded2f 100644 --- a/x/zkauth/client/cli/tx.go +++ b/x/zkauth/client/cli/tx.go @@ -52,8 +52,11 @@ func NewCmdExecution() *cobra.Command { if err != nil { return err } - clientCtx.Codec.UnmarshalInterfaceJSON(bytes, &zkAuthInputs) - maxBlockHeight, err := strconv.ParseUint(args[2], 10, 64) + err = clientCtx.Codec.UnmarshalInterfaceJSON(bytes, &zkAuthInputs) + if err != nil { + return err + } + maxBlockHeight, err := strconv.ParseInt(args[2], 10, 64) if err != nil { return err } diff --git a/x/zkauth/keeper/keeper.go b/x/zkauth/keeper/keeper.go index 6995c848a6..03a16801f7 100644 --- a/x/zkauth/keeper/keeper.go +++ b/x/zkauth/keeper/keeper.go @@ -1,41 +1,44 @@ package keeper import ( - "encoding/json" "fmt" - "io" - "net/http" - "os" - "path/filepath" "strconv" "time" + "github.com/Finschia/ostracon/libs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/Finschia/finschia-sdk/baseapp" "github.com/Finschia/finschia-sdk/codec" storetypes "github.com/Finschia/finschia-sdk/store/types" sdk "github.com/Finschia/finschia-sdk/types" sdkerrors "github.com/Finschia/finschia-sdk/types/errors" "github.com/Finschia/finschia-sdk/x/zkauth/types" - "github.com/Finschia/ostracon/libs/log" - abci "github.com/tendermint/tendermint/abci/types" ) type Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - router *baseapp.MsgServiceRouter + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + jwksMap *types.JWKsMap // JWK manager + zkVerifier types.ZKAuthVerifier // zkp verification key byte + router *baseapp.MsgServiceRouter } +var _ types.ZKAuthKeeper = &Keeper{} + func NewKeeper( cdc codec.BinaryCodec, storeKey storetypes.StoreKey, + jwksMap *types.JWKsMap, + zkVerifier types.ZKAuthVerifier, router *baseapp.MsgServiceRouter, ) *Keeper { - return &Keeper{ - cdc: cdc, - storeKey: storeKey, - router: router, + cdc: cdc, + storeKey: storeKey, + jwksMap: jwksMap, + zkVerifier: zkVerifier, + router: router, } } @@ -82,13 +85,14 @@ func (k Keeper) DispatchMsgs(ctx sdk.Context, msgs []sdk.Msg) ([][]byte, error) return results, nil } -func (k Keeper) FetchJWK(ctx sdk.Context, nodeHome string) { +func (k Keeper) FetchJWK(ctx sdk.Context) { logger := k.Logger(ctx) var defaultZKAuthOAuthProviders = [1]types.OidcProvider{types.Google} var fetchIntervals uint64 go func() { for { select { + // goroutine ends when a timeout occurs case <-ctx.Context().Done(): return default: @@ -96,29 +100,18 @@ func (k Keeper) FetchJWK(ctx sdk.Context, nodeHome string) { provider := types.GetConfig(name) fetchIntervals = provider.FetchIntervals - resp, err := k.GetJWK(provider.JwkEndpoint) + jwks, err := types.FetchJWK(provider.JwkEndpoint) if err != nil { time.Sleep(time.Duration(fetchIntervals) * time.Second) logger.Error(fmt.Sprintf("%s", err)) continue } - file, err := os.Create(filepath.Join(nodeHome, k.CreateJWKFileName(name))) - if err != nil { - time.Sleep(time.Duration(fetchIntervals) * time.Second) - logger.Error(fmt.Sprintf("%s", err)) - continue + // add jwk + for _, jwk := range jwks.Keys { + jwkCopy := jwk + k.jwksMap.AddJWK(&jwkCopy) } - - _, err = io.Copy(file, resp.Body) - if err != nil { - time.Sleep(time.Duration(fetchIntervals) * time.Second) - logger.Error(fmt.Sprintf("%s", err)) - continue - } - - resp.Body.Close() - file.Close() } time.Sleep(time.Duration(fetchIntervals) * time.Second) } @@ -126,45 +119,14 @@ func (k Keeper) FetchJWK(ctx sdk.Context, nodeHome string) { }() } -func (k Keeper) ParseJWKs(byteArray []byte) (jwks []types.JWK, err error) { - var data map[string]interface{} - err = json.Unmarshal(byteArray, &data) - if err != nil { - return jwks, err - } - - for _, v := range data["keys"].([]interface{}) { - var jwk types.JWK - - b, err := json.Marshal(v) - if err != nil { - return jwks, err - } - - if err := json.Unmarshal(b, &jwk); err != nil { - return jwks, err - } - - jwks = append(jwks, jwk) - } - - return jwks, nil +func (k Keeper) GetJWKSize() int { + return k.jwksMap.Size() } -func (k Keeper) GetJWK(endpoint string) (*http.Response, error) { - req, err := http.NewRequest("GET", endpoint, nil) - if err != nil { - return nil, err - } - client := new(http.Client) - resp, err := client.Do(req) - if err != nil { - return nil, err - } - - return resp, nil +func (k Keeper) GetJWK(kid string) *types.JWK { + return k.jwksMap.GetJWK(kid) } -func (k Keeper) CreateJWKFileName(name types.OidcProvider) string { - return fmt.Sprintf("jwk-%s.json", name) +func (k Keeper) GetVerifier() *types.ZKAuthVerifier { + return &k.zkVerifier } diff --git a/x/zkauth/keeper/keeper_test.go b/x/zkauth/keeper/keeper_test.go index 5a29542dd1..7a298ec3f1 100644 --- a/x/zkauth/keeper/keeper_test.go +++ b/x/zkauth/keeper/keeper_test.go @@ -3,20 +3,19 @@ package keeper_test import ( "context" "encoding/json" - "io" "net/http" "net/http/httptest" "os" - "path/filepath" "testing" "time" + "github.com/stretchr/testify/require" + "github.com/Finschia/finschia-sdk/simapp" sdk "github.com/Finschia/finschia-sdk/types" banktypes "github.com/Finschia/finschia-sdk/x/bank/types" "github.com/Finschia/finschia-sdk/x/zkauth/testutil" "github.com/Finschia/finschia-sdk/x/zkauth/types" - "github.com/stretchr/testify/require" ) const testData = `{ @@ -55,44 +54,11 @@ func mockHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(jsonData)) } -func TestGetJWK(t *testing.T) { - testApp := testutil.ZkAuthKeeper(t) - k := testApp.Keeper - server := httptest.NewServer(http.HandlerFunc(mockHandler)) - defer server.Close() - - res, err := k.GetJWK(server.URL) - defer res.Body.Close() - require.NoError(t, err) - - expected := testData - bodyBytes, err := io.ReadAll(res.Body) - bodyString := string(bodyBytes) - require.Equal(t, expected, bodyString) -} - -func TestParseJWKs(t *testing.T) { - testApp := testutil.ZkAuthKeeper(t) - k := testApp.Keeper - server := httptest.NewServer(http.HandlerFunc(mockHandler)) - defer server.Close() - - res, err := k.GetJWK(server.URL) - defer res.Body.Close() - require.NoError(t, err) - - bodyBytes, err := io.ReadAll(res.Body) - - jwks, err := k.ParseJWKs(bodyBytes) - require.NoError(t, err) - require.Equal(t, 3, len(jwks)) -} - func TestFetchJwk(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockHandler)) defer server.Close() testApp := testutil.ZkAuthKeeper(t) - k := testApp.Keeper + k := testApp.ZKAuthKeeper ctx := testApp.Ctx timeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -101,23 +67,19 @@ func TestFetchJwk(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(tempDir) - k.FetchJWK(ctx.WithContext(timeoutCtx), tempDir) + k.FetchJWK(ctx.WithContext(timeoutCtx)) <-timeoutCtx.Done() - content, err := os.ReadFile(filepath.Join(tempDir, k.CreateJWKFileName(types.Google))) + require.True(t, k.GetJWKSize() == 3) + var expectedObj types.JWKs + err = json.Unmarshal([]byte(testData), &expectedObj) require.NoError(t, err) - var expectedObj []types.JWK - json.Unmarshal([]byte(testData), &expectedObj) - - var actualObj []types.JWK - json.Unmarshal(content, &actualObj) - require.Equal(t, expectedObj, actualObj) } func TestDispatchMsgs(t *testing.T) { testApp := testutil.ZkAuthKeeper(t) - app, k, ctx := testApp.Simapp, testApp.Keeper, testApp.Ctx + app, k, ctx := testApp.Simapp, testApp.ZKAuthKeeper, testApp.Ctx addrs := simapp.AddTestAddrs(app, ctx, 2, sdk.NewInt(100)) fromAddr := addrs[0] diff --git a/x/zkauth/keeper/msg_server_test.go b/x/zkauth/keeper/msg_server_test.go index 403b849be3..bfe0abd2ed 100644 --- a/x/zkauth/keeper/msg_server_test.go +++ b/x/zkauth/keeper/msg_server_test.go @@ -3,18 +3,19 @@ package keeper_test import ( "testing" + "github.com/stretchr/testify/require" + "github.com/Finschia/finschia-sdk/simapp" sdk "github.com/Finschia/finschia-sdk/types" banktypes "github.com/Finschia/finschia-sdk/x/bank/types" "github.com/Finschia/finschia-sdk/x/zkauth/keeper" datest "github.com/Finschia/finschia-sdk/x/zkauth/testutil" "github.com/Finschia/finschia-sdk/x/zkauth/types" - "github.com/stretchr/testify/require" ) func setupMsgServer(t testing.TB) (types.MsgServer, *simapp.SimApp, sdk.Context) { testApp := datest.ZkAuthKeeper(t) - k, ctx := testApp.Keeper, testApp.Ctx + k, ctx := testApp.ZKAuthKeeper, testApp.Ctx return keeper.NewMsgServerImpl(*k), testApp.Simapp, ctx } diff --git a/x/zkauth/testutil/keeper.go b/x/zkauth/testutil/keeper.go index 144025bc0a..4e7cce06a7 100644 --- a/x/zkauth/testutil/keeper.go +++ b/x/zkauth/testutil/keeper.go @@ -3,29 +3,37 @@ package testutil import ( "testing" - "github.com/Finschia/finschia-sdk/simapp" - "github.com/Finschia/finschia-sdk/x/zkauth/keeper" - "github.com/Finschia/finschia-sdk/x/zkauth/types" - "github.com/stretchr/testify/require" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmdb "github.com/tendermint/tm-db" + "github.com/Finschia/finschia-sdk/client" "github.com/Finschia/finschia-sdk/codec" codectypes "github.com/Finschia/finschia-sdk/codec/types" + types2 "github.com/Finschia/finschia-sdk/crypto/types" + "github.com/Finschia/finschia-sdk/simapp" "github.com/Finschia/finschia-sdk/store" storetypes "github.com/Finschia/finschia-sdk/store/types" + "github.com/Finschia/finschia-sdk/testutil/testdata" sdk "github.com/Finschia/finschia-sdk/types" + "github.com/Finschia/finschia-sdk/types/tx/signing" + xauthsigning "github.com/Finschia/finschia-sdk/x/auth/signing" + authtypes "github.com/Finschia/finschia-sdk/x/auth/types" + minttypes "github.com/Finschia/finschia-sdk/x/mint/types" + "github.com/Finschia/finschia-sdk/x/zkauth/keeper" + "github.com/Finschia/finschia-sdk/x/zkauth/types" ) type TestApp struct { - Simapp *simapp.SimApp - Keeper *keeper.Keeper - Ctx sdk.Context + Simapp *simapp.SimApp + ZKAuthKeeper *keeper.Keeper + Ctx sdk.Context + ClientCtx client.Context + TxBuilder client.TxBuilder } func ZkAuthKeeper(t testing.TB) TestApp { - checkTx := false + const checkTx = false app := simapp.Setup(checkTx) storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) @@ -39,19 +47,123 @@ func ZkAuthKeeper(t testing.TB) TestApp { registry := codectypes.NewInterfaceRegistry() cdc := codec.NewProtoCodec(registry) + var verificationKey = []byte("{\n \"protocol\": \"groth16\",\n \"curve\": \"bn128\",\n \"nPublic\": 1,\n \"vk_alpha_1\": [\n \"20491192805390485299153009773594534940189261866228447918068658471970481763042\",\n \"9383485363053290200918347156157836566562967994039712273449902621266178545958\",\n \"1\"\n ],\n \"vk_beta_2\": [\n [\n \"6375614351688725206403948262868962793625744043794305715222011528459656738731\",\n \"4252822878758300859123897981450591353533073413197771768651442665752259397132\"\n ],\n [\n \"10505242626370262277552901082094356697409835680220590971873171140371331206856\",\n \"21847035105528745403288232691147584728191162732299865338377159692350059136679\"\n ],\n [\n \"1\",\n \"0\"\n ]\n ],\n \"vk_gamma_2\": [\n [\n \"10857046999023057135944570762232829481370756359578518086990519993285655852781\",\n \"11559732032986387107991004021392285783925812861821192530917403151452391805634\"\n ],\n [\n \"8495653923123431417604973247489272438418190587263600148770280649306958101930\",\n \"4082367875863433681332203403145435568316851327593401208105741076214120093531\"\n ],\n [\n \"1\",\n \"0\"\n ]\n ],\n \"vk_delta_2\": [\n [\n \"21349319915249622662700217004338779716430783387183352766870647565870141979289\",\n \"8213816744021090866451311756048660670381089332123677295675725952502733471420\"\n ],\n [\n \"4787213629490370557685854255230879988945206163033639129474026644007741911075\",\n \"20003855859301921415178037270191878217707285640767940877063768682564788786247\"\n ],\n [\n \"1\",\n \"0\"\n ]\n ],\n \"vk_alphabeta_12\": [\n [\n [\n \"2029413683389138792403550203267699914886160938906632433982220835551125967885\",\n \"21072700047562757817161031222997517981543347628379360635925549008442030252106\"\n ],\n [\n \"5940354580057074848093997050200682056184807770593307860589430076672439820312\",\n \"12156638873931618554171829126792193045421052652279363021382169897324752428276\"\n ],\n [\n \"7898200236362823042373859371574133993780991612861777490112507062703164551277\",\n \"7074218545237549455313236346927434013100842096812539264420499035217050630853\"\n ]\n ],\n [\n [\n \"7077479683546002997211712695946002074877511277312570035766170199895071832130\",\n \"10093483419865920389913245021038182291233451549023025229112148274109565435465\"\n ],\n [\n \"4595479056700221319381530156280926371456704509942304414423590385166031118820\",\n \"19831328484489333784475432780421641293929726139240675179672856274388269393268\"\n ],\n [\n \"11934129596455521040620786944827826205713621633706285934057045369193958244500\",\n \"8037395052364110730298837004334506829870972346962140206007064471173334027475\"\n ]\n ]\n ],\n \"IC\": [\n [\n \"801233197807402683764630185033839955156034586542543249813920835808534245147\",\n \"13286420793149616228297035344471157585445615731792629462934831296345279687002\",\n \"1\"\n ],\n [\n \"17608180544527043978731301492557909061209088433544687588079992534282036547698\",\n \"11240405619785894451348234456278767489162139374206168239508590931049712428392\",\n \"1\"\n ]\n ]\n}") + + jwKsMap := types.NewJWKs() + jwKsMap.AddJWK(&types.JWK{ + Kty: "RSA", + E: "AQAB", + N: "q0CrF3x3aYsjr0YOLMOAhEGMvyFp6o4RqyEdUrnTDYkhZbcud-fJEQafCTnjS9QHN1IjpuK6gpx5i3-Z63vRjs5EQX7lP1jG8Qg-CnBdTTLw4uJi7RmmlKPsYaO1DbNkFO2uEN62sOOzmJCh1od3CZXI1UYH5cvZ_sLJaN2A4TwvUTU3aXlXbUNJz_Hy3l0q1Jjta75NrJtJ7Pfj9tVXs8qXp15tZXrnbaM-AI0puswt35VsQbmLwUovFFGeToo5q2c_c1xYnV5uQYMadANekGPRFPM9JZpSSIvH0Lv_f15V2zRqmIgX7a3RcmTnr3-w3QNQTogdy-MogxPUdRbxow", + Alg: "RS256", + Kid: "55c188a83546fc188e51576ba72836e0600e8b73", + }) + jwKsMap.AddJWK(&types.JWK{ + N: "pOpd5-7RpMvcfBcSjqlTNYjGg3YRwYRV9T9k7eDOEWgMBQEs6ii3cjcuoa1oD6N48QJmcNvAme_ud985DV2mQpOaCUy22MVRKI8DHxAKGWzZO5yzn6otsN9Vy0vOEO_I-vnmrO1-1ONFuH2zieziaXCUVh9087dRkM9qaQYt6QJhMmiNpyrbods6AsU8N1jeAQl31ovHWGGk8axXNmwbx3dDZQhx-t9ZD31oF-usPhFZtM92mxgehDqi2kpvFmM0nzSVgPrOXlbDb9ztg8lclxKwnT1EtcwHUq4FeuOPQMtZ2WehrY10OvsqS5ml3mxXUQEXrtYfa5V1v4o3rWx9Ow", + Kty: "RSA", + Alg: "RS256", + E: "AQAB", + Kid: "6f9777a685907798ef794062c00b65d66c240b1b", + }) + k := keeper.NewKeeper( cdc, storeKey, + jwKsMap, + types.NewZKAuthVerifier(verificationKey), app.MsgServiceRouter(), ) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + encodingConfig := simapp.MakeTestEncodingConfig() + encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) + testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry) + + clientCtx := client.Context{}.WithTxConfig(encodingConfig.TxConfig) + testApp := TestApp{ - Simapp: app, - Keeper: k, - Ctx: ctx, + Simapp: app, + ZKAuthKeeper: k, + Ctx: ctx, + ClientCtx: clientCtx, + TxBuilder: clientCtx.TxConfig.NewTxBuilder(), } return testApp } + +func (t *TestApp) CreateTestAccounts(numAcc int) ([]authtypes.AccountI, error) { + var accounts []authtypes.AccountI + + for i := 0; i < numAcc; i++ { + _, _, addr := testdata.KeyTestPubAddr() + acc, err := t.addAccount(addr, i) + if err != nil { + return nil, err + } + + accounts = append(accounts, acc) + } + return accounts, nil +} + +func (t *TestApp) addAccount(accAddr sdk.AccAddress, accNum int) (authtypes.AccountI, error) { + acc := t.Simapp.AccountKeeper.NewAccountWithAddress(t.Ctx, accAddr) + if err := acc.SetAccountNumber(uint64(accNum)); err != nil { + return nil, err + } + + t.Simapp.AccountKeeper.SetAccount(t.Ctx, acc) + someCoins := sdk.Coins{sdk.NewInt64Coin("cony", 10000000)} + if err := t.Simapp.BankKeeper.MintCoins(t.Ctx, minttypes.ModuleName, someCoins); err != nil { + return nil, err + } + + if err := t.Simapp.BankKeeper.SendCoinsFromModuleToAccount(t.Ctx, minttypes.ModuleName, accAddr, someCoins); err != nil { + return nil, err + } + + return acc, nil +} + +func (t *TestApp) AddTestAccounts(addrs []string) ([]authtypes.AccountI, error) { + accounts := make([]authtypes.AccountI, 0) + + for i, addrStr := range addrs { + addr, err := sdk.AccAddressFromBech32(addrStr) + if err != nil { + return nil, err + } + + acc, err := t.addAccount(addr, i) + if err != nil { + return nil, err + } + + accounts = append(accounts, acc) + } + + return accounts, nil +} + +func (t *TestApp) CreateTestTx(pubs []types2.PubKey, accSeqs []uint64) (xauthsigning.Tx, error) { + sigsV2 := make([]signing.SignatureV2, 0) + for i, pub := range pubs { + sigV2 := signing.SignatureV2{ + PubKey: pub, + Data: &signing.SingleSignatureData{ + SignMode: t.ClientCtx.TxConfig.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: accSeqs[i], + } + + sigsV2 = append(sigsV2, sigV2) + } + err := t.TxBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, err + } + + return t.TxBuilder.GetTx(), nil +} diff --git a/x/zkauth/testutil/testdata/verification_key.json b/x/zkauth/testutil/testdata/verification_key.json new file mode 100644 index 0000000000..a739da7bfd --- /dev/null +++ b/x/zkauth/testutil/testdata/verification_key.json @@ -0,0 +1,94 @@ +{ + "protocol": "groth16", + "curve": "bn128", + "nPublic": 1, + "vk_alpha_1": [ + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + "1" + ], + "vk_beta_2": [ + [ + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + ], + [ + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + ], + [ + "1", + "0" + ] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + ], + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + ], + [ + "1", + "0" + ] + ], + "vk_delta_2": [ + [ + "21349319915249622662700217004338779716430783387183352766870647565870141979289", + "8213816744021090866451311756048660670381089332123677295675725952502733471420" + ], + [ + "4787213629490370557685854255230879988945206163033639129474026644007741911075", + "20003855859301921415178037270191878217707285640767940877063768682564788786247" + ], + [ + "1", + "0" + ] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + "801233197807402683764630185033839955156034586542543249813920835808534245147", + "13286420793149616228297035344471157585445615731792629462934831296345279687002", + "1" + ], + [ + "17608180544527043978731301492557909061209088433544687588079992534282036547698", + "11240405619785894451348234456278767489162139374206168239508590931049712428392", + "1" + ] + ] +} \ No newline at end of file diff --git a/x/zkauth/types/address.go b/x/zkauth/types/address.go new file mode 100644 index 0000000000..806437469e --- /dev/null +++ b/x/zkauth/types/address.go @@ -0,0 +1,49 @@ +package types + +import ( + "encoding/binary" + "errors" + "math/big" + "strings" + + "github.com/Finschia/finschia-sdk/types" + "github.com/tendermint/crypto/blake2b" +) + +// AccAddressFromAddressSeed create an AccAddress from addressSeed string and iss string +// AccAddress = blake2b_256(iss_L, iss, addressSeed) +func AccAddressFromAddressSeed(addrSeed, iss string) (types.AccAddress, error) { + if len(strings.TrimSpace(addrSeed)) == 0 { + return types.AccAddress{}, errors.New("empty address seed string is not allowed") + } + + // convert addrSeed string to big endian bytes + addrSeedBigInt, ok := new(big.Int).SetString(addrSeed, 10) + if !ok { + return types.AccAddress{}, errors.New("invalid address seed") + } + addrSeedBytes := addrSeedBigInt.Bytes() + + if iss == "accounts.google.com" { + iss = "https://accounts.google.com" + } + issBytes := []byte(iss) + + // convert the issBytes length to big endian 2 bytes + issL := make([]byte, 2) + binary.BigEndian.PutUint16(issL, uint16(len(issBytes))) + + // hash by blake2b + hasher, err := blake2b.New256(nil) + if err != nil { + return types.AccAddress{}, err + } + + hasher.Write(issL) + hasher.Write(issBytes) + hasher.Write(addrSeedBytes) + + addrBytes := hasher.Sum(nil) + + return addrBytes, nil +} diff --git a/x/zkauth/types/address_test.go b/x/zkauth/types/address_test.go new file mode 100644 index 0000000000..d51f44310c --- /dev/null +++ b/x/zkauth/types/address_test.go @@ -0,0 +1,23 @@ +package types + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestAccAddressFromAddressSeed(t *testing.T) { + addressSeeds := map[string]string{ + "6837746941479125904804058040336379861713403246941015384197322102329773944159": "link15uxm4w27fyuy0dcczvnu7edq7dnuf3r6t97t82j3fuc2pcfux4zsrxq8mu", + "15631290963325153295531082175644052605061447806381313417788319283841674180543": "link1etuxuak3q5tgjs8ew09h5q4yct5vrcc7thl2llcjpk72fuq8r50q958h26", + "11967859756179757815812269077480243698674971831305032302706373939074904776780": "link1muet5uz59utufwvux67tn5lgez28dasg4renvzhuku4a0jz459cs7m6tuw", + "10248927216838193947032332743622432828052626024625324374470479888091711985426": "link14x0mfl95kxg739fqpqdyvrykhd9ghsqk4uew3tr4aqcprfmtpaesh858kj", + "16423356555234621455999265971523270820664611063213539441365583262907894845196": "link1a4m70fx99udpr6c2excsgxa9x0vmra9gvzs4w6cz78dx3djrhuaqlppfp2", + } + + iss := "accounts.google.com" + for seed, exp := range addressSeeds { + acc, err := AccAddressFromAddressSeed(seed, iss) + require.NoError(t, err) + require.Equal(t, exp, acc.String()) + } +} diff --git a/x/zkauth/types/config.go b/x/zkauth/types/config.go index d55ed5175d..613d6e2267 100644 --- a/x/zkauth/types/config.go +++ b/x/zkauth/types/config.go @@ -16,3 +16,7 @@ func GetConfig(provider OidcProvider) ProviderConfig { panic("unexpected provider") } } + +var SupportedOidcProviders = map[string]OidcProvider{ + "https://accounts.google.com": Google, +} diff --git a/x/zkauth/types/errors.go b/x/zkauth/types/errors.go index eadb6ffc7c..4715a1b194 100644 --- a/x/zkauth/types/errors.go +++ b/x/zkauth/types/errors.go @@ -7,6 +7,8 @@ import ( const zkAuthCodespace = ModuleName var ( - ErrInvalidZkAuthInputs = sdkerrors.Register(zkAuthCodespace, 2, "invalid zkauth inputs") - ErrSample = sdkerrors.Register(zkAuthCodespace, 1100, "sample error") + ErrInvalidZKAuthSignature = sdkerrors.Register(zkAuthCodespace, 2, "invalid ZKAuthSignature") + ErrInvalidMessage = sdkerrors.Register(zkAuthCodespace, 3, "invalid message") + ErrInvalidZkAuthInputs = sdkerrors.Register(zkAuthCodespace, 4, "invalid zkauth inputs") + ErrSample = sdkerrors.Register(zkAuthCodespace, 1100, "sample error") ) diff --git a/x/zkauth/types/expected_keepers.go b/x/zkauth/types/expected_keepers.go index a64a1facab..4fd81077a3 100644 --- a/x/zkauth/types/expected_keepers.go +++ b/x/zkauth/types/expected_keepers.go @@ -1,11 +1,15 @@ package types import ( - "github.com/Finschia/finschia-sdk/x/auth/types" - sdk "github.com/Finschia/finschia-sdk/types" + "github.com/Finschia/finschia-sdk/x/auth/types" ) +type ZKAuthKeeper interface { + GetJWK(kid string) *JWK + GetVerifier() *ZKAuthVerifier +} + // AccountKeeper defines the expected account keeper used for simulations (noalias) type AccountKeeper interface { GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI diff --git a/x/zkauth/types/jwks.go b/x/zkauth/types/jwks.go new file mode 100644 index 0000000000..590c549184 --- /dev/null +++ b/x/zkauth/types/jwks.go @@ -0,0 +1,127 @@ +package types + +import ( + "crypto/rsa" + "encoding/base64" + "encoding/json" + "math/big" + "net/http" + "sync" +) + +//type JWK struct { +// Kty string `json:"kty,omitempty"` +// E string `json:"e.omitempty"` +// N string `json:"n,omitempty"` +// Alg string `json:"alg.omitempty"` +// Kid string `json:"kid,omitempty"` +//} + +type JWKs struct { + Keys []JWK `json:"keys"` +} + +// FetchJWK retrieve Certificates +func FetchJWK(endpoint string) (*JWKs, error) { + req, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + return nil, err + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var jwks JWKs + err = json.NewDecoder(resp.Body).Decode(&jwks) + if err != nil { + return nil, err + } + + return &jwks, nil +} + +func Base64ToBigInt(encoded string) (*big.Int, error) { + decoded, err := base64.RawURLEncoding.DecodeString(encoded) + if err != nil { + return nil, err + } + + n := big.NewInt(0) + n.SetBytes(decoded) + + return n, nil +} + +func (jwk *JWK) NBytes() ([]byte, error) { + nBigInt, err := Base64ToBigInt(jwk.N) + if err != nil { + return nil, err + } + return nBigInt.Bytes(), nil +} + +func (jwk *JWK) PubKey() (*rsa.PublicKey, error) { + n, err := Base64ToBigInt(jwk.N) + if err != nil { + return nil, err + } + + e, err := Base64ToBigInt(jwk.E) + if err != nil { + return nil, err + } + + pubKey := rsa.PublicKey{ + N: n, + E: int(e.Int64()), + } + + return &pubKey, nil +} + +type JWKsMap struct { + sync.RWMutex + jwks map[string]*JWK +} + +func NewJWKs() *JWKsMap { + js := JWKsMap{} + js.jwks = make(map[string]*JWK) + + return &js +} + +func (js *JWKsMap) AddJWK(jwk *JWK) bool { + // lock before writing + js.Lock() + defer js.Unlock() + + if _, ok := js.jwks[jwk.Kid]; ok { + return false + } + + js.jwks[jwk.Kid] = jwk + return true +} + +func (js *JWKsMap) GetJWK(kid string) *JWK { + js.RLock() + defer js.RUnlock() + + if jwk, ok := js.jwks[kid]; ok { + return jwk + } + + return nil +} + +func (js *JWKsMap) Size() int { + js.RLock() + defer js.RUnlock() + + return len(js.jwks) +} diff --git a/x/zkauth/types/jwt.go b/x/zkauth/types/jwt.go new file mode 100644 index 0000000000..d01b1e08f9 --- /dev/null +++ b/x/zkauth/types/jwt.go @@ -0,0 +1,7 @@ +package types + +type JWTHeader struct { + Alg string `json:"alg,omitempty"` + Kid string `json:"kid,omitempty"` + Typ string `json:"typ,omitempty"` +} diff --git a/x/zkauth/types/msgs.go b/x/zkauth/types/msgs.go index b5e3251a61..91e6a359ff 100644 --- a/x/zkauth/types/msgs.go +++ b/x/zkauth/types/msgs.go @@ -27,9 +27,62 @@ func NewMsgExecution(msgs []sdk.Msg, zkauthSignature ZKAuthSignature) *MsgExecut } } -func (msg MsgExecution) GetMessages() ([]sdk.Msg, error) { - msgs := make([]sdk.Msg, len(msg.Msgs)) - for i, msgAny := range msg.Msgs { +func ValidateZkAuthSignature(signature ZKAuthSignature) error { + if signature.ZkAuthInputs == nil { + return sdkerrors.Wrap(ErrInvalidZKAuthSignature, "ZkAuthInputs is empty") + } + + if err := signature.ZkAuthInputs.Validate(); err != nil { + return err + } + + if signature.MaxBlockHeight == 0 { + return sdkerrors.Wrapf(ErrInvalidZKAuthSignature, "invalid max_block_height %d", signature.MaxBlockHeight) + } + + return nil +} + +func (e *MsgExecution) SetMsgs(msgs []sdk.Msg) error { + anys := make([]*cdctypes.Any, len(msgs)) + for i, msg := range msgs { + var err error + anys[i], err = cdctypes.NewAnyWithValue(msg) + if err != nil { + return err + } + } + e.Msgs = anys + return nil +} + +func (e *MsgExecution) ValidateBasic() error { + if len(e.GetMsgs()) == 0 { + return sdkerrors.Wrap(ErrInvalidMessage, "message is empty") + } + + // validate msg + for _, m := range e.GetMsgs() { + message, ok := m.GetCachedValue().(sdk.Msg) + if !ok { + return sdkerrors.Wrapf(ErrInvalidMessage, "message contains %T which is not a sdk.MsgRequest", m) + } + if err := message.ValidateBasic(); err != nil { + return sdkerrors.Wrap(ErrInvalidMessage, err.Error()) + } + } + + // validate signature + if err := ValidateZkAuthSignature(e.ZkAuthSignature); err != nil { + return err + } + + return nil +} + +func (e *MsgExecution) GetMessages() ([]sdk.Msg, error) { + msgs := make([]sdk.Msg, len(e.Msgs)) + for i, msgAny := range e.Msgs { msg, ok := msgAny.GetCachedValue().(sdk.Msg) if !ok { return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "messages contains %T which is not a sdk.MsgRequest", msgAny) @@ -40,16 +93,19 @@ func (msg MsgExecution) GetMessages() ([]sdk.Msg, error) { return msgs, nil } -func (msg MsgExecution) GetSigners() []sdk.AccAddress { - // TODO: - return []sdk.AccAddress{} +func (e *MsgExecution) GetSigners() []sdk.AccAddress { + addr, err := e.ZkAuthSignature.ZkAuthInputs.AccAddress() + if err != nil { + return nil + } + + return []sdk.AccAddress{addr} } -func (msg MsgExecution) GetSignBytes() []byte { - bz := ModuleCdc.MustMarshalJSON(&msg) +func (e *MsgExecution) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(e) return sdk.MustSortJSON(bz) } -func (msg MsgExecution) ValidateBasic() error { return nil } -func (msg MsgExecution) Route() string { return RouterKey } -func (msg MsgExecution) Type() string { return sdk.MsgTypeURL(&msg) } +func (e *MsgExecution) Route() string { return RouterKey } +func (e *MsgExecution) Type() string { return sdk.MsgTypeURL(e) } diff --git a/x/zkauth/types/msgs_test.go b/x/zkauth/types/msgs_test.go new file mode 100644 index 0000000000..35866b7c78 --- /dev/null +++ b/x/zkauth/types/msgs_test.go @@ -0,0 +1,102 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/Finschia/finschia-sdk/crypto/keys/secp256k1" + sdk "github.com/Finschia/finschia-sdk/types" + "github.com/Finschia/finschia-sdk/x/bank/types" +) + +func TestMsgExecutionValidateBasic(t *testing.T) { + addrs := make([]sdk.AccAddress, 2) + for i := range addrs { + addrs[i] = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + } + + const proofStr = "{\n \"pi_a\": [\n \"7575287679446209007446416020137456670042570578978230730578011103770415897062\",\n \"20469978368515629364541212704109752583692706286549284712208570249653184893207\",\n \"1\"\n ],\n \"pi_b\": [\n [\n \"4001119070037193619600086014535210556571209449080681376392853276923728808564\",\n \"18475391841797083641468254159150812922259839776046448499150732610021959794558\"\n ],\n [\n \"19781252109528278034156073207688818205850783935629584279449144780221040670063\",\n \"5873714313814830719712095806732872482213125567325442209795797618441438990229\"\n ],\n [\n \"1\",\n \"0\"\n ]\n ],\n \"pi_c\": [\n \"18920522434978516095250248740518039198650690968720755259416280639852277665022\",\n \"1945774583580804632084048753815901730674007769630810705050114062476636502591\",\n \"1\"\n ],\n \"protocol\": \"groth16\",\n \"curve\": \"bn128\"\n}" + validZKAuthInputs := &ZKAuthInputs{ + ProofPoints: []byte(proofStr), + IssF: "aHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29t", + HeaderBase64: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjU1YzE4OGE4MzU0NmZjMTg4ZTUxNTc2YmE3MjgzNmUwNjAwZThiNzMiLCJ0eXAiOiJKV1QifQ", + AddressSeed: "15035161560159971633800983619931498696152633426768016966057770643262022096073", + } + + type TestMsg struct { + addr string + } + + testcase := map[string]struct { + msgs []sdk.Msg + zkAuthSignature ZKAuthSignature + valid bool + }{ + "valid msg": { + []sdk.Msg{ + &types.MsgSend{ + FromAddress: addrs[0].String(), + ToAddress: addrs[1].String(), + Amount: sdk.NewCoins(sdk.NewInt64Coin("cony", 1000)), + }, + }, + ZKAuthSignature{ + ZkAuthInputs: validZKAuthInputs, + MaxBlockHeight: 32754, + }, + true, + }, + "no msg": { + nil, + ZKAuthSignature{ + ZkAuthInputs: validZKAuthInputs, + MaxBlockHeight: 32754, + }, + false, + }, + "empty signature": { + []sdk.Msg{ + &types.MsgSend{ + FromAddress: addrs[0].String(), + ToAddress: addrs[1].String(), + Amount: sdk.NewCoins(sdk.NewInt64Coin("cony", 1000)), + }, + }, + ZKAuthSignature{}, + false, + }, + "max block height is zero": { + []sdk.Msg{ + &types.MsgSend{ + FromAddress: addrs[0].String(), + ToAddress: addrs[1].String(), + Amount: sdk.NewCoins(sdk.NewInt64Coin("cony", 1000)), + }, + }, + ZKAuthSignature{ + ZkAuthInputs: validZKAuthInputs, + MaxBlockHeight: 0, + }, + false, + }, + } + + for name, tc := range testcase { + t.Run(name, func(t *testing.T) { + msg := MsgExecution{ + ZkAuthSignature: tc.zkAuthSignature, + } + err := msg.SetMsgs(tc.msgs) + require.NoError(t, err) + + err = msg.ValidateBasic() + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } + +} diff --git a/x/zkauth/types/tx.pb.go b/x/zkauth/types/tx.pb.go index 219d09efc0..4dc7a7829c 100644 --- a/x/zkauth/types/tx.pb.go +++ b/x/zkauth/types/tx.pb.go @@ -130,7 +130,7 @@ func (m *MsgExecutionResponse) GetResults() [][]byte { type ZKAuthSignature struct { ZkAuthInputs *ZKAuthInputs `protobuf:"bytes,1,opt,name=zk_auth_inputs,json=zkAuthInputs,proto3" json:"zk_auth_inputs,omitempty"` - MaxBlockHeight uint64 `protobuf:"varint,2,opt,name=max_block_height,json=maxBlockHeight,proto3" json:"max_block_height,omitempty"` + MaxBlockHeight int64 `protobuf:"varint,2,opt,name=max_block_height,json=maxBlockHeight,proto3" json:"max_block_height,omitempty"` } func (m *ZKAuthSignature) Reset() { *m = ZKAuthSignature{} } @@ -173,7 +173,7 @@ func (m *ZKAuthSignature) GetZkAuthInputs() *ZKAuthInputs { return nil } -func (m *ZKAuthSignature) GetMaxBlockHeight() uint64 { +func (m *ZKAuthSignature) GetMaxBlockHeight() int64 { if m != nil { return m.MaxBlockHeight } @@ -191,7 +191,7 @@ func init() { proto.RegisterFile("finschia/zkauth/v1beta1/tx.proto", fileDescrip var fileDescriptor_aa153348b02c6cc1 = []byte{ // 419 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x4f, 0x8e, 0xd3, 0x30, - 0x14, 0xc6, 0x13, 0xa6, 0x02, 0xe1, 0xa9, 0x66, 0x20, 0xaa, 0x44, 0x5b, 0x89, 0x10, 0x55, 0x20, + 0x14, 0xc6, 0x13, 0x3a, 0x02, 0xe1, 0xa9, 0x66, 0x20, 0xaa, 0x44, 0x5b, 0x89, 0x10, 0x55, 0x20, 0x65, 0x41, 0x6d, 0xa6, 0x9c, 0x60, 0x22, 0xf1, 0x67, 0x34, 0xea, 0x26, 0xec, 0xba, 0x89, 0x9c, 0xd4, 0x75, 0xa2, 0x34, 0x71, 0x54, 0xdb, 0x28, 0xed, 0x09, 0x58, 0x72, 0x17, 0x38, 0x44, 0xc5, 0xaa, 0x4b, 0x56, 0x08, 0xb5, 0x17, 0x41, 0xb1, 0x63, 0x28, 0x48, 0x15, 0xb3, 0xf3, 0xfb, 0xfc, @@ -200,22 +200,22 @@ var fileDescriptor_aa153348b02c6cc1 = []byte{ 0x04, 0xd4, 0x04, 0x6c, 0x89, 0xe1, 0x20, 0x61, 0xbc, 0x60, 0x3c, 0x52, 0x18, 0xd2, 0x85, 0x7e, 0x33, 0xec, 0x51, 0x46, 0x99, 0xd6, 0x9b, 0x53, 0xab, 0x0e, 0x28, 0x63, 0x74, 0x49, 0x90, 0xaa, 0x62, 0xb9, 0x40, 0xb8, 0x5c, 0xb7, 0x57, 0xcf, 0x4f, 0xc5, 0x68, 0x7b, 0x2a, 0x6a, 0xf4, 0xc5, - 0x06, 0xdd, 0x29, 0xa7, 0x6f, 0x6a, 0x92, 0x48, 0x91, 0xb1, 0xd2, 0x79, 0x07, 0x3a, 0x05, 0xa7, - 0xbc, 0x6f, 0x7b, 0x67, 0xfe, 0xf9, 0xa4, 0x07, 0x75, 0x03, 0x68, 0x1a, 0xc0, 0xeb, 0x72, 0x1d, + 0x06, 0xdd, 0x29, 0xa7, 0x6f, 0x6a, 0x92, 0x48, 0x91, 0xb1, 0xd2, 0x79, 0x07, 0xce, 0x0a, 0x4e, + 0x79, 0xdf, 0xf6, 0x3a, 0xfe, 0xf9, 0xa4, 0x07, 0x75, 0x03, 0x68, 0x1a, 0xc0, 0xeb, 0x72, 0x1d, 0x3c, 0xfd, 0xf6, 0x75, 0x3c, 0xe0, 0xf3, 0x1c, 0x4e, 0x39, 0x7d, 0xe9, 0xb5, 0x7e, 0xbf, 0x2d, 0x42, 0x65, 0xe0, 0xcc, 0xc0, 0xe3, 0x4d, 0x1e, 0x35, 0x57, 0x11, 0xcf, 0x68, 0x89, 0x85, 0x5c, 0x91, 0xfe, 0x3d, 0xcf, 0xf6, 0xcf, 0x27, 0x3e, 0x3c, 0x31, 0x00, 0x38, 0xbb, 0xbd, 0x96, 0x22, - 0xfd, 0x60, 0xf8, 0xa0, 0xb3, 0xfd, 0xf1, 0xcc, 0x0a, 0x2f, 0x37, 0xf9, 0x5f, 0xf2, 0xe8, 0x15, - 0xe8, 0x1d, 0x87, 0x0e, 0x09, 0xaf, 0x58, 0xc9, 0x89, 0xd3, 0x07, 0x0f, 0x56, 0x84, 0xcb, 0xa5, - 0xd0, 0xf9, 0xbb, 0xa1, 0x29, 0x47, 0x9f, 0x6c, 0x70, 0xf9, 0x8f, 0xb9, 0x73, 0x0b, 0x2e, 0x4c, - 0xc2, 0xac, 0xac, 0xa4, 0x7a, 0xd4, 0xc4, 0x7b, 0xf1, 0x9f, 0x78, 0x37, 0x0a, 0x0e, 0xbb, 0x3a, - 0x95, 0xae, 0x1c, 0x1f, 0x3c, 0x2a, 0x70, 0x1d, 0xc5, 0x4b, 0x96, 0xe4, 0x51, 0x4a, 0x32, 0x9a, - 0x0a, 0xf5, 0xdb, 0x4e, 0x78, 0x51, 0xe0, 0x3a, 0x68, 0xe4, 0xf7, 0x4a, 0x9d, 0xa4, 0xe0, 0x6c, + 0xfd, 0x60, 0xf8, 0xe0, 0x6c, 0xfb, 0xe3, 0x99, 0x15, 0x5e, 0x6e, 0xf2, 0xbf, 0xe4, 0xd1, 0x2b, + 0xd0, 0x3b, 0x0e, 0x1d, 0x12, 0x5e, 0xb1, 0x92, 0x13, 0xa7, 0x0f, 0x1e, 0xac, 0x08, 0x97, 0x4b, + 0xa1, 0xf3, 0x77, 0x43, 0x53, 0x8e, 0x3e, 0xd9, 0xe0, 0xf2, 0x1f, 0x73, 0xe7, 0x16, 0x5c, 0x98, + 0x84, 0x59, 0x59, 0x49, 0xf5, 0xa8, 0x89, 0xf7, 0xe2, 0x3f, 0xf1, 0x6e, 0x14, 0x1c, 0x76, 0x75, + 0x2a, 0x5d, 0x39, 0x3e, 0x78, 0x54, 0xe0, 0x3a, 0x8a, 0x97, 0x2c, 0xc9, 0xa3, 0x94, 0x64, 0x34, + 0x15, 0xea, 0xb7, 0x9d, 0xf0, 0xa2, 0xc0, 0x75, 0xd0, 0xc8, 0xef, 0x95, 0x3a, 0x49, 0x41, 0x67, 0xca, 0xa9, 0x83, 0xc1, 0xc3, 0x3f, 0x53, 0x3f, 0xdd, 0xf2, 0xf8, 0x9f, 0xc3, 0xf1, 0x9d, 0x30, 0x33, 0x8e, 0xe0, 0x66, 0xbb, 0x77, 0xed, 0xdd, 0xde, 0xb5, 0x7f, 0xee, 0x5d, 0xfb, 0xf3, 0xc1, 0xb5, 0x76, 0x07, 0xd7, 0xfa, 0x7e, 0x70, 0xad, 0x19, 0xa2, 0x99, 0x48, 0x65, 0x0c, 0x13, 0x56, 0xa0, 0xb7, 0x66, 0x4f, 0x8c, 0xf7, 0x98, 0xcf, 0x73, 0x54, 0x9b, 0xb5, 0x11, 0xeb, 0x8a, 0xf0, - 0xf8, 0xbe, 0x5a, 0x80, 0xd7, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x31, 0x70, 0x22, 0xdd, + 0xf8, 0xbe, 0x5a, 0x80, 0xd7, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0x4c, 0x86, 0x17, 0x69, 0xdd, 0x02, 0x00, 0x00, } @@ -763,7 +763,7 @@ func (m *ZKAuthSignature) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.MaxBlockHeight |= uint64(b&0x7F) << shift + m.MaxBlockHeight |= int64(b&0x7F) << shift if b < 0x80 { break } diff --git a/x/zkauth/types/zkauth_inputs.go b/x/zkauth/types/zkauth_inputs.go index 120cd4b861..2cb846f936 100644 --- a/x/zkauth/types/zkauth_inputs.go +++ b/x/zkauth/types/zkauth_inputs.go @@ -2,6 +2,8 @@ package types import ( "encoding/base64" + "encoding/json" + "fmt" "math" "math/big" "unicode/utf8" @@ -9,6 +11,7 @@ import ( "github.com/iden3/go-iden3-crypto/poseidon" "github.com/pkg/errors" + "github.com/Finschia/finschia-sdk/types" sdkerrors "github.com/Finschia/finschia-sdk/types/errors" ) @@ -158,11 +161,7 @@ func (zk *ZKAuthInputs) CalculateAllInputsHash(ephPkBytes, modulus []byte, maxBl if err != nil { return nil, sdkerrors.Wrap(ErrInvalidZkAuthInputs, "invalid Iss base64") } - headerBytes, err := base64.StdEncoding.DecodeString(zk.HeaderBase64) - if err != nil { - return nil, err - } - headerF, err := HashASCIIStrToField(string(headerBytes), MaxHeaderLen) + headerF, err := HashASCIIStrToField(zk.HeaderBase64, MaxHeaderLen) if err != nil { return nil, sdkerrors.Wrap(ErrInvalidZkAuthInputs, "invalid jwt header") } @@ -181,3 +180,64 @@ func (zk *ZKAuthInputs) CalculateAllInputsHash(ephPkBytes, modulus []byte, maxBl modulusF, }) } + +func ValidIss(iss string) bool { + if _, ok := SupportedOidcProviders[iss]; ok { + return true + } + + return false +} + +func ValidJWTHeader(encodedHeader string) error { + decodedBytes, err := base64.RawURLEncoding.DecodeString(encodedHeader) + if err != nil { + return fmt.Errorf("invalid base64 in header: %w", err) + } + + var header JWTHeader + if err = json.Unmarshal(decodedBytes, &header); err != nil { + return fmt.Errorf("invalid JSON in header: %w", err) + } + + if header.Alg == "" { + return fmt.Errorf("missing 'alg' field in header") + } + if header.Typ == "" { + return fmt.Errorf("missing 'typ' field in header") + } + + return nil +} + +func (zk *ZKAuthInputs) Validate() error { + // check proof points + if zk.ProofPoints == nil { + return sdkerrors.Wrap(ErrInvalidZkAuthInputs, "invalid proof points") + } + + // check iss + issBytes, err := base64.StdEncoding.DecodeString(zk.IssF) + if err != nil { + return sdkerrors.Wrapf(ErrInvalidZkAuthInputs, "invalid iss, %s", err) + } + if !ValidIss(string(issBytes)) { + return sdkerrors.Wrap(ErrInvalidZkAuthInputs, "invalid iss") + } + + // check header + if err = ValidJWTHeader(zk.HeaderBase64); err != nil { + return sdkerrors.Wrap(ErrInvalidZkAuthInputs, err.Error()) + } + + // check address_seed + if len(zk.AddressSeed) == 0 { + return sdkerrors.Wrap(ErrInvalidZkAuthInputs, "invalid address_seed") + } + + return nil +} + +func (zk *ZKAuthInputs) AccAddress() (types.AccAddress, error) { + return AccAddressFromAddressSeed(zk.AddressSeed, zk.IssF) +} diff --git a/x/zkauth/types/zkauth_inputs_test.go b/x/zkauth/types/zkauth_inputs_test.go index bb9aeca0d3..e755303173 100644 --- a/x/zkauth/types/zkauth_inputs_test.go +++ b/x/zkauth/types/zkauth_inputs_test.go @@ -103,7 +103,7 @@ func TestCalculateAllInputsHash(t *testing.T) { zkAuthInputs := ZKAuthInputs{ ProofPoints: nil, IssF: base64.StdEncoding.EncodeToString([]byte(iss)), - HeaderBase64: base64.StdEncoding.EncodeToString([]byte(jwtHeader)), + HeaderBase64: jwtHeader, AddressSeed: addressSeed, } diff --git a/x/zkauth/types/zkauth_verify.go b/x/zkauth/types/zkauth_verify.go new file mode 100644 index 0000000000..a18a57653f --- /dev/null +++ b/x/zkauth/types/zkauth_verify.go @@ -0,0 +1,72 @@ +package types + +import ( + "encoding/base64" + "encoding/json" + + snarktypes "github.com/iden3/go-rapidsnark/types" + "github.com/iden3/go-rapidsnark/verifier" + "github.com/pkg/errors" + + sdk "github.com/Finschia/finschia-sdk/types" + sdkerrors "github.com/Finschia/finschia-sdk/types/errors" +) + +type ZKAuthVerifier struct { + VerifyKey []byte +} + +func NewZKAuthVerifier(vk []byte) ZKAuthVerifier { + return ZKAuthVerifier{ + VerifyKey: vk, + } +} + +func (v *ZKAuthVerifier) Verify(proof snarktypes.ZKProof) error { + return verifier.VerifyGroth16(proof, v.VerifyKey) +} + +func VerifyZKAuthSignature(ctx sdk.Context, zkKeeper ZKAuthKeeper, ephPubKey []byte, msg *MsgExecution) error { + // check max block height + if msg.ZkAuthSignature.MaxBlockHeight < ctx.BlockHeader().Height { + return sdkerrors.Wrap(ErrInvalidZKAuthSignature, "The permitted block height was exceeded.") + } + + var proofData snarktypes.ProofData + err := json.Unmarshal(msg.ZkAuthSignature.ZkAuthInputs.ProofPoints, &proofData) + if err != nil { + return err + } + + // get OAuth publicKey + jwtHeaderBytes, err := base64.RawURLEncoding.DecodeString(msg.ZkAuthSignature.ZkAuthInputs.HeaderBase64) + if err != nil { + return err + } + var jwtHeader JWTHeader + if err = json.Unmarshal(jwtHeaderBytes, &jwtHeader); err != nil { + return err + } + jwk := zkKeeper.GetJWK(jwtHeader.Kid) + if jwk == nil { + return errors.Errorf("no jwk of kid:%s", jwtHeader.Kid) + } + modulus, err := jwk.NBytes() + if err != nil { + return err + } + + // calculate all input hash + allInputHash, err := msg.ZkAuthSignature.ZkAuthInputs.CalculateAllInputsHash(ephPubKey, modulus, msg.ZkAuthSignature.MaxBlockHeight) + if err != nil { + return err + } + + // verify + proof := snarktypes.ZKProof{ + Proof: &proofData, + PubSignals: []string{allInputHash.String()}, + } + + return zkKeeper.GetVerifier().Verify(proof) +} diff --git a/x/zkauth/types/zkauth_verify_test.go b/x/zkauth/types/zkauth_verify_test.go new file mode 100644 index 0000000000..347f30a349 --- /dev/null +++ b/x/zkauth/types/zkauth_verify_test.go @@ -0,0 +1,94 @@ +package types + +import ( + "encoding/json" + "math/big" + "os" + "testing" + + snarktypes "github.com/iden3/go-rapidsnark/types" + "github.com/stretchr/testify/require" + + sdk "github.com/Finschia/finschia-sdk/types" +) + +func TestZKAuthVerifierVerify(t *testing.T) { + const proofStr = "{\n \"pi_a\": [\n \"19522371839270073620652913303981953856602066371307169133095252437574898167087\",\n \"4833523328406165158329834156323299381989099864962000556295485103939841522677\",\n \"1\"\n ],\n \"pi_b\": [\n [\n \"13590751830803055634813187832453152641731583060610323629642183986767393594680\",\n \"14562876116960780180699310377103898768025569460014751784685583318505733065584\"\n ],\n [\n \"9109019813807833053703748676868056381436829864508782138974623759022676848785\",\n \"15372401783891458941369342924584882274874259166617081630036949233929956089219\"\n ],\n [\n \"1\",\n \"0\"\n ]\n ],\n \"pi_c\": [\n \"19650458257976957471150466891156721008030985770755836553951832770722262072792\",\n \"18859262322622715946926814109590642931253610417578764508946716428980514815460\",\n \"1\"\n ],\n \"protocol\": \"groth16\",\n \"curve\": \"bn128\"\n}" + const publicDataStr = "2897363891707776560374456764255972429123418378332336636175790055619926988108" + + verificationKey, err := os.ReadFile("../testutil/testdata/verification_key.json") + require.NoError(t, err) + + zkAuthVerifier := NewZKAuthVerifier(verificationKey) + + var proofData snarktypes.ProofData + err = json.Unmarshal([]byte(proofStr), &proofData) + require.NoError(t, err) + + proof := snarktypes.ZKProof{ + Proof: &proofData, + PubSignals: []string{publicDataStr}, + } + + err = zkAuthVerifier.Verify(proof) + require.NoError(t, err) +} + +type zkKeeperMock struct { + jwks *JWKsMap + verifier *ZKAuthVerifier +} + +var _ ZKAuthKeeper = &zkKeeperMock{} + +func (z *zkKeeperMock) GetJWK(kid string) *JWK { + return z.jwks.GetJWK(kid) +} + +func (z *zkKeeperMock) GetVerifier() *ZKAuthVerifier { + return z.verifier +} + +func TestVerifyZKAuthSignature(t *testing.T) { + verificationKey, err := os.ReadFile("../testutil/testdata/verification_key.json") + require.NoError(t, err) + + zkAuthVerifier := NewZKAuthVerifier(verificationKey) + + ephPubKey, ok := new(big.Int).SetString("18948426102457371978524559226152399917062673825697601263047735920285791872240", 10) + require.True(t, ok) + + const proofStr = "{\n \"pi_a\": [\n \"7575287679446209007446416020137456670042570578978230730578011103770415897062\",\n \"20469978368515629364541212704109752583692706286549284712208570249653184893207\",\n \"1\"\n ],\n \"pi_b\": [\n [\n \"4001119070037193619600086014535210556571209449080681376392853276923728808564\",\n \"18475391841797083641468254159150812922259839776046448499150732610021959794558\"\n ],\n [\n \"19781252109528278034156073207688818205850783935629584279449144780221040670063\",\n \"5873714313814830719712095806732872482213125567325442209795797618441438990229\"\n ],\n [\n \"1\",\n \"0\"\n ]\n ],\n \"pi_c\": [\n \"18920522434978516095250248740518039198650690968720755259416280639852277665022\",\n \"1945774583580804632084048753815901730674007769630810705050114062476636502591\",\n \"1\"\n ],\n \"protocol\": \"groth16\",\n \"curve\": \"bn128\"\n}" + zkAuthMsg := MsgExecution{ + Msgs: nil, + ZkAuthSignature: ZKAuthSignature{ + ZkAuthInputs: &ZKAuthInputs{ + ProofPoints: []byte(proofStr), + IssF: "aHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29t", + HeaderBase64: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjU1YzE4OGE4MzU0NmZjMTg4ZTUxNTc2YmE3MjgzNmUwNjAwZThiNzMiLCJ0eXAiOiJKV1QifQ", + AddressSeed: "15035161560159971633800983619931498696152633426768016966057770643262022096073", + }, + MaxBlockHeight: 32754, + }, + } + + jks := NewJWKs() + jks.AddJWK(&JWK{ + Kty: "RSA", + E: "AQAB", + N: "q0CrF3x3aYsjr0YOLMOAhEGMvyFp6o4RqyEdUrnTDYkhZbcud-fJEQafCTnjS9QHN1IjpuK6gpx5i3-Z63vRjs5EQX7lP1jG8Qg-CnBdTTLw4uJi7RmmlKPsYaO1DbNkFO2uEN62sOOzmJCh1od3CZXI1UYH5cvZ_sLJaN2A4TwvUTU3aXlXbUNJz_Hy3l0q1Jjta75NrJtJ7Pfj9tVXs8qXp15tZXrnbaM-AI0puswt35VsQbmLwUovFFGeToo5q2c_c1xYnV5uQYMadANekGPRFPM9JZpSSIvH0Lv_f15V2zRqmIgX7a3RcmTnr3-w3QNQTogdy-MogxPUdRbxow", + Alg: "RS256", + Kid: "55c188a83546fc188e51576ba72836e0600e8b73", + }) + + require.NoError(t, err) + + var zkKeeper = zkKeeperMock{ + jwks: jks, + verifier: &zkAuthVerifier, + } + + ctx := sdk.Context{} + err = VerifyZKAuthSignature(ctx, &zkKeeper, ephPubKey.Bytes(), &zkAuthMsg) + require.NoError(t, err) +}