A Crypto Wallet in Technical Perspective
Written on May 02, 2024 by Renaka Agusta.
Introduction
A crypto wallet is an integral part of the blockchain ecosystem. A crypto wallet in blockchain is not similar to a wallet in the real world. One of the most misconceptions people think is a wallet in blockchain stores their coins, tokens, or even their silly NFTs. Unfortunately, it doesn't. Apart from that, a crypto wallet is not only related to your digital asset, but also acts as a digital identity that authorizes you to access some decentralized applications or also known as dapps. In this post, I will cover basic concept of crypto wallet, how it is created, the use of it, etc.
Comparing to the E-Wallet
In the context of digital ecosystem it will be closer to compare a crypto wallet to an e-wallet. What you see as a balance in your e-wallet app doesn't mean it stores your money directly on its app, instead it's just a representation of your money stored in the related merchant or bank. Similar to that, balance of coins or tokens in a crypto wallet is just a representation of the amount of coins or tokens stored in the blockchain. In outline, they seem pretty similar in terms of how they track the user's balance. An e-wallet track the balance by mapping the user's unique identifer, while a crypto wallet track the balance by mapping the user's wallet address.
Even though a crypto wallet has some similarities to an e-wallet, actually it's beyond of that. Since a crypto wallet is used in the blockchain ecosystem, obviously there is no centralized party. If we store cryptocurrency in our wallet like metamask or something else, there is no custodian as we store our money in a bank. Sounds pretty scary right? well, everything has it's pros and cons. A non-custodian wallet, like a crypto wallet is considered to be more beneficial to the user since it gives more control to the their asset because they don't have to trust any third parties to hold their asset. Moreover, while an e-wallet stores user's unique identifier or some confidental data directly related to user's personal data, a crypto wallet stores a bunch of keys that doesn't related with user's personal data.
Public Key Cryptogaphy
Everything in blockchain is not far from cryptography including a crypto wallet. Crypto wallet uses many cryptography techniques to perform various tasks. For example, the implementation of public key cryptography is the core cryptography principal used in crypto wallet. There are two keys: a public key and a private key. For the simple analogy:
- Public Key: similar to account number, everyone is allowed to share their public keys with each other.
- Private Key: similar to pin number or something else used as a credential to your account whereas there is no one permitted to know that except you.
Basically both of public key and private key is a pair. Public key is derived from private key using asymmetric cryptography. In Bitcoin, the public key derivation is done using Elliptic Curve Cryptogaphy (ECC) algorithm. ECC is well known as one of the best asymmetric cryptography algorithms since this algorithm offers the higher security with the shorter key length and fewer computational resources required compared to others.
We can use application like OpenSSL to generate a private key and achieve the same result as a real Bitcoin wallet.
openssl ecparam -name secp256k1 -genkey -noout -out private-key.pem
secp256k1 is a parameter used by Bitcoin wallet to generate a private key. After that, we can derive the public key from the private key
openssl ec -in private-key.pem -pubout -out public-key.pem
We also able to do those tasks with a cli application that dedicated for that, namely Bitcoin-cli or even some libraries in any programming languages like Javascript, Golang, Rust, etc.
Even though in the previous explanation we use an account number as an analogy for public key, actually we aren't able to receive coins with our public key directly. It's just a simplification used by most of people to explain private key and public key. Instead of a public key, a value called as address is used to receive coins from another wallet. This value is generated from public key with the following steps in Bitcoin:
- Hashing the Public Key: hash the public key using sha-256 algorithm.
- Applying RIPEMD-160 Hash Function: hash the previous result using RIPEMD-160 algorithm.
- Adding Version Byte: A version byte is prefixed to the RIPEMD-160 hash. This byte indicates the network (mainnet, testnet, etc.) and the type of address (pay-to-public-key-hash or P2PKH).
- Checksum calculation: A checksum is calculated by performing a double SHA-256 hash of the version byte concatenated with the RIPEMD-160 hash. The first four bytes of this double hash are appended to the end of the version byte and RIPEMD-160 hash.
- Base58 Encoding: The version byte, RIPEMD-160 hash, and checksum are concatenated and encoded using Base58 encoding. This encoding removes potentially confusing characters (like 0, O, I, and l) and provides a more human-readable representation of the address.
Meanwhile, in Ethereum steps required to produce an address from a public key are more straightforward
- Hash the public key using keccak256 algorithm.
- Take the last 20 bytes.
- Convert to a hexadecimal string.
Wallet Evolution
Crypto wallet has evolve over time to improve various aspects, including user experience and security. Currently, there are at least three types of crypto wallet in the blockchain like Bitcoin and Ethereum.
Non-deteministic Wallet
This is the initial type of wallet used in the blockchain. A non-deterministic wallet is simply a bunch of private keys. The private keys are generated randomly without any relation to each other. If someone want to access all of their funds, they have to remember all of their private keys, which is very not practical approach. Just information, a valid private key in Bitcoin has 64 characters and each character can be from the range (0-9) or (A-F). It is clear that it isn't easy for someone to memoize. In case of key management, users need to manage and store each key individually. Moreover, losing a private key also means lose the associated funds.
Deterministic Wallet
The deterministic wallet is developed to address two main issues: key management and recovery. The deterministic wallet introduces mnemonic or most of people called as a seed phrase. Mnemonic typically consist of 12 or 24 words that used to generate all private keys in a wallet. This new way is proposed in BIP39. BIP stands for Bitcoin Improvement Proposal. Mnemonic words described in this proposal are chosen randomly from the word list that available at https://github.com/Bitcoin/bips/blob/master/bip-0039/english.txt.
You can generate new mnemonic words using many libraries, for example you can use Ethereum cryptography library. The example code is as follows:
import { generateMnemonic } from 'Ethereum-cryptography/bip39/index.js';
import { wordlist } from 'Ethereum-cryptography/bip39/wordlists/english.js';
const mnemonicWords = generateMnemonic(wordlist);
console.log(mnemonicWords);
The result looks like this:
course expire ribbon physical grit brief correct age salad melody chair scrap
As you can see the given 12 words are easier to remember than a 64 hexadecimal characters. The diagram below shows some steps required to generate the private key until the address:
Based on the diagram, we know that mnemonic words generation is done at the first. Mnemonic words are generated randomly from the provided word list, which consist of 2048 words. Some people might be curious about the possibility more than similiar mnemonic words are generated for more than one user. The case is possible to happen but the probability is very low. Suppose someone decide to generate mnemonic words with 12 words, the number of possible generated words are equal to \(2048^{12}\). 2048 is the number of possibility for each word. While, 12 is the length of mnemonic words we have chosen. Therefore, the possibility for another user has same mnemonic words is \(\frac{1}{2048^{12}}\). Based on that, the possibility is extremely low. To decrease the possibility user can choose the longer mnemonic words characters. For example, if we want to generate mnemonic words with 24 words the probability is equal to ** \(\frac{1}{2048^{24}}\)**. The longer mnemonic words, the more secure wallet will be.
A Seed is generated from mnemonic words using a key derivation function. Below is the code to generate a seed from mnemonic words
import { pbkdf2 } from 'Ethereum-cryptography/pbkdf2';
import { utf8ToBytes } from 'Ethereum-cryptography/utils.js';
// Your mnemonic words (space-separated)
const mnemonic =
'course expire ribbon physical grit brief correct age salad melody chair scrap';
const mnemonicBytes = utf8ToBytes(mnemonic);
// Salt for the Scrypt KDF
const passphrase = '';
const salt = 'mnemonic' + passphrase;
const saltBytes = utf8ToBytes(salt);
const seed = await pbkdf2(mnemonicBytes, saltBytes, 2048, 64, 'sha512');
console.log(Buffer.from(seed).toString('hex'));
The core of the code is the implementation KDF which stands for Key Derivation Function using pbkdf2 function from the previous generated mnemonic words.
Later on we will generate the master private key from generated seed with the following code
import { createHmac } from 'crypto';
import { bytesToHex } from 'Ethereum-cryptography/utils';
const seed = Buffer.from(
'c38ab77a34684b09eab241a258734b4663e405b745b30f441681c4a132cc47d6c478005fc39510cf923b381cd0bf2c9eb9e41025d1988943f50089dd0cbf00fa',
'hex'
);
// BIP32 Master Key Derivation
const key = 'Bitcoin seed'; // Standard BIP32 Master key derivation uses "Bitcoin seed"
const hmac = createHmac('sha512', Buffer.from(key));
hmac.update(seed);
const output = hmac.digest(); // Outputs a Buffer of 64 bytes
// Split the output to get the master private key and chain code
const masterPrivateKey = output.slice(0, 32); // First 256 bits
const masterChainCode = output.slice(32, 64); // Next 256 bits
console.log('Master Private Key:', bytesToHex(masterPrivateKey));
console.log('Master Chain Code:', bytesToHex(masterChainCode));
Essentially, we utilize KDF again with the difference function that is HMAC-SHA512. The outputs has 64 hexadecimal characters and will be splitted into a master private key and a chain code. Those values will be used again to derive a child private key.
import { keccak256 } from 'Ethereum-cryptography/keccak.js';
import { secp256k1 } from 'Ethereum-cryptography/secp256k1.js';
import {
bytesToHex as toHex,
hexToBytes as toBytes,
} from 'Ethereum-cryptography/utils.js';
const masterPrivateKey =
'9170fb5b5197aea38a0ba4c4b97de14d3357c1dd829abb07328ecaf11fbfba92';
const childIndex = 0;
const masterPrivateKeyBuffer = toBytes(masterPrivateKey);
const childIndexBuffer = new Uint8Array([childIndex]);
const childPrivateKeyBuffer = keccak256(
masterPrivateKeyBuffer,
childIndexBuffer
);
const privateKey = toHex(childPrivateKeyBuffer);
const publicKeyBuffer = secp256k1.getPublicKey(privateKey);
const publicKey = toHex(publicKeyBuffer);
const publickKeyBufferHashed = keccak256(publicKeyBuffer);
const address = toHex(publickKeyBufferHashed.slice(-20));
console.log('private key: ', privateKey);
console.log('public key: ', publicKey);
console.log('address: ', address);
In deterministic wallet, each child private key is derived by concatenating the master private key with the child index. The child index is a unique numerical value specified to derive a new child private key. In case of recovery, the child index will be incremented by iterating from zero until the last generated private key. The generated private key can be tracked using some scenarios such as tracking the transactions of current generated address in existing blocks. If there are no transactions for the current generated address, the previous child index can be considered as the last child index.
After we get the private key value, the public key can be generated using the secp256k1 curve's cousin curve that is represented as secp256k1.getPublicKey(privateKey) in the code above. Generating address is more straightforward, we just need to get the last 20 bytes of public key and convert it to hexadecimal.
Hierarchical Deteministic Wallets
The previous deterministic wallet generates a new private key sequentially by incrementing the value of the child index. However, in the context of organization, the previous method isn't considered as the proper way since the derivation is done sequentially can't represent the organization structure. To address that issue, BIP 32 and BIP 44 introduce a new way to generate child private keys in tree structure to reflect the organization structure.
The key difference of hierarchical deterministic wallet with the previous one is the derivation path parameter used in the generation of child private key. Suppose the given derivation path looks like this
m/60'/0'/0'/0'/0/2
Breakdown:
- m:: The master private key.
- 60: Purpose is used to specify the blockchain used, for example 40 is used for Bitcoin and 60 is used for Ethereum.
- 0': Coin type is used to specify the coin type, for example 0 is used for Ethereum mainnet, 1 is used for Ethereum testnet, 2 is used for Ethereum-based token or asset etc.
- 0': Account is depend on the wallet spesific implementation. For example 0 is used for Ledger Live, 1 is used for Metamask, 1001 is used for Trust Wallet etc.
- 0': Address type. In case of Bitcoin that used UTXO Model, if someone decide to transfer 1 BTC, but they have 2 BTC UTX0 (unspent transaction output). The wallet will create a new address that usually called as change address to receive 1 BTC. So basically change address is used internally. For this property the possible value is 0 for receive address and 1 for change address. In case of blockchain like Ethereum that use Account Based Model the value is always 0 becuase there is no concept like change address in Ethereum.
- 0: First level child key.
- 2: Third level child key.
The length of derivation path can be longer than the example, it depends on the organization structure. Here is how we can utilize Ethereum-cryptography to derive a child private key from a given derivation path:
import { HDKey } from 'Ethereum-cryptography/hdkey.js';
import {
bytesToHex as toHex,
hexToBytes as toBytes,
} from 'Ethereum-cryptography/utils.js';
const hdKey = HDKey.fromMasterSeed(
toBytes(
'3fe945b259a7ed1f7466999f5c9bea0464c873cde8c07790ad38eadc4b8b5cea94dfe927673727b6f2a5b80f52b038e30f112fd0d56a06f77c53dba9050a7b4e'
)
);
const derivationPath = "m/66'/0'/0'/0/2";
const derivedKey = hdKey.derive(derivationPath);
console.log(toHex(derivedKey.privateKey));
First, we have to create HD Key object. Ethereum cryptography provides a method called fromMasterSeed to streamline the creation of HD Key object instead of using a master private key. In the above code we derive a new child private key using provided method named as derive with passing derivation path we want.
Actually, i want to use a lower level way to generate a private child key for better understanding, but after several attempts, i still can't figure out why my lower level way doesn't have the same result compared to hdKey.derive function provided by ethereum-cryptography library. Hopefully the following diagram can give the better understanding about that:
Based on that diagram, the private key generation uses HMAC-SHA512. HMAC-SHA512 (Hash-based Message Authentication Code using the Secure Hash Algorithm with a 512-bit hash function) is a cryptography technique used for data integrity and authentication. The output produced by this technique has 512 bits that will be divided into two parts. The first one is private key and the second one is chain code. Both of them will be used to derive a child private key along with index and a flag to indicates wether it's hardened or not. Hardened prevent an attacker who knows a child public key and a parent chain code to generate siblings private key. Suppose we have a derivation path value, then how to generate the child private key based on it? let's started it with the following steps:
- As we know derivation path contains some
/
characters. Initially, the deriviation path will be splitted by/
character. The splitted values will be stored to a variable as an array. Let say the name of variable is indexes. - Then perform an iteration where the number of iteration steps depends on the length of the indexes variable.
- For each element in the iteration step, we have to make a flag to determine wether it's hardened or not by checkhing if the current element contains
'
character or not. Let say the value is stored in a boolean variable called isHardened. - Then remove the
'
character to convert the current element as an integer and stored it to a integer variable calledindex
. - Later on, if isHardened value is true then we have to add 0x80000000 to he current index value.
- Concate private key and index.
- Hash it along with chain code using HMAC-SHA512 algorithm. This process will give a value that has 512 bits.
- Split it into two parts, the first one is private key and the second one is chain code. Update the old private key and chain code with the new generated values. Back to the third step until the last element of indexes variable.
Transaction Signing
Transaction signing is the process required to make sure that value specified as a sender in a transaction is an actual sender. This is a important step before sending a request transaction to the network since a validator node will validate the transaction first before process it. The transaction signing can be done by leveraging Elliptic Curve Digital Signature Algorithm (ECDSA). Before signing process is done, there are several steps to do:
- Transaction Creation: wallet constructs a transaction data including:
- Nonce: the transaction count of sender addrss
- Gas Price: the fee per unit of gas
- Gas Limit: the maximum amount of gas units
- To Address: the recipient address
- Value: the amount of Ether
- Data: the optional data that will be sent along with the transaction
- Transaction Serialization: transaction is serialized using Recursive Length Prefix (RLP) encoding. One of the objectives of this serialization is to make the transaction data is more compact to store, transmit, parse and so on.
- Message Hashing: hash the transaction data using Keccak-256 algorithm to make the transactiondata that will be signed is more compact.
After the transaction is hashed, it will be signed using Elliptic Curve Digital Signature Algorithm (ECDSA). The signature resulted by the signing is r
, s
, and v
where
- r and s: ECDSA result value
- v: A value to help recovery the public key. This value is crucial to validate that the sender address specified in the transaction is actual sender.
ps : this transaction creation format is used in Ethereum that used Account Based Model, meanwhile blockchain that used UTXO Model will slightly different since it requires to gather the UTXO first.
To make an example of that implmentation with code, i have downgraded the version of Ethereum-cryptography library to 1.1.2 since this version is more straightforward to use and understand for this purpose
import { keccak256 } from 'Ethereum-cryptography/keccak.js';
import secp256k1 from 'Ethereum-cryptography/secp256k1.js';
import { bytesToHex, hexToBytes } from 'Ethereum-cryptography/utils.js';
import rlp from 'rlp';
const toBufferBE = (value, byteLength) => {
const hex = value.toString(16).padStart(byteLength * 2, '0');
return Buffer.from(hex, 'hex');
};
const signTransaction = async (rlpEncodedTx, privateKey) => {
const hashedMessage = keccak256(rlpEncodedTx);
const signature = await secp256k1.sign(hashedMessage, privateKey, {
recovered: true,
});
return signature;
};
const recoverKey = (message, signature, recoveryBit) => {
const bytes = secp256k1.recoverPublicKey(
keccak256(message),
signature,
recoveryBit
);
return bytes;
};
const serializeTransaction = (tx) => {
const txData = [
toBufferBE(BigInt(tx.nonce), 32),
toBufferBE(BigInt(tx.gasPrice), 32),
toBufferBE(BigInt(tx.gasLimit), 32),
Buffer.from(tx.to.slice(2), 'hex'),
toBufferBE(BigInt(tx.value), 32),
Buffer.from(tx.data.slice(2), 'hex'),
];
const rlpEncodedTx = rlp.encode(txData);
return rlpEncodedTx;
};
const main = async () => {
const privateKey = bytesToHex(secp256k1.utils.randomPrivateKey());
// Transaction creation
const transaction = {
nonce: '0x0',
gasPrice: '0x09184e72a000',
gasLimit: '0x2710',
to: '0x3535353535353535353535353535353535353535',
value: '0x0',
data: '0x',
};
const serializedTransaction = serializeTransaction(transaction);
const [signature, recoveryBit] = await signTransaction(
serializedTransaction,
hexToBytes(privateKey)
);
return signature;
};
main();
Above is the code of transaction signing implementation. Basically, most values in Ethereum are in big integer format, so to do some operation with them, we have to create a custom function to convert it to buffer called toBufferBE.
To sign a transaction data we leverage secp256k1.sign() method. In this case, it is required to specify options recovered: true, so we can recover the public key from the signature and use it to validate the signature. This method will return signature and recovery bit. The value of recovery bit is required to validate the signature. Without it, recovering an exact public keys is difficult since there are many possible public keys for a given signature and hashed message satisfy ECDSA equation.
Here is some enhancement of previous to validate the signature:
...
const verifySignature = (
serializedTransaction,
signature,
recoveryBit,
publicKey
) => {
const hashedMessage = keccak256(serializedTransaction);
const recoveredPublicKey = secp256k1.recoverPublicKey(
hashedMessage,
signature,
recoveryBit
);
console.log('public key', publicKey);
console.log('recovered public key', bytesToHex(recoveredPublicKey));
return recoveredPublicKey === publicKey;
};
const main = async () => {
const privateKey = bytesToHex(secp256k1.utils.randomPrivateKey());
const publicKey = bytesToHex(secp256k1.getPublicKey(hexToBytes(privateKey)));
const transaction = {
nonce: '0x0',
gasPrice: '0x09184e72a000',
gasLimit: '0x2710',
to: '0x3535353535353535353535353535353535353535',
value: '0x0',
data: '0x',
};
const serializedTransaction = serializeTransaction(transaction);
const [signature, recoveryBit] = await signTransaction(
serializedTransaction,
hexToBytes(privateKey)
);
return verifySignature(
serializedTransaction,
signature,
recoveryBit,
publicKey
);
};
main();
As i have said before, signature validation is simply done by comparing the public key you want to validate with the recovered public key. Recovered public key is generated by ulitizing secp256k1.recoverPublicKey() method by specifying hashed message, signature, and recovery bit that we obtained from the signing process. In code above, i add logs to show determining wether a signature is valid or not can be done easily by comparing a given public key to the recovered public key.
Get Balance
In the initial section, there are some correlations between a crypto wallet and e-wallet in terms of how to track user's balance. In this section we will delve into this case. First of all, we should have common understanding about the difference between coin and token.
- Coin: a native currency used in a blockchain that mainly used as a currency in a blockchain. This is created when a blockchain is created. The examples of coins include BTC in Bitcoin, ETH in Ethereum, SOL in Solana, etc.
- Token: a digital asset that running on top of blockchain ecosystem that has various usecases such as digital asset, access to services, voting rights, etc. This is created by deploying a new smart conctract that implements ERC-20 on Ethereum or BEP-20 on Binance. The examples of tokens include USDT (Tether), UNI (UNISWAP), LINK (Chainlink), etc.
Has understanding to distinguish between those terms is important before continiung this section since the way to track the balances of a coin and a token is slightly difference.
Coin
As we know that a coin is created along with a blockchain created, obviously we don't need to interact with smart contract, instead we have to interact with the blockchain network. Interacting with the blockchain network means we have to interact with other nodes especially in this case is a full node.
Communicating between nodes is facilitated by the existence of RPC protocol. RPC stands for Remote Procedure Call. Most of us might be more familiar with terms of REST API
. In REST API, there is an endpoint url that is routed by backend application to a spesific function. Instead of using an endpoint url as an interface to the targeted function, an RPC client uses name of targeted function directly. Typically, blockchain uses JSON-RPC, means the data transmitted and received is in JSON format.
Below is an example of JSON-RPC call in curl:
curl -X POST -H "Content-Type: application/json" --data '{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe", "latest"],
"id": 1
}' http://localhost:8545
- jsonrpc: specifies JSON-RPC version
- method: specifies which method/function you want to call. In this case i want to get the balance of an address that i have, so i specify eth_getBalance. Basically you can specify any methods you want based on your need. For example, to get the current block number, user's transaction count, gas price, etc. You can see the available methods in the Ethereum documentation at this link.
- params: specifies required parameters you have to pass based on method you specify. Based on the documentation i need to pass an address and block number/state.
Latest
indicates the state at the most recent block. - id: act as an identifier to the current request. It will be crucial if you send a bunch of requests at the same time. This field you can be used to map responses properly.
The example of that request looks like this:
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x0234c8a3397aab58"
}
- jsonrpc: specifies JSON-RPC version
- id: act as an identifier to the current request. It will be crucial if you send a bunch of requests at the same time. With this field you can mapping the responses properly.
- result: the balance that we have requested. This value is formatted in hexadecimal to represent a big int or wei. Before display it to the user we have to convert it to decimal and then covert it to ether.
Token
Based on the previous explanatian, we know that if someone want to get the balance of their token, they need to interact with the smart contract of its token. According this this documentation, smart contract that implements the ERC-20 standard has a method to get the balance of spesific address namely balanceOf
. Therefore, we have to call that method to retrieve the balance of token for an address.
Interacting with smart contract also means interacting with a full node since all persistent data of smart contract is stored in a full node. So we will use the JSON-RPC again. Because we want to call a function inside a smart contract we will use eth_call as a method that we will specify in the request. Eth_call is a special function used to interact with a smart contract for read-only purpose. According to the previous documentation, eth_call has a parameter called as call object that consist of:
- to: (Required) The address of the contract you want to interact with.
- from: (Optional) The address from which the call is made. Defaults to the address associated with the node.
- gas: (Optional) The amount of gas to use for the call.
- gasPrice: (Optional) The gas price in Wei to use for the call.
- value: (Optional) The amount of Wei to send with the call.
- data: (Optional) The data to send with the call, typically the function signature and encoded parameters.
In case to get the current token balance we just need to specify to and data. To as described above is specified based on the smart contract address, while data is depend on the function of smart contract we want to call. Let's take a look at the balanceOf function in ERC-20 standard:
function balanceOf(address _owner) public view returns (uint256 balance)
Let's create the data:
- According to that function the signature is balanceOf(address).
- Hash it with keccak256 and the result is
0x70a08231
. - Suppose the user's address is
0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe
remove the prefix 0x
and pad it with leading zeros to 64 characters. The result is
000000000000000000000000de0B295669a9FD93d5F28D9Ec85E40f4cb697BAe
- Concate the result of step 2 and step 3, so we get
0x70a08231000000000000000000000000de0b295669a9fd93d5f28d9ec85e40f4cb697bae
Below is the curl representation of our example request:
curl -X POST -H "Content-Type: application/json" --data '{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"data": "0x70a08231000000000000000000000000de0b295669a9fd93d5f28d9ec85e40f4cb697bae"
},
"latest"
],
"id": 1
}' http://localhost:8545
The example response of the request is
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x000000000000000000000000000000000000000000000000000000000000000a"
}
Compared to the result of previous response from eth_getBalance, it looks slightly difference, especially in terms of the length of the result. This is because the result all functions in smart contract that returns uint256 data type will be padded to 64 hex characters.
Conclusion
A crypto wallet plays an important role in the blockchain use. A crypto wallet can be used as we use an e-wallet for checking balance, transfering, and receiving. However a crypto wallet is more than that, a crypto wallet is also can be used as a digital identity or a tool to interact with some services in a blockchain. A crypto wallet relies on cryptography techniques, especially those related to public-key cryptography and hashing to perform its function. Having a good understanding about that in technical is a good point, especially for developer.