diff --git a/proto/rarimocore/csca_root_update.proto b/proto/rarimocore/op_csca_root_update.proto similarity index 86% rename from proto/rarimocore/csca_root_update.proto rename to proto/rarimocore/op_csca_root_update.proto index 6d8a44e2..923e6a31 100644 --- a/proto/rarimocore/csca_root_update.proto +++ b/proto/rarimocore/op_csca_root_update.proto @@ -4,7 +4,7 @@ package rarimo.rarimocore.rarimocore; option go_package = "github.com/rarimo/rarimo-core/x/rarimocore/types"; message CSCARootUpdate { - // Root is new root. Hex 0x uint256 + // Root is new Merkle root. Hex 0x uint256 string root = 1; // Timestamp is the moment of accepting proposal of CSCA list edit/replacement uint64 timestamp = 2; diff --git a/x/README.md b/x/README.md index 69e383a4..789da34d 100644 --- a/x/README.md +++ b/x/README.md @@ -12,6 +12,7 @@ title: List of Modules * [x/oraclemanager](./oraclemanager/README.md) - Managing oracles that submit events to Rarimo chain. * [x/evm and x/feemarket](./evm/README.md) - EVM compatibility feature. * [x/identity](./identity/README.md) - Storing aggregated identity data. +* [x/cscalist](./cscalist/README.md) - Storing and managing CSCA Master List ## EVM diff --git a/x/cscalist/README.md b/x/cscalist/README.md index 4c304618..4ebecaa5 100644 --- a/x/cscalist/README.md +++ b/x/cscalist/README.md @@ -4,3 +4,150 @@ title: x/cscalist --- # `x/cscalist` + +## Abstract + +The module is designed to store and manage [CSCA Master List](https://pkddownloadsg.icao.int/). +The list itself is provided by [ICAO](https://www.icao.int/Pages/default.aspx) and contains root certificates of passport issuing authorities for a lot of countries. +When obtained the passport signer's root certificate, you can check its presence in the Merkle tree, +stored on Rarimo chain in a decentralized manner. +Changing the list is possible via proposal. + +---- + +## Concepts + +The CSCA certificates can only be obtained from the ICAO website in form of an LDIF file. +The file is parsed by our [LDIF SDK](https://github.com/rarimo/ldif-sdk) onto the hash values of raw public keys (Poseidon hash with special splitting scheme for 4096-bit RSA keys). +The obtained hashes are used as leaves for the Merkle tree. + +Dynamic treap-based Merkle tree is used to store the CSCA public key hashes, see [treap](https://en.wikipedia.org/wiki/Treap). +The reason is good consistency of treap with Cosmos KVStore, while updates of the list may be quite frequent. +For each node the priority is deterministically derived from the key with formula: `priority = poseidon_hash(key) mod MAX_UINT64`, where `key` is Poseidon hash of public key and `MAX_UINT64 = 2^64-1`. + +In order to update the tree the proposal can be created. There are two types: +- `ReplaceCSCAListProposal` - replaces the whole tree with a new one, very gas-consuming, suits for initial setup. +- `EditCSCAListProposal` - add or remove certain CSCA public key hashes, flexible and efficient. + +On update an operation `CSCARootUpdate` (see `rarimocore` module) is emitted in order to update the root key in external systems, e.g. EVM registration contract. + +CLI is provided to work with LDIF (extract public keys, hash them or build a tree with root), query tree and module params, compare the current tree with the locally built from CSCA list. + +### Architecture + +Implementation of the Merkle tree is copied from LDIF SDK and optimized for using with KVStore. Base methods are defined in `x/cscalist/keeper/treap.go`: +```go +func (t Treap) Insert(ctx sdk.Context, key string) {} + +func (t Treap) Remove(ctx sdk.Context, key string) {} + +func (t Treap) split(ctx sdk.Context, root, key string) (string, string) {} + +func (t Treap) merge(ctx sdk.Context, left, rirght string) string {} +``` + +The `Node` structure is used to store the tree nodes. The public key hash received from proposal is processed in the following way: +1. The hash must be a hex string with 0x prefix +2. Hex string is set in new `Node` along with its `Hash` and `ChildrenHash` values. +3. On node insertion, deeply in KVStore implementation, string is converted directly to bytes, like `[]byte(key)`. +4. Some operations on treap are performed to build a new tree + +Processing in 3 results in redundant gas consumption, because we could store bytes directly that are 2 times smaller than bytes of hex strings themselves. +Also, algorithms of insertion and removal are not currently optimized for multiple values at once, resulting in a huge gas consumption. + +## State examples + +Models can be found in `proto/cscalist`. Example values are provided in JSON for convenient reading. + +Module params +```json +{ + "params": { + "rootKey": "0x36141b81b879c28068b3df0bbe9fad19c202b3ef7a140046e018c4153a8ce4c1", + "rootUpdated": false, + "updatedAtBlock": 854221, + "lastUpdateOperationIndex": "0x2fd7af49f584db04cc8048fd09be7fccf01bd7efe6c93127c0dbae55e643d625" + } +} +``` + +Node of the tree +```json +{ + "node": { + "key": "0x28815c9a1c9d638886d6ac193df55f98824c491d09bbbd712f96b5adfeba742e", + "priority": 1833377742, + "left": "0x", + "right": "0x022e071cf4eed456dc7f3a36b7b190c6a1f991ef8c5809f612cb193e9c28af78", + "hash": "0x2fd7af49f584db04cc8048fd09be7fccf01bd7efe6c93127c0dbae55e643d625", + "childrenHash": "0x022e071cf4eed456dc7f3a36b7b190c6a1f991ef8c5809f612cb193e9c28af78" + } +} +``` + +Replace CSCA list proposal +```json +{ + "@type": "/rarimo.rarimocore.cscalist.ReplaceCSCAListProposal", + "title": "Init", + "description": "Lorem ipsum dolor sit amet concestetur!", + "leaves": [ + "0x28815c9a1c9d638886d6ac193df55f98824c491d09bbbd712f96b5adfeba742e" + ] +} +``` + +Edit CSCA list proposal +```json +{ + "@type": "/rarimo.rarimocore.cscalist.EditCSCAListProposal", + "title": "Add", + "description": "Lorem ipsum dolor sit amet concestetur!", + "toAdd": [ + "0x28815c9a1c9d638886d6ac193df55f98824c491d09bbbd712f96b5adfeba742e" + ], + "toRemove": [] +} +``` + +## Create proposal + +You can create a draft proposal with `rarimo-cored tx gov draft-proposal`. +Select `other`, then choose type `cosmos.gov.v1.MsgExecLegacyContent`. +In `messages[0].content` section, fill `@type` (see proposals above) with module prefix `rarimo.rarimocore.cscalist`. +Depending on the proposal type, fill either `leaves` or `toAdd/toRemove` fields with the list of CSCA public key hashes. + +Or you can directly edit this sample (replace `@type` and `leaves` for another type): +```json +{ + "messages": [ + { + "@type": "/cosmos.gov.v1.MsgExecLegacyContent", + "content": { + "@type": "/rarimo.rarimocore.cscalist.ReplaceCSCAListProposal", + "title": "Init", + "description": "Lorem ipsum dolor sit amet concestetur!", + "leaves": [ + "0x28815c9a1c9d638886d6ac193df55f98824c491d09bbbd712f96b5adfeba742e" + ] + }, + "authority": "rarimo10d07y265gmmuvt4z0w9aw880jnsr700jw44g3e" + } + ], + "metadata": "ipfs://CID", + "deposit": "10000000urmo" +} +``` + +When you have saved your proposal (assuming `draft_proposal.json`), you may submit it, make a deposit for it and vote, then retrieve your proposal. +Here is the script sample for testing: +```bash +# approximate gas limit required to build full list of 382 nodes +rarimo-cored --keyring-backend test --home config/validator tx gov submit-proposal --gas 9999999999 --yes --from "
" "draft_proposal.json" +sleep 5 +rarimo-cored --keyring-backend test --home config/validator tx gov deposit --gas 9999999 --yes --from "
" "" 10000000stake +sleep 5 +rarimo-cored --keyring-backend test --home config/validator tx gov vote --yes --gas 9999999 --from "
" "" yes +sleep 5 +rarimo-cored query gov proposal "" +``` diff --git a/x/cscalist/keeper/abci.go b/x/cscalist/keeper/abci.go index 9f89368d..0815ef9e 100644 --- a/x/cscalist/keeper/abci.go +++ b/x/cscalist/keeper/abci.go @@ -12,9 +12,15 @@ func (k Keeper) EndBlocker(ctx sdk.Context) { return } + root, ok := k.GetNode(ctx, params.RootKey) + if !ok { + k.Logger(ctx).Error("Root node not found: ", params.RootKey) + return + } + // Creating operation to be signed by TSS parties index, err := k.rarimo.CreateCSCARootUpdateOperation(ctx, types.ModuleName, &rarimo.CSCARootUpdate{ - Root: params.RootKey, + Root: root.Hash, Timestamp: uint64(ctx.BlockTime().Unix()), }) if err != nil { diff --git a/x/rarimocore/README.md b/x/rarimocore/README.md index 97e006bf..0e0971c0 100644 --- a/x/rarimocore/README.md +++ b/x/rarimocore/README.md @@ -39,6 +39,7 @@ In such case operation can be already approved. - Identity GIST transfer: `HASH(source contract, id, GIST hash, GIST timestamps, replaced GIST hash)` - Identity aggregated transfer: `HASH(GIST, timestamp, states root, contract address, chain)` - WorldCoin identity transfer: `HASH(source contract, old state, new state, timestamp)` +- CSCA root update: `HASH(hashPrefix, root, timestamp)`, where `hashPrefix="Rarimo CSCA root"` To add new operation check the following [manual](../../docs/common/core/001-adding-operation.md). @@ -392,6 +393,19 @@ Also, use - `CreateRemoveFeeTokenOperation(ctx sdk.Context, token tokentypes.FeeToken, chain string, nonce string) error` - `CreateAddFeeTokenOperation(ctx sdk.Context, token tokentypes.FeeToken, chain string, nonce string) error` +### CreateCSCARootUpdateOperation + +**CreateCSCARootUpdateOperation** - used by `cscalist` module to create CSCA root update operation after proposal pass + +Definition: +`CreateCSCARootUpdateOperation(ctx sdk.Context, creator string, update *rarimo.CSCARootUpdate) (string, error)` + +Flow: +- Create proposal to replace/edit CSCA list (see [x/cscalist/README.md](../cscalist/README.md)) +- When proposal is accepted, root key is updated +- On the end block operation with **Merkle root** is created +- Operation is auto-approved + ---- ## Transactions diff --git a/x/rarimocore/types/csca_root_update.pb.go b/x/rarimocore/types/op_csca_root_update.pb.go similarity index 69% rename from x/rarimocore/types/csca_root_update.pb.go rename to x/rarimocore/types/op_csca_root_update.pb.go index 500ede5b..dab904cd 100644 --- a/x/rarimocore/types/csca_root_update.pb.go +++ b/x/rarimocore/types/op_csca_root_update.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: rarimocore/csca_root_update.proto +// source: rarimocore/op_csca_root_update.proto package types @@ -23,7 +23,7 @@ var _ = math.Inf const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type CSCARootUpdate struct { - // Root is new root. Hex 0x uint256 + // Root is new Merkle root. Hex 0x uint256 Root string `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty"` // Timestamp is the moment of accepting proposal of CSCA list edit/replacement Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` @@ -33,7 +33,7 @@ func (m *CSCARootUpdate) Reset() { *m = CSCARootUpdate{} } func (m *CSCARootUpdate) String() string { return proto.CompactTextString(m) } func (*CSCARootUpdate) ProtoMessage() {} func (*CSCARootUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_a5f5865c9187473d, []int{0} + return fileDescriptor_11cb43c2e78ab453, []int{0} } func (m *CSCARootUpdate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -80,22 +80,24 @@ func init() { proto.RegisterType((*CSCARootUpdate)(nil), "rarimo.rarimocore.rarimocore.CSCARootUpdate") } -func init() { proto.RegisterFile("rarimocore/csca_root_update.proto", fileDescriptor_a5f5865c9187473d) } +func init() { + proto.RegisterFile("rarimocore/op_csca_root_update.proto", fileDescriptor_11cb43c2e78ab453) +} -var fileDescriptor_a5f5865c9187473d = []byte{ - // 182 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2c, 0x4a, 0x2c, 0xca, - 0xcc, 0xcd, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0x2e, 0x4e, 0x4e, 0x8c, 0x2f, 0xca, 0xcf, 0x2f, - 0x89, 0x2f, 0x2d, 0x48, 0x49, 0x2c, 0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x81, - 0x28, 0xd1, 0x43, 0xa8, 0x44, 0x62, 0x2a, 0x39, 0x71, 0xf1, 0x39, 0x07, 0x3b, 0x3b, 0x06, 0xe5, - 0xe7, 0x97, 0x84, 0x82, 0x75, 0x09, 0x09, 0x71, 0xb1, 0x80, 0x0c, 0x91, 0x60, 0x54, 0x60, 0xd4, - 0xe0, 0x0c, 0x02, 0xb3, 0x85, 0x64, 0xb8, 0x38, 0x4b, 0x32, 0x73, 0x53, 0x8b, 0x4b, 0x12, 0x73, - 0x0b, 0x24, 0x98, 0x14, 0x18, 0x35, 0x58, 0x82, 0x10, 0x02, 0x4e, 0x5e, 0x27, 0x1e, 0xc9, 0x31, - 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, - 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x90, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, - 0x9f, 0xab, 0x0f, 0xb1, 0x14, 0x4a, 0xe9, 0x82, 0x5d, 0x5c, 0xa1, 0x8f, 0xe4, 0xfc, 0x92, 0xca, - 0x82, 0xd4, 0xe2, 0x24, 0x36, 0xb0, 0xa3, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x44, 0xe5, - 0x75, 0x0e, 0xd9, 0x00, 0x00, 0x00, +var fileDescriptor_11cb43c2e78ab453 = []byte{ + // 185 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x29, 0x4a, 0x2c, 0xca, + 0xcc, 0xcd, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0xcf, 0x2f, 0x88, 0x4f, 0x2e, 0x4e, 0x4e, 0x8c, 0x2f, + 0xca, 0xcf, 0x2f, 0x89, 0x2f, 0x2d, 0x48, 0x49, 0x2c, 0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, + 0x17, 0x92, 0x81, 0xa8, 0xd2, 0x43, 0x28, 0x46, 0x62, 0x2a, 0x39, 0x71, 0xf1, 0x39, 0x07, 0x3b, + 0x3b, 0x06, 0xe5, 0xe7, 0x97, 0x84, 0x82, 0x75, 0x09, 0x09, 0x71, 0xb1, 0x80, 0x0c, 0x91, 0x60, + 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x85, 0x64, 0xb8, 0x38, 0x4b, 0x32, 0x73, 0x53, 0x8b, + 0x4b, 0x12, 0x73, 0x0b, 0x24, 0x98, 0x14, 0x18, 0x35, 0x58, 0x82, 0x10, 0x02, 0x4e, 0x5e, 0x27, + 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, + 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x90, 0x9e, 0x59, 0x92, 0x51, 0x9a, + 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x0f, 0xb1, 0x14, 0x4a, 0xe9, 0x82, 0x1d, 0x5d, 0xa1, 0x8f, 0xe4, + 0x83, 0x92, 0xca, 0x82, 0xd4, 0xe2, 0x24, 0x36, 0xb0, 0xa3, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, + 0xff, 0x63, 0xeb, 0xd9, 0x20, 0xdc, 0x00, 0x00, 0x00, } func (m *CSCARootUpdate) Marshal() (dAtA []byte, err error) { @@ -119,22 +121,22 @@ func (m *CSCARootUpdate) MarshalToSizedBuffer(dAtA []byte) (int, error) { var l int _ = l if m.Timestamp != 0 { - i = encodeVarintCscaRootUpdate(dAtA, i, uint64(m.Timestamp)) + i = encodeVarintOpCscaRootUpdate(dAtA, i, uint64(m.Timestamp)) i-- dAtA[i] = 0x10 } if len(m.Root) > 0 { i -= len(m.Root) copy(dAtA[i:], m.Root) - i = encodeVarintCscaRootUpdate(dAtA, i, uint64(len(m.Root))) + i = encodeVarintOpCscaRootUpdate(dAtA, i, uint64(len(m.Root))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func encodeVarintCscaRootUpdate(dAtA []byte, offset int, v uint64) int { - offset -= sovCscaRootUpdate(v) +func encodeVarintOpCscaRootUpdate(dAtA []byte, offset int, v uint64) int { + offset -= sovOpCscaRootUpdate(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) @@ -152,19 +154,19 @@ func (m *CSCARootUpdate) Size() (n int) { _ = l l = len(m.Root) if l > 0 { - n += 1 + l + sovCscaRootUpdate(uint64(l)) + n += 1 + l + sovOpCscaRootUpdate(uint64(l)) } if m.Timestamp != 0 { - n += 1 + sovCscaRootUpdate(uint64(m.Timestamp)) + n += 1 + sovOpCscaRootUpdate(uint64(m.Timestamp)) } return n } -func sovCscaRootUpdate(x uint64) (n int) { +func sovOpCscaRootUpdate(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } -func sozCscaRootUpdate(x uint64) (n int) { - return sovCscaRootUpdate(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +func sozOpCscaRootUpdate(x uint64) (n int) { + return sovOpCscaRootUpdate(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (m *CSCARootUpdate) Unmarshal(dAtA []byte) error { l := len(dAtA) @@ -174,7 +176,7 @@ func (m *CSCARootUpdate) Unmarshal(dAtA []byte) error { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { - return ErrIntOverflowCscaRootUpdate + return ErrIntOverflowOpCscaRootUpdate } if iNdEx >= l { return io.ErrUnexpectedEOF @@ -202,7 +204,7 @@ func (m *CSCARootUpdate) Unmarshal(dAtA []byte) error { var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { - return ErrIntOverflowCscaRootUpdate + return ErrIntOverflowOpCscaRootUpdate } if iNdEx >= l { return io.ErrUnexpectedEOF @@ -216,11 +218,11 @@ func (m *CSCARootUpdate) Unmarshal(dAtA []byte) error { } intStringLen := int(stringLen) if intStringLen < 0 { - return ErrInvalidLengthCscaRootUpdate + return ErrInvalidLengthOpCscaRootUpdate } postIndex := iNdEx + intStringLen if postIndex < 0 { - return ErrInvalidLengthCscaRootUpdate + return ErrInvalidLengthOpCscaRootUpdate } if postIndex > l { return io.ErrUnexpectedEOF @@ -234,7 +236,7 @@ func (m *CSCARootUpdate) Unmarshal(dAtA []byte) error { m.Timestamp = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { - return ErrIntOverflowCscaRootUpdate + return ErrIntOverflowOpCscaRootUpdate } if iNdEx >= l { return io.ErrUnexpectedEOF @@ -248,12 +250,12 @@ func (m *CSCARootUpdate) Unmarshal(dAtA []byte) error { } default: iNdEx = preIndex - skippy, err := skipCscaRootUpdate(dAtA[iNdEx:]) + skippy, err := skipOpCscaRootUpdate(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthCscaRootUpdate + return ErrInvalidLengthOpCscaRootUpdate } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF @@ -267,7 +269,7 @@ func (m *CSCARootUpdate) Unmarshal(dAtA []byte) error { } return nil } -func skipCscaRootUpdate(dAtA []byte) (n int, err error) { +func skipOpCscaRootUpdate(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 @@ -275,7 +277,7 @@ func skipCscaRootUpdate(dAtA []byte) (n int, err error) { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { - return 0, ErrIntOverflowCscaRootUpdate + return 0, ErrIntOverflowOpCscaRootUpdate } if iNdEx >= l { return 0, io.ErrUnexpectedEOF @@ -292,7 +294,7 @@ func skipCscaRootUpdate(dAtA []byte) (n int, err error) { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { - return 0, ErrIntOverflowCscaRootUpdate + return 0, ErrIntOverflowOpCscaRootUpdate } if iNdEx >= l { return 0, io.ErrUnexpectedEOF @@ -308,7 +310,7 @@ func skipCscaRootUpdate(dAtA []byte) (n int, err error) { var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { - return 0, ErrIntOverflowCscaRootUpdate + return 0, ErrIntOverflowOpCscaRootUpdate } if iNdEx >= l { return 0, io.ErrUnexpectedEOF @@ -321,14 +323,14 @@ func skipCscaRootUpdate(dAtA []byte) (n int, err error) { } } if length < 0 { - return 0, ErrInvalidLengthCscaRootUpdate + return 0, ErrInvalidLengthOpCscaRootUpdate } iNdEx += length case 3: depth++ case 4: if depth == 0 { - return 0, ErrUnexpectedEndOfGroupCscaRootUpdate + return 0, ErrUnexpectedEndOfGroupOpCscaRootUpdate } depth-- case 5: @@ -337,7 +339,7 @@ func skipCscaRootUpdate(dAtA []byte) (n int, err error) { return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { - return 0, ErrInvalidLengthCscaRootUpdate + return 0, ErrInvalidLengthOpCscaRootUpdate } if depth == 0 { return iNdEx, nil @@ -347,7 +349,7 @@ func skipCscaRootUpdate(dAtA []byte) (n int, err error) { } var ( - ErrInvalidLengthCscaRootUpdate = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowCscaRootUpdate = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupCscaRootUpdate = fmt.Errorf("proto: unexpected end of group") + ErrInvalidLengthOpCscaRootUpdate = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowOpCscaRootUpdate = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupOpCscaRootUpdate = fmt.Errorf("proto: unexpected end of group") )