arrow-left

All pages
gitbookPowered by GitBook
1 of 4

Loading...

Loading...

Loading...

Loading...

Typescript SDK

CCL IBC SDK for typescript developers

hashtag
Typescript Demo

circle-info

See a fullstack Next.js typescript demo herearrow-up-right (using Osmosis Mainnet). Code available herearrow-up-right.

hashtag
Dependencies

For encryption, we recommend using @solar-republic/neutrino which has useful chacha20poly1305related functionalities and additional primitives for generating ephemereal lightweight wallets. Installation:

For signing, encoding and other cryptographic needs in the Cosmos ecosystem, it is common to use the suite of @cosmjs packages. You can install the following:

If you are developing in the browser environment or connecting to a public network you might also need

circle-info

Note: You can also use any Typescript / Javascript package managers and runtimes e,g, bun, yarn, pnpm etc.

hashtag
Generating Wallets

For chacha20poly1305, we need to use a crypthographic keypair and it's advised to use one that isn't the same as the user's wallet. The SDK provides a method for generating a new wallet that can be used for encryption purposes. For our purposes, we just need a private / public keys of Secp256k1 type and there are various ways to generate them.

@cosmjs/crypto

@solar-republic/neutrino

@secretjs

hashtag
Query Client

Before proceeding to encryption, you might want to create a quering client that will be used for querying the state and contract of the Secret Network. At the very least, it is requited for fetching the public key of a gateway contract for deriving a shared key used later for encryption.

To perform a simple query on a secret contract we can use methods from @solar-republic/neutrino:

For more persistent use-cases you can use secretjs:

hashtag
Signatures

To make sure that malicious applications aren't tricking the user into signing an actual blockchain transaction, it is discouraged to sign arbitrary blobs of data. To address the situation, there are various standards that inject additional data to the message before signing it. The most used in the Cosmos ecosystem is defined in which is also used in the SDK.

hashtag
Browser Wallets

Most of the Cosmos wallets provide a method for signing arbitrary messages following the mentioned specification.

Here is a definition taken from documentation of :

Although the API method requires a chainId, it is set to empty string before signing the message

Cosmology

Cosmology signArbitrary method as part of the interface for their wallet client and provides implementation / integration for every popular Cosmos wallet out there

CosmJS

The logic of the method has already been implemented and proposed as an addition to the library however it has been hanging in a unmerged PR for a while. You can find the full implementation with examples and tests

Manually

hashtag
Getting Signer and Signer Address

Firstly we need to get an amino signer that will be used for generating the signature. @cosmjs has a defined interface OfflineAminoSigner with signAmino and getAccounts methods and any other signer that implements it can be used for the purpose.

hashtag
Generating the message and StdSignDoc

To use signAmino, we need to generate a StdSignDoc object that will be used for signing the message to pass as an argument.

CosmJS provides a function for this:

The 036 standard requires the message fields to AminoMsg to be:

As for the rest of the fields, they can be set to an empty string or 0. The final example will look like this:

After getting the document we can sign it with the signer:

hashtag
Encryption

After getting a public key of a gateway contract you can use it to derive a shared key like this:

hashtag
Encrypting + Signing

Produced digest of hashing the ciphertext can be used as our message that we want to sign according to the 036 standard. The final message will look like this:

After this we are getting all the required fields for creating an EncryptedPayload message or an ExecuteMsg::Encrypted { ... }

hashtag
Broadcasting the message

The encrypted message is safe to broadcast over public blockchain and other infrastructure. A common use-case in context of Cosmos account might be broadcasting it over IBC originating from a chain other than the Secret Network.

The potential use-case might involve broadcasting the message by initiating an IBC message directly and attaching the message as a payload (IBC-Hook) or passing the message to a smart contract on a remote chain to process and bridge it to the Secret Network.

Since Cosmwasm is quite flexible with defining messages due to supporting JSON serialization, it is possible the process is very similar in both cases so we only going to cover IBC-hooks for simplicity:

ADR 036arrow-up-right
Keplr walletarrow-up-right
definesarrow-up-right
[PR] Herearrow-up-right
npm install --save @solar-republic/neutrino
npm install --save @cosmjs/crypto @cosmjs/amino @cosmjs/encoding
npm install --save @cosmjs/stargate
# or
npm install --save @cosmjs/cosmwasm-stargate
import { Slip10Curve, Random, Bip39, Slip10, stringToPath, Secp256k1 } from "@cosmjs/crypto"

const seed = await Bip39.mnemonicToSeed(Bip39.encode(Random.getBytes(16)));
const { privateKey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, stringToPath("m/44'/1'/0'/0"));
const pair = await Secp256k1.makeKeypair(privateKey);
// must be compressed to 33 bytes from 65
const publicKey = Secp256k1.compressPubkey(pair.pubkey);
import { gen_sk, sk_to_pk } from "@solar-republic/neutrino"

const privateKey = gen_sk(); 
const publicKey = sk_to_pk(privateKey);
import { Wallet } from "secretjs";
const { privateKey, publicKey } = new Wallet()
import { SecretContract } from "@solar-republic/neutrino"

// create a contract instantse
const contract = await SecretContract(
    secretNodeEndpoint,
    secretContractAddress
)

// query example:
// get encryption key from a gateway contract
const queryRes = await contract.query({ encryption_key: {} })


// extract res value
const gatewayPublicKey = queryRes[2]
import { SecretNetworkClient } from "secretjs"

// create a client:
const client = new SecretNetworkClient({
    chainId, 
    url // endpoint URL of the node
});

// query the contact and get the value directly
const gatewayPublicKey =  await client.query.compute.queryContract({
    contract_address
    code_hash, // optionally
    { encryption_key: {} } // query msg
});
// window.keplr.signArbi....
signArbitrary(chainId: string, signer: string, data: string | Uint8Array) : Promise<StdSignature>
// In browser environment we can get it from a wallet extension. E.g with Keplr:
const signer = window.keplr.getOfflineSigner(chainId);


//  ...


// In Node environment we can use `Secp256k1Wallet` class that also implements `OfflineAminoSigner` interface
import { Secp256k1Wallet } from "@cosmjs/amino"


// see examples of generating a random above but in this case you will probably be using a persistent one from a .env file 
// you can also pass extra options like prefix as the second argument
const signer = await Secp256k1HdWallet.fromMnemonic(userMnemonic) 


//  ...

// Here we are getting the first accounts from the signer
// accessing the address and renaming it to `signerAddress` 
const [{ address : signerAddress }] =  await signer.getAccounts();

//  ...
function makeSignDoc(msgs: AminoMsg[], fee: StdFee, chainId: string, memo: string | undefined, accountNumber: number | string, sequence: number | string, timeout_height?: bigint): StdSignDoc;
type AminoMsg = {
    // static type url
    type: ;
    value: {
        // signer address
        signer: string;
        // plaintext or base64 encoded message
        data: string;
    }
}
const data = "my message";


const signDoc  = makeSignDoc(
    [{                              // list of amino messages
        type: "sign/MsgSignData", 
        value:  { 
            signer: signerAddress, 
            data:   data 
        }
    }], 
    { gas: "0", amount: [] },      // StdFee
    "",                            // chainId 
    "",                            // memo
    0,                             // accountNumber
    0                              // sequence
    // timeout_height
)
const signRes = await signer.signAmino(signerAddress, signDoc);
import { sha256, Random } from "@cosmjs/crypto"
import { fromBase64, toBase64, toAscii } from "@cosmjs/encoding";
import { chacha20_poly1305_seal, ecdh } from "@solar-republic/neutrino"
// this is a dependency of `@solar-republic/neutrino` so consider importing these methos 
import { concat, json_to_bytes } from "@blake.regalia/belt";


// ...
//   define
//  
//      `clientPrivateKey`
//      `gatewayPublicKey`
//      `signerAddress`
//
//   like described above
// ...


const sharedKey = sha256(ecdh(clientPrivateKey, gatewayPublicKey))

// We also need to generate a one-time nonce, which can be done like this:
const nonce =  Random.getBytes(12)

// Prepare a message for the action you want to take on the contract
const msg = ExecuteMsg { ...}


/// Defining payload structure idential to the Rust data structure
const payload : EncryptedPayload = {
    // e.g, cosmos1...
    user_address: signerAddress,
    // uint8array -> base64  (-> Binary)
    user_pubkey: toBase64(signerPubkey),
    // e.g. "cosmos" from "cosmos1..."
    hrp: signerAddress.split("1")[0], 
    // or to toBinary(msg) from `@cosmjs/cosmwasm-stargate`
    msg: toBase64(json_to_bytes(msg))
}


/// getting the payload ciphertext 
const ciphertext = concat(chacha20_poly1305_seal(
    sharedKey,
    nonce,
    // or toUtf8( JSON.stringify(payload) )  
    json_to_bytes( payload )
));

// finally the payload_hash is sha256 of the ciphertext
const ciphertextHash = sha256(ciphertext);


//    ...
// calling `makeSignDoc` with nullifed fields like described earlier
const signDoc = getArb36SignDoc(signerAddress, ciphertextHash);
// signing the message
const signRes = await signer.signAmino(signerAddress, signDoc);
const encrypted = {
    // uint8array -> base64  (-> Binary)
    nonce              :     toBase64(nonce),
    // public key of a pair that was used in deriving a shread key 
    user_key           :     toBase64(clientPublicKey),
    // ciphertext of with the user data and actual message
    payload            :     toBase64(ciphertext),
    // sha256 hash of the ciphertext
    payload_hash       :     toBase64(ciphertextHash),
    // signatire over sha256( getArb36SignDoc( payload_hash ) ) already in base64
    payload_signature  :     signRes.signature.signature,
}
import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx";
import { SigningStargateClient, MsgTransferEncodeObject } from "@cosmjs/stargate";

/// creating a client
const client = await SigningStargateClient(
    "https://rpc.cosmoshub.io"    //  endpoint of the remote network 
    signer,                       // offlineSigner  
)


// defining the IBC transfer message
const msg : MsgTransferEncodeObject = {
    typeUrl: "/ibc.applications.transfer.v1.MsgTransfer",
    value: MsgTransfer.fromPartial({
      sender:   signerAddress,
      receiver: secretGatewayContractAddress,
      sourceChannel: "channel-0",
      sourcePort: "transfer",
      timeoutTimestamp:  BigInt(
        // 5 minutes from now           |  ms -> ns
        Math.floor(Date.now() + 300_000) * 1_000_000
      ),
      // IBC Hook memo msg
      memo: JSON.stringify({
        wasm: {
            // must be same as receiver
            contract: secretGatewayContractAddress,
            // encrypted message defined above
            msg: encrypted
        }
      })
    })
}

// signing and broadcasting the message
const res = await client.signAndBroadcast(signerAddress, [msg])

Cross-chain Messaging with IBC Hooks

hashtag
IBC Hooks for Cross-Chain Messaging

IBC-Hooks is an IBC middleware that uses incoming ICS-20 token transfers to initiate smart contract calls. Secret Network created a Cosmos Developer SDK that uses IBC hooks to execute Secret gateway contracts, which allows Cosmos developers on other chains to use Secret as a privacy layer for your chain.

The SDK abstracts the complexities involved in interacting with the Secret Network for applications that deal with Cosmos wallets. It introduces a secure method for generating confidential messages and reliably authenticating users at the same time using the chacha20poly1305 algorithm.

The SDK can be used be for developing major gateways that forward incoming messages to Secret Network, as well as built-in support for confidential messages directly in other contracts. To learn by doing, start with the key value store developer tutorial .

hashtag
SDK Requirements

  • IBC Transfer channel between the consumer chain and Secret Network

circle-info

See for a list of existing transfer channels between Cosmos chains and Secret Network

herearrow-up-right
Mintscanarrow-up-right

IBC-Hooks

Initiate a contract call with an incoming IBC token transfer using IBC hooks

hashtag
Overview

IBC-Hooks is an IBC middleware that uses incoming ICS-20 token transfers to initiate smart contract calls. This allows for arbitrary data to be passed in along with token transfers. This is useful for a variety of use cases such as cross-chain token swaps, auto-wrapping of SNIP-20 tokens, General Message Passing (GMP) between Secret Network and EVM chains, and much more! The mechanism enabling this is a memo field on every ICS20 transfer packet as of IBC v3.4.0. Wasm hooks is an IBC middleware that parses an ICS20 transfer, and if the memo field is of a particular form, it executes a Wasm contract call.

circle-info

Note that the metadata in the memo field is not used within ICS-20 itself, but instead, a middleware or custom CosmWasm contract can wrap around the transfer protocol to parse the metadata and execute custom logic based off of it.

hashtag
ICS20 Packet Structure

ICS20 is JSON native, so JSON is used for the memo format:

An ICS20 packet is formatted correctly for Wasm hooks if the following all hold:

  • memo is not blank

  • memo is valid JSON

  • memo

circle-info

If an ICS20 packet is not directed towards wasmhooks, wasmhooks doesn't do anything. If an ICS20 packet is directed towards wasmhooks, and is formatted incorrectly, then wasmhooks returns an error.

hashtag
ICS20 Packet Execution Flow

Before Wasm hooks:

  • Ensure the incoming IBC packet is cryptographically valid

  • Ensure the incoming IBC packet has not timed out

In Wasm hooks, before packet execution:

  • Ensure the packet is correctly formatted (as defined above)

  • Edit the receiver to be the hardcoded IBC module account

In Wasm hooks, after packet execution:

  • Construct wasm message as defined above

  • Execute wasm message

  • If wasm message has error, return ErrAck

hashtag
Auto-wrapping of SNIP-20 Example

receives funds from IBC, wraps them as SNIP-20 tokens, and then transfers them to the recipient that is specified in the ibc-hooks message:

hashtag
Ack callbacks

A contract that sends an IBC transfer may need to listen for the acknowledgment (ack) of that packet. To allow contracts to listen to ack of specific packets, we provide Ack callbacks. The sender of an IBC transfer packet may specify a callback in the memo field of the transfer packet when the ack of that packet is received.

circle-info

Only the IBC packet sender can set the callback

hashtag
Ack callback implementation

For the callback to be processed, the transfer packet's memo should contain the following in its JSON:

{"ibc_callback": "secret1contractAddr"}

The WASM hooks will keep the mapping from the packet's channel and sequence to the contract in storage. When an ack is received, it will notify the specified contract via an execute message.

Interface for receiving the Acks and Timeouts

should implement the following interface for a sudo message:

Functions, Methods, and Data Structures

hashtag
CCL SDK

circle-info

The Secret Network CCL SDK can be forked .

has at least one key, with value
"wasm"
  • memo["wasm"] has exactly two entries, "contract" and "msg"

  • memo["wasm"]["msg"] is a valid JSON object

  • receiver == memo["wasm"]["contract"]

  • Otherwise continue through middleware

    See more here.arrow-up-right
    The following contractarrow-up-right
    The contract that awaits the callbackarrow-up-right
    hashtag
    Data Structures

    The essential parameters required for chacha20poly1305flow are defined in the following data structure:

    EncryptedParams

    A data structure that is visible to all network participants and can be transmitted over non-secure channels

    hashtag
    EncryptedPayload

    Data meant to be encrypted and stored in the payload field of EncryptedParams

    hashtag
    Custom Contract Message

    Your contract must define an endpoint where a user can pass all the required fields of the EncryptedParams. E.g:

    If you want to define a custom message, rename the fields, or add additional fields, there is a helpful trait WithEncryption that you can implement. It simply tells the compiler how to extract the essential parameters from your custom message and turn it into EncryptedParams

    Implementing the trait for your message will allow you to use other useful methods of the SDK (like handle_encrypted_wrapper) that significantly simplify the development experience.

    Example of the implementation for the ExecuteMsg is as follows:

    hashtag
    Extending existing data structures

    The SDK has multiple data structures that already implement WithEncryption trait and also use the template engine of Rust to make them easily extendable. Take for example the following message:

    You can define a new message that extends the GatewayExecuteMsg by simply providing a new type for the Extension instead of the default Option<Empty> like this:

    Your extended type in this case is available under MyGatewayExecuteMsg::Extension variant and you can use it in your contract like this:

    hashtag
    Functions and methods

    handle_encrypted_wrapper

    The encryption logic, handle_encrypted_wrapper, is where the encryption magic happens ⭐

    You can review the function in the SDK herearrow-up-right. It has the following functionality:

    1. Check if Message is Encrypted:

      • If the message is encrypted (msg.is_encrypted()), it proceeds with decryption.

    2. Extract Encryption Parameters:

      • Retrieves the encryption parameters from the message (msg.encrypted()).

    3. Check Nonce:

      • Ensures the nonce has not been used before to prevent replay attacks.

    4. Load Encryption Wallet:

      • Loads the encryption wallet from storage.

    5. Decrypt Payload:

      • Decrypts the payload using the wallet and the provided parameters (payload, user_key, and nonce).

    decrypt_to_payloadarrow-up-right uses chacha20poly1305 algorithm

    1. Verify Credentials:

    • Constructs a CosmosCredential from the decrypted data.

    • Inserts the nonce into storage to mark it as used.

    • Verifies the sender using the verify_arbitrary function with the credential.

    1. Deserialize Inner Message:

    • Converts the decrypted payload into the original message type E.

    • Ensures the decrypted message is not encrypted (nested encryption is not allowed).

    1. Return Decrypted Message and Updated Info:

    • Returns the decrypted message and updated MessageInfo with the verified sender.

    chacha20poly1305_decrypt

    The following function uses the following types for as the input parameters:

    • cosmwasm_std::Binary,

    • std::vec::Vec.

    • [u8]

    • and others that implement Deref<Target = [u8]> trait

    hashtag
    Various authentication utilities

    To verify a message that was was signed through a method cosmos arbitrary (036) message format, you can use the following function:

    The method takes in a CosmosCredential struct as an argument which is a a helpful wrapper over essential required fields required for the verification:

    Both CosmosCredential and EncryptedParams can be used with String or base64 encoded Binary types

    To generate a preamble message for the cosmos arbitrary (036) message format, you can use the following utility function:

    The function uses a hardcoded JSON string with all the required keys present and sorted.

    herearrow-up-right
    {
      //... other ibc fields omitted for example
      "data": {
        "denom": "denom on counterparty chain (e.g. uatom)", // will be transformed to the local denom (ibc/...)
        "amount": "1000",
        "sender": "addr on counterparty chain", // will be ignored and shown to the contract as a null sender (cannot be verifed over IBC)
        "receiver": "secret1contractAddr",
        "memo": {
          "wasm": {
            "contract": "secret1contractAddr",
            "msg": {
              "raw_message_fields": "raw_message_data"
            }
          }
        }
      }
    }
    #[entry_point]
    pub fn execute(_deps: DepsMut, env: Env, info: MessageInfo, msg: Msg) -> StdResult<Response> {
        match msg {
            Msg::WrapDeposit {
                snip20_address,
                snip20_code_hash,
                recipient_address,
            } => Ok(Response::default().add_messages(vec![
                CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute {
                    contract_addr: snip20_address.clone(),
                    code_hash: snip20_code_hash.clone(),
                    msg: to_binary(&secret_toolkit::snip20::HandleMsg::Deposit { padding: None })
                        .unwrap(),
                    funds: info.funds.clone(),
                }),
                CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute {
                    contract_addr: snip20_address,
                    code_hash: snip20_code_hash,
                    msg: to_binary(&secret_toolkit::snip20::HandleMsg::Transfer {
                        recipient: recipient_address,
                        amount: info.funds[0].amount,
                        memo: None,
                        padding: None,
                    })
                    .unwrap(),
                    funds: vec![],
                }),
            ])),
            }
        }
    #[cw_serde]
    pub enum IBCLifecycleComplete {
        #[serde(rename = "ibc_ack")]
        IBCAck {
            /// The source channel (secret side) of the IBC packet
            channel: String,
            /// The sequence number that the packet was sent with
            sequence: u64,
            /// String encoded version of the ack as seen by OnAcknowledgementPacket(..)
            ack: String,
            /// Weather an ack is a success of failure according to the transfer spec
            success: bool,
        },
        #[serde(rename = "ibc_timeout")]
        IBCTimeout {
            /// The source channel (secret side) of the IBC packet
            channel: String,
            /// The sequence number that the packet was sent with
            sequence: u64,
        },
    }
    
    /// Message type for `sudo` entry_point
    #[cw_serde]
    pub enum SudoMsg {
        #[serde(rename = "ibc_lifecycle_complete")]
        IBCLifecycleComplete(IBCLifecycleComplete),
    }
    
    #[entry_point]
    pub fn sudo(_deps: DepsMut, _env: Env, msg: SudoMsg) -> StdResult<Response> {
        match msg {
            SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCAck {
                channel,
                sequence,
                ack,
                success,
            }) => todo!(),
            SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCTimeout {
                channel,
                sequence,
            }) => todo!(),
        }
    }
    /// A data structure that is safe to be visible by all network participants and can be transmited over non-secure channels
    struct EncryptedParams {
        /// Encrypted payload containging hidden message
        pub payload            :   Binary,
        /// Sha256 hash of the payload
        pub payload_hash       :   Binary,
        /// Signed base64 digest of the payload_hash being wrapped
        /// in an cosmos arbitrary (036) object and rehashed again with sha256
        pub payload_signature  :   Binary,
        /// Public key of wallet used for deriving a shared key for chacha20_poly1305
        /// Not necessary the same as user's public key 
        pub user_key           :   Binary,
        /// One-time nonce used for chacha20_poly1305 encryption
        pub nonce              :   Binary,
    }
    
    /// Data meant to be encrypted and stored in the payload field of [EncryptedParams]
    #[cw_serde]
    pub struct EncryptedPayload {
        /// bech32 prefix address of a wallet used for signing hash of the payload 
        pub user_address   :  String,
        /// Public key of a wallet used for signing hash of the payload 
        pub user_pubkey   :   Binary,
        /// Human readable prefix for the bech32 address on the remote cosmos chain
        pub hrp           :   String,
        /// Plaintext message e.g. normal `ExecuteMsg` of your contract
        pub msg           :   Binary,
    }
    pub enum ExecuteMsg {
        ...
    
        Encrypted {
            payload             :   Binary,
            payload_signature   :   Binary,
            payload_hash        :   Binary,
            user_key            :   Binary,
            nonce               :   Binary,
        }
        ...
    
    }
    trait WithEncryption : Serialize + Clone  {
        fn encrypted(&self)     -> EncryptedParams;
        fn is_encrypted(&self)  -> bool;
    }
    impl WithEncryption for ExecuteMsg {
       fn encrypted(&self)     -> EncryptedParams {
           match self.clone() {
               ExecuteMsg::Encrypted {
                   payload,
                   payload_signature,
                   payload_hash,
                   user_key,
                   nonce,
               } => EncryptedParams {
                   payload,
                   payload_signature,
                   payload_hash,
                   user_key,
                   nonce
               },
               _ => panic!("Not encrypted")
    
           }
       }
    
       fn is_encrypted(&self)  -> bool {
           if ExecuteMsg::Encrypted{..} = self {
               true
           } else {
               false
           }
       }
    }
    pub enum GatewayExecuteMsg<E = Option<Empty>> 
        where E: JsonSchema
    {
        ResetEncryptionKey  {} ,
    
        Encrypted {
            payload             :   Binary,
            payload_signature   :   Binary,
            payload_hash        :   Binary,
            user_key            :   Binary,
            nonce               :   Binary,
        },
    
        Extension {
            msg : E
        }
    }
    // Degine your custom message
    #[cw_serde]
    pub enum MyCustomMessage {
        HandleFoo {}
        HandleBar {}
    }
    // Extend the GatewayExecuteMsg
    pub type MyGatewayExecuteMsg = GatewayExecuteMsg<MyCustomMessage>;
    /// MyGatewayExecuteMsg
    match msg {
        ... 
        ResetEncryptionKey => { ... },
    
        MyGatewayExecuteMsg::Extension{msg} => {
    
            /// MyCustomMessage
            match msg {
                MyCustomMessage::HandleFoo{} => {
                    // Do something
                }
                MyCustomMessage::HandleBar{} => {
                    // Do something
                }
            }
        }
        
        ...
    }
          let decrypted  = wallet.decrypt_to_payload(
                &params.payload,
                &params.user_key,
                &params.nonce,
            )?;
    pub fn chacha20poly1305_decrypt(
        ciphertext    :     &impl Deref<Target = [u8]>,
        key           :     &impl Deref<Target = [u8]>,
        nonce         :     &impl Deref<Target = [u8]>,
    ) -> StdResult<Vec<u8>> {
        ...
    }
    fn verify_arbitrary<M : Display>(api:  &dyn Api, cred: &CosmosCredential<M>) -> StdResult<String>
    pub struct CosmosCredential<M = String> 
        where M: Display
    {
        /// public key matching the respective secret that was used to sign message
        pub pubkey    :   Binary,
        /// signed sha256 digest of a message wrapped in arbitary data (036) object
        pub signature :   Binary,
        /// signed inner message before being wrapped with 036
        pub message   :   M,
        /// prefix for the bech32 address on remote cosmos chain
        pub hrp       :   String
    }
    fn preamble_msg_arb_036(signer: &str, data: &str) -> String