Skip to content

Instantly share code, notes, and snippets.

@nnkken
Last active November 25, 2021 07:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nnkken/90428d73f38d957de1b75ec3992d9342 to your computer and use it in GitHub Desktop.
Save nnkken/90428d73f38d957de1b75ec3992d9342 to your computer and use it in GitHub Desktop.
Accessing LikeCoin Chain

Key and Address

A private key is a 32 bytes (256 bits) binary.

The public key is derived from the private key according to secp256k1, in 33-bytes compressed format.

The Cosmos address is derived by the follows: address = ripemd160(sha256(pubKey)), encoded by bech32 with cosmos as prefix.

See here for an example in JavaScript.

Nodes and Endpoints

To use the RPC APIs, we need a a lite client endpoint, which communicates with full nodes and verify the data from full nodes cryptographically (e.g. verifying Merkle proof).

For the Taipei testnet, users can use the following endpoint:

Lite client endpoint: https://node.taipei.like.co
Chain ID: likechain-testnet-taipei-1

For the SheungWan mainnet:

Lite client endpoint: https://mainnet-node.like.co
Chain ID: likecoin-chain-sheungwan

Users can also run a full node and a lite client.

To setup a full node, please follow the instruction from this document: https://github.com/likecoin/likecoin-chain/wiki/Setup-LikeCoin-chain-mainnet-node

For lite client, users can host a lite client using the likecli command in the Docker image.

Here is an example Docker Compose config file for running the Taipei network full node and lite client. This hosts an RPC API endpoint on port 1317.

Cosmos Concepts

Chain ID

Chain ID defines which chain / network you are operating on, to prevent replaying transactions on other networks.

Account

Each address corresponds to an account, which will have an account number and a sequence number.

Account Number

Account number is the account's number.

It is required in transactions, and could be queried from the /auth/accounts/{address} API.

Sequence

Sequence is the number of transactions the account has sent.

It is required in transactions to prevent transaction replays, similar to the concept of nonce in Ethereum.

It could be queried from the /auth/accounts/{address} API.

Coins

In Cosmos SDK, coins are represented as an object with 2 fields: amount and denom.

amount is the number of coins, while denom is the identifier of the coins.

In LikeCoin Chain, we only have one denom, which is nanolike. 1 LikeCoin = 1000000000nanolike.

When used in short forms (e.g. CLI parameters), the format of coins is ${amount}${denom}, e.g. 1000000000nanolike.

Gas and Fees

In Cosmos SDK, there is a concept called gas, which is similar to the concept of gas in Ethereum.

Each transaction must specify the maximum amount of gas it may consume.

If the transaction consumed more gas than provided during execution, it will fail.

Different from Ethereum, transactions specify their transaction fee directly, instead of specifying the gas price.

Nodes calculates the gas price by gas_price = fee / gas, and may reject transactions with gas price lower than their expectation.

We currently recommend validators to set the gas price to 1000nanolike. For a typical coin sending transaction, it consumes less than 100000 gas, which means the transaction fee is 0.1 LIKE.

Since the fee is specified in the transaction, it will not be refunded even if the actual gas consumption is less than the provided one.

Signature

The signing process is the standard secp256k1 signing.

The output bytes are organized in 64 bytes, which are the r and s values, 256 bites each, concatenated.

The signature is represented as object in transactions, which includes the public key and the signature bytes.

Example:

    {
        "signature": "yXcQvLVEHZMIzZijEgmcL5S7orusBURZoRjWuG1IpoItt5DhY8P9TUaxx31huxV200l6GcEbUlB/Y7jONuf3Bw==",
        "account_number": "21",
        "sequence": "0",
        "pub_key": {
            "type": "tendermint/PubKeySecp256k1",
            "value": "A0ZGrlBHMWtCMNAIbIrOxofwCxzZ0dxjT2yzWKwKmo//"
        }
    }

Sign Bytes

The sign bytes are the input to the secp256k1 signing algorithm.

Sign bytes are obtained by the following process:

  1. construct a standard transaction as follows:
{
  "fee": {
    "amount": [
      {
        "denom": "nanolike",
        "amount": "100000000"
      }
    ],
    "gas": "100000"
  },
  "msgs": [msgs...],
  "chain_id": "likechain-testnet-taipei-1",
  "account_number": "21",
  "sequence": "0",
  "memo": "",
}

Where msgs are the msg objects of the transactions. For a typical Send transaction, it will look like this:

{
  "fee": {
    "amount": [
      {
        "denom": "nanolike",
        "amount": "100000000"
      }
    ],
    "gas": "100000"
  },
  "msgs": [
    {
      "type": "cosmos-sdk/MsgSend",
      "value": {
        "from_address": "cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04",
        "to_address": "cosmos1ca0zlqxjqv5gek5qxm602umtkmu88564hpyws4",
        "amount": [
          {
            "denom": "nanolike",
            "amount": "123456789"
          }
        ]
      }
    }
  ],
  "chain_id": "likechain-testnet-taipei-1",
  "account_number": "21",
  "sequence": "0",
  "memo": "",
}

Note that all numbers (e.g. amount, sequence, account_number) are encoded as string in the sign bytes.

  1. remove all fields with null value.
  2. reorganize the JSON message to remove all indents and newline formattings, and sort the fields by alphanumeric order.

The sign bytes of the example above after processing will look like this:

{"account_number":"21","chain_id":"likechain-testnet-taipei-1","fee":{"amount":[{"amount":"100000000","denom":"nanolike"}],"gas":"100000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"123456789","denom":"nanolike"}],"from_address":"cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04","to_address":"cosmos1ca0zlqxjqv5gek5qxm602umtkmu88564hpyws4"}}],"sequence":"0"}

RPC API

GET /auth/accounts/{address}

(TODO)

Returns the information about the account.

Some commonly used fields:

response.result.value.coins ([{amount: string, denom: string}]): the balance of the account. response.result.value.account_number (string): the account number, which is needed when signing transactions. response.result.value.sequence (string): the sequence for replay protection, which is needed when signing transactions.

Example: http://34.66.207.129:1317/auth/accounts/cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04

{
  "height": "574791",
  "result": {
    "type": "cosmos-sdk/Account",
    "value": {
      "address": "cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04",
      "coins": [
        {
          "denom": "nanolike",
          "amount": "10000000000"
        }
      ],
      "public_key": null,
      "account_number": "21",
      "sequence": "0"
    }
  }
}

GET /txs/{hash}

(TODO)

Fetch the transaction information.

A transaction may contain multiple messages, and if any of them failed during execution, the whole transaction fails without any state change, i.e. previous messages will be rollbacked.

User can check if the whole transaction succeeded by checking if the last object in response.logs shows success: true or not.

Example: http://34.66.207.129:1317/txs/4F5DC521E5A37F1EBB08B36DDA64A1051352C3F9070D6D969ADE28CA6F33B5C2

{
  "height":"574778",
  "txhash":"4F5DC521E5A37F1EBB08B36DDA64A1051352C3F9070D6D969ADE28CA6F33B5C2",
  "raw_log":"[{\"msg_index\":0,\"success\":true,\"log\":\"\"}]",
  "logs":[
    {
      "msg_index":0,
      "success":true,
      "log":""
    }
  ],
  "gas_wanted":"44000",
  "gas_used":"43053",
  "events":[
    {
      "type":"message",
      "attributes":[
        {
          "key":"action",
          "value":"send"
        },
        {
          "key":"sender",
          "value":"cosmos1ca0zlqxjqv5gek5qxm602umtkmu88564hpyws4"
        },
        {
          "key":"module",
          "value":"bank"
        }
      ]
    },
    {
      "type":"transfer",
      "attributes":[
        {
          "key":"recipient",
          "value":"cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04"
        },
        {
          "key":"amount",
          "value":"10000000000nanolike"
        }
      ]
    }
  ],
  "tx":{
    "type":"cosmos-sdk/StdTx",
    "value":{
      "msg":[
        {
          "type":"cosmos-sdk/MsgSend",
          "value":{
            "from_address":"cosmos1ca0zlqxjqv5gek5qxm602umtkmu88564hpyws4",
            "to_address":"cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04",
            "amount":[
              {
                "denom":"nanolike",
                "amount":"10000000000"
              }
            ]
          }
        }
      ],
      "fee":{
        "amount":[
          {
            "denom":"nanolike",
            "amount":"44000000"
          }
        ],
        "gas":"44000"
      },
      "signatures":[
        {
          "pub_key":{
            "type":"tendermint/PubKeySecp256k1",
            "value":"AwqajsW8qgrN0evcrRzz893GeGt4wl2ALbqXbaoGoYiB"
          },
          "signature":"E18vj9ENpYtXD35CgbEtdFXMiMQPF/+MvoEJ7dBIuJ05trQJGDihJhki5ctne9TuW1tl4oafRED37sXwslR7Fg=="
        }
      ],
      "memo":""
    }
  },
  "timestamp":"2019-10-16T07:40:27Z"
}

POST /txs

(TODO)

Sends a signed transaction.

Example request body:

{
  "mode":"sync",
  "tx":{
    "msg":[
      {
        "type":"cosmos-sdk/MsgSend",
        "value":{
          "from_address":"cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04",
          "to_address":"cosmos1ca0zlqxjqv5gek5qxm602umtkmu88564hpyws4",
          "amount":[
            {
              "denom":"nanolike",
              "amount":"123456789"
            }
          ]
        }
      }
    ],
    "fee":{
      "amount":[
        {
          "denom":"nanolike",
          "amount":"44000000"
        }
      ],
      "gas":"44000"
    },
    "memo":"",
    "signatures":[
      {
        "signature":"yXcQvLVEHZMIzZijEgmcL5S7orusBURZoRjWuG1IpoItt5DhY8P9TUaxx31huxV200l6GcEbUlB/Y7jONuf3Bw==",
        "account_number":"21",
        "sequence":"0",
        "pub_key":{
          "type":"tendermint/PubKeySecp256k1",
          "value":"A0ZGrlBHMWtCMNAIbIrOxofwCxzZ0dxjT2yzWKwKmo//"
        }
      }
    ]
  }
}

Response:

{
  "height":"0",
  "logs":[
    {
      "log":"",
      "msg_index":0,
      "success":true
    }
  ],
  "raw_log":"[{\"msg_index\":0,\"success\":true,\"log\":\"\"}]",
  "txhash":"8C3A38A9F340050C459DCD9C3F52396E7C76FC286BA879C791C233580F7A64F0"
}
version: "3.6"
services:
liked:
image: likechain/likechain
container_name: likechain_liked
volumes:
- ./.liked:/likechain/.liked
- ./.likecli:/likechain/.likecli
ports:
- 26656:26656
- 26657:26657
restart: always
command: ["liked", "--home", "/likechain/.liked", "--get-ip", "start", "--rpc.laddr", "tcp://0.0.0.0:26657", "--p2p.seeds", "b3b4160cd31cb31c1243427686dc80e72bc42bc5@35.226.174.222:26666"]
lcd:
image: likechain/likechain
container_name: likechain_lcd
volumes:
- ./.liked:/likechain/.liked
- ./.likecli:/likechain/.likecli
ports:
- 1317:1317
depends_on:
- liked
restart: always
command: ["likecli", "--home", "/likechain/.likecli", "rest-server", "--laddr", "tcp://0.0.0.0:1317", "--node", "tcp://liked:26657", "--chain-id", "likechain-testnet-taipei-1"]
networks:
default:
name: likechain
{
"fee": {
"amount": [
{
"denom": "nanolike",
"amount": "100000000"
}
],
"gas": "100000"
},
"msgs": [
{
"type": "cosmos-sdk/MsgSend",
"value": {
"from_address": "cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04",
"to_address": "cosmos1ca0zlqxjqv5gek5qxm602umtkmu88564hpyws4",
"amount": [
{
"denom": "nanolike",
"amount": "123456789"
}
]
}
}
],
"chain_id": "likechain-testnet-taipei-1",
"account_number": "21",
"sequence": "0",
"memo": "",
}
{"account_number":"21","chain_id":"likechain-testnet-taipei-1","fee":{"amount":[{"amount":"100000000","denom":"nanolike"}],"gas":"100000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"123456789","denom":"nanolike"}],"from_address":"cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04","to_address":"cosmos1ca0zlqxjqv5gek5qxm602umtkmu88564hpyws4"}}],"sequence":"0"}
const secp256k1 = require('secp256k1');
const bech32 = require('bech32');
const createHash = require('create-hash');
const jsonStringify = require('fast-json-stable-stringify');
const bip39 = require('bip39');
const bip32 = require('bip32');
// the code of `seedToPrivateKey` is derived from @lunie/cosmos-key
// https://github.com/luniehq/cosmos-keys/blob/2586e7af82fc52c2c2603383e850a1969539f4f1/src/cosmos-keys.ts
function seedToPrivateKey(mnemonic, hdPath = `m/44'/118'/0'/0/0`) {
const seed = bip39.mnemonicToSeedSync(mnemonic)
const masterKey = bip32.fromSeed(seed)
const { privateKey } = masterKey.derivePath(hdPath)
return privateKey
}
function createSigner(privateKey) {
console.log(`private key: ${privateKey.toString('hex')}`);
const publicKeyArr = secp256k1.publicKeyCreate(privateKey, true);
const publicKey = Buffer.from(publicKeyArr);
console.log(`public key: ${publicKey.toString('base64')}`);
const sha256 = createHash('sha256');
const ripemd = createHash('ripemd160');
sha256.update(publicKey);
ripemd.update(sha256.digest());
const rawAddr = ripemd.digest();
const cosmosAddress = bech32.encode('cosmos', bech32.toWords(rawAddr));
console.log(`address: ${cosmosAddress}`);
const sign = (msg) => {
const msgSha256 = createHash('sha256');
msgSha256.update(msg);
const msgHash = msgSha256.digest();
const { signature: signatureArr } = secp256k1.ecdsaSign(msgHash, privateKey);
const signature = Buffer.from(signatureArr)
console.log(`signature: ${signature.toString('base64')}`);
return { signature, publicKey };
}
return { cosmosAddress, sign };
}
const privKey = Buffer.from('0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 'hex');
// may also derive private key from seed words:
// const seed = "novel novel novel novel novel novel novel novel novel novel novel novel novel novel novel novel novel novel novel novel novel novel novel novel";
// const privKey = seedToPrivateKey(seed);
const signer = createSigner(privKey);
const signBytes = jsonStringify({
"fee": {
"amount": [
{
"denom": "nanolike",
"amount": "100000000"
}
],
"gas": "100000"
},
"msgs": [
{
"type": "cosmos-sdk/MsgSend",
"value": {
"from_address": "cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04",
"to_address": "cosmos1ca0zlqxjqv5gek5qxm602umtkmu88564hpyws4",
"amount": [
{
"denom": "nanolike",
"amount": "123456789"
}
]
}
}
],
"chain_id": "likechain-testnet-taipei-1",
"account_number": "21",
"sequence": "0",
"memo": "",
});
signer.sign(signBytes);
// private key: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
// public key: A0ZGrlBHMWtCMNAIbIrOxofwCxzZ0dxjT2yzWKwKmo//
// address: cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04
// signature: iBIA5d+tZ99hlcjdzvpm8/eHtK31kblp1lCHWb4CSzEQUm/Wns/emogUn6VsSQVt2eYPpLjnfNXas5PMgWzdnw==
{
"mode": "sync",
"tx": {
"msg": [
{
"type": "cosmos-sdk/MsgSend",
"value": {
"from_address": "cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04",
"to_address": "cosmos1ca0zlqxjqv5gek5qxm602umtkmu88564hpyws4",
"amount": [
{
"denom": "nanolike",
"amount": "123456789"
}
]
}
}
],
"fee": {
"amount": [
{
"denom": "nanolike",
"amount": "100000000"
}
],
"gas": "100000"
},
"memo": "",
"signatures": [
{
"signature": "iBIA5d+tZ99hlcjdzvpm8/eHtK31kblp1lCHWb4CSzEQUm/Wns/emogUn6VsSQVt2eYPpLjnfNXas5PMgWzdnw==",
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "A0ZGrlBHMWtCMNAIbIrOxofwCxzZ0dxjT2yzWKwKmo//"
}
}
]
}
}
const secp256k1 = require('secp256k1');
const createHash = require('create-hash');
const signature = Buffer.from('iBIA5d+tZ99hlcjdzvpm8/eHtK31kblp1lCHWb4CSzEQUm/Wns/emogUn6VsSQVt2eYPpLjnfNXas5PMgWzdnw==', 'base64');
const publicKey = Buffer.from('A0ZGrlBHMWtCMNAIbIrOxofwCxzZ0dxjT2yzWKwKmo//', 'base64');
const msg = '{"account_number":"21","chain_id":"likechain-testnet-taipei-1","fee":{"amount":[{"amount":"100000000","denom":"nanolike"}],"gas":"100000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"123456789","denom":"nanolike"}],"from_address":"cosmos1mnyn7x24xj6vraxeeq56dfkxa009tvhgknhm04","to_address":"cosmos1ca0zlqxjqv5gek5qxm602umtkmu88564hpyws4"}}],"sequence":"0"}'
const msgSha256 = createHash('sha256');
msgSha256.update(msg);
const msgHash = msgSha256.digest();
console.log(secp256k1.ecdsaVerify(signature, msgHash, publicKey));
// true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment