Search…
Generate a transaction signature
When a user sends a transaction, they need to sign the data with their own private key. There are two major steps involved in signing the transaction, serializing the original transaction data and generating a signature with user's own private key. This document describes the process of generating a digital signature of transaction data.

Intended Audience

    Someone who wants to develop the program which communicates with ICON network with languages whose SDK is unsupported

Purpose

Generating a valid transaction signature in ICON network.

Prerequisite

How to Serialize Transaction Data

Before signing the data, the data needs to be serialized as bytes. This section describes how to serialize the transaction data. This document does not describe how to make the transaction data itself. Transaction message specification is defined in JSON-RPC API v3.

Precondition

Transaction data is in JSON format with some restrictions.
Allowed types in JSON
Type
Description
String
Normal string without U+0000 (NULL) character. ex) “Value”
Dictionary
Pairs of key and value. ex) {“key1”: “value1”, “key1”: “value2”}
Array
Series of values. ex) [“value1”, “value2”]
Null
null

Serialize

ICON follows the JSON-RPC v2.0 protocol spec. A signed transaction request looks like the following:
1
{
2
"jsonrpc": "2.0",
3
"method": "icx_sendTransaction",
4
"id": 1234,
5
"params": {
6
"version": "0x3",
7
"from": "hxbe258ceb872e08851f1f59694dac2558708ece11",
8
"to": "hx5bfdb090f43a808005ffc27c25b213145e80b7cd",
9
"value": "0xde0b6b3a7640000",
10
"stepLimit": "0x12345",
11
"timestamp": "0x563a6cf330136",
12
"nid": "0x1",
13
"nonce": "0x1",
14
"signature": "X1tpJdHBvqroonpTbdsNEur7KAeYcZd9XGa39AkW51Uck8EqgJnioedm5W2jZSQuBzZJHWm0Uf5BeXSmXoOByAA="
15
}
16
}
Copied!
Transaction data is serialized by concatenating key, value pairs in params with . as a delimiter. Our final goal is generating the signature of transaction data, therefore, the signature field shown above example is not part of the data to be serialized. Adding the method name, “icx_sendTransaction”, to the serialized string as a prefix completes the serialization process.
1
icx_sendTransaction.<key1>.<value1>....<keyN>.<valueN>
Copied!
Make sure that all the special characters in string must be UTF-8 encoded.
String type
Apply UTF-8 encoding to the text. Characters listed below should be escaped with \. String must not have any null character, U+0000.
Name
Backslash (REVERSE SOLIDUS)
Period (FULL STOP)
Left curly bracket
Right curly bracket
Left square bracket
Right square bracket
Shape
\
.
{
}
[
]
UTF-8
0x5C
0x2E
0x7B
0x7D
0x5B
0x5D
Dictionary type
Enclosed with { and }, and key/value pairs are separated with .. Every keys in dictionary are string type, therefore, the same encoding rules apply. The order of keys in the serialized data follows the natural ordering of the UTF-8 encoded byte comparison (it’s same as Unicode string order).
1
{<key1>.<value1>.<key2>.<value2>....<keyN>.<valueN>}
Copied!
Example:
1
{
2
"method": "transfer",
3
"params": {
4
"to": "hxab2d8215eab14bc6bdd8bfb2c8151257032ecd8b",
5
"value": "0x1"
6
}
7
}
Copied!
Serialized as
1
{method.transfer.params.{to.hxab2d8215eab14bc6bdd8bfb2c8151257032ecd8b.value.0x1}}
Copied!
Array type
Enclosed with [ and ]. All values are separated with ..
1
[<value1>.<value2>.<value3>....<valueN>]
Copied!
Null type
Null will be represented as an escaped zero.
1
\0
Copied!

Example

ICX transfer
Original JSON request:
1
{
2
"jsonrpc": "2.0",
3
"method": "icx_sendTransaction",
4
"id": 1234,
5
"params": {
6
"version": "0x3",
7
"from": "hxbe258ceb872e08851f1f59694dac2558708ece11",
8
"to": "hx5bfdb090f43a808005ffc27c25b213145e80b7cd",
9
"value": "0xde0b6b3a7640000",
10
"stepLimit": "0x12345",
11
"timestamp": "0x563a6cf330136",
12
"nid": "0x1",
13
"nonce": "0x1"
14
}
15
}
Copied!
Serialized params:
1
icx_sendTransaction.from.hxbe258ceb872e08851f1f59694dac2558708ece11.nid.0x1.nonce.0x1.stepLimit.0x12345.timestamp.0x563a6cf330136.to.hx5bfdb090f43a808005ffc27c25b213145e80b7cd.value.0xde0b6b3a7640000.version.0x3
Copied!
SCORE API invocation
Original JSON request:
1
{
2
"jsonrpc": "2.0",
3
"method": "icx_sendTransaction",
4
"id": 1234,
5
"params": {
6
"version": "0x3",
7
"from": "hxbe258ceb872e08851f1f59694dac2558708ece11",
8
"to": "cxb0776ee37f5b45bfaea8cff1d8232fbb6122ec32",
9
"stepLimit": "0x12345",
10
"timestamp": "0x563a6cf330136",
11
"nid": "0x1",
12
"nonce": "0x1",
13
"dataType": "call",
14
"data": {
15
"method": "transfer",
16
"params": {
17
"to": "hxab2d8215eab14bc6bdd8bfb2c8151257032ecd8b",
18
"value": "0x1"
19
}
20
}
21
}
22
}
Copied!
Serialized params:
1
icx_sendTransaction.data.{method.transfer.params.{to.hxab2d8215eab14bc6bdd8bfb2c8151257032ecd8b.value.0x1}}.dataType.call.from.hxbe258ceb872e08851f1f59694dac2558708ece11.nid.0x1.nonce.0x1.stepLimit.0x12345.timestamp.0x563a6cf330136.to.cxb0776ee37f5b45bfaea8cff1d8232fbb6122ec32.version.0x3
Copied!

How to Create Transaction Signature

This section describes how to create a valid signature for a transaction using the private key associated with a specific wallet address.

Required data

Private key
The private key of the from address from which the transaction request was made.
Transaction hash
32 bytes (256-bit) hash data which is created by hashing the serialized transaction data using SHA3_256.

Create signature

The first step is making a serialized signature. Using the secp256k1 library, create a recoverable ECDSA signature of a transaction hash. This ensures that the transaction was originated from the private key owner, and the transaction message received by the recipient is not compromised. The resulting output should be 64 bytes serialized signature (R, S) with 1 byte recovery id (V).
The final step is to encode the generated signature as a Base64-encoded string.

Steps of How-to

How to Generate Transaction Signature in Python

Below is the example of creating a signature in python.
Here is a sample transaction request message. We will create a signature for the transaction data.
1
{
2
"jsonrpc": "2.0",
3
"method": "icx_sendTransaction",
4
"id": 1234,
5
"params": {
6
"version": "0x3",
7
"from": "hxbe258ceb872e08851f1f59694dac2558708ece11",
8
"to": "cxb0776ee37f5b45bfaea8cff1d8232fbb6122ec32",
9
"value": "0xde0b6b3a7640000",
10
"stepLimit": "0x12345",
11
"timestamp": "0x563a6cf330136",
12
"nid": "0x1"
13
}
14
}
Copied!
    step1: Serialize transaction data.
1
icx_sendTransaction.from.hxbe258ceb872e08851f1f59694dac2558708ece11.nid.0x1.stepLimit.0x12345.timestamp.0x563a6cf330136.to.cxb0776ee37f5b45bfaea8cff1d8232fbb6122ec32.value.0xde0b6b3a7640000.version.0x3
Copied!
    step2: Create a transaction signature.
      step2-1: Hash the serialized transaction data.
      step2-2: Create a recoverable ECDSA signature.
      step2-3: Encode the generated recoverable ECDSA signature as a Base64-encoded string.
1
import base64
2
import hashlib
3
import secp256k1
4
5
serialized_transaction = "icx_sendTransaction.from.hxbe258ceb872e08851f1f59694dac2558708ece11.nid.0x1.stepLimit.0x12345.timestamp.0x563a6cf330136.to.cxb0776ee37f5b45bfaea8cff1d8232fbb6122ec32.value.0xde0b6b3a7640000.version.0x3"
6
7
# transaction_hash
8
# result: b'z\xdc\xa3\[email protected]\x19{\xc0\xc5\xe3b\xc3I\x84&k\xeb\xbc\xd2\xda\xe2\xfd\x06\x08\x95TR[\x9b\xfc\xd0\xff'
9
msg_hash = hashlib.sha3_256(serialized_transaction.encode()).digest()
10
11
# prepare the private key (this private key is for example purpose)
12
private_key = b'\x870\x91*\xef\xedB\xac\x05\x8f\xd3\xf6\xfdvu8\x11\x04\xd49\xb3\xe1\x1f\x17\x1fTR\xd4\xf9\x19mL'
13
14
# create a private key object
15
private_key_object = secp256k1.PrivateKey(private_key)
16
17
# create a recoverable ECDSA signature
18
recoverable_signature = private_key_object.ecdsa_sign_recoverable(msg_hash, raw=True)
19
20
# convert the result from ecdsa_sign_recoverable to a tuple composed of 64 bytes and an integer denominated as recovery id.
21
signature, recovery_id = private_key_object.ecdsa_recoverable_serialize(recoverable_signature)
22
recoverable_sig = bytes(bytearray(signature) + recovery_id.to_bytes(1, 'big'))
23
24
# base64 encode
25
transaction_signature = base64.b64encode(recoverable_sig)
26
27
# transaction_signature: b'HNsFOK1qRkVKMB8ePZhKg/ELmT53MmnZn4ftt2sD69VdobB94BT0h52Bb8ven53186A9u+eIiIiWrSu8VjMUpwE='
Copied!

Summary

So far, we have learned about the rule of serializing transaction data, a process of converting from serialized transaction data to transaction signature and the list of libraries used to generate a signature. Be aware that you can not generate a valid transaction signature if you omit process or break any rule.
Last modified 4mo ago