Using nodejs
Generating private keys, public keys and addresses
To generate a private key we are going to use the randomBytes
(opens in a new tab) method of the crypto
(opens in a new tab) package in nodejs
to have a reasonable source of randomness and then use the secp256k1
(opens in a new tab) package to verify the validity of the private key.
Open the main.js
file and add the following.
const { randomBytes } = require("crypto");
const secp256k1 = require("secp256k1");
function makePrivateKey() {
let pk;
do {
pk = randomBytes(32);
} while (!secp256k1.privateKeyVerify(pk));
return pk;
}
const privateKey = makePrivateKey();
console.log(`Private Key: ${privateKey.toString("hex")}`);
To generate the public key we will use the publicKeyCreate
method of the secp256k1
package, this will return a buffer
(opens in a new tab) object that we are later converting into a hexadecimal string.
const secp256k1 = require("secp256k1");
// sample private key
const pk = "1111111111111111111111111111111111111111111111111111111111111111";
function makePublicKey(pk) {
// slice(1) removes the 'type byte' which is hardcoded as '04'
const publicKey = secp256k1.publicKeyCreate(pk, false).slice(1);
return publicKey;
}
function getPublicKey(pubK) {
let pubKHex = null;
try {
pubKHex = Buffer.from(pubK).toString("hex");
} catch (err) {
console.log("Unexpected error parsing public key into hex");
console.log(err);
}
return pubKHex;
}
function privateKeyAsBuffer(pk) {
let pkBuffer = null;
try {
pkBuffer = Buffer.from(pk, "hex");
} catch (err) {
console.log("Unexpected error parsing private key into buffer");
}
return pkBuffer;
}
const pkAsBuffer = privateKeyAsBuffer(pk);
const pubKBuffer = makePublicKey(pkAsBuffer);
const pubKHex = getPublicKey(pubKBuffer);
console.log(`Public Key: ${pubKHex}`);
To generate the wallet address we convert the hexadecimal string of the public key into a Buffer object and then apply the SHA3-256 hashing algorithm to it, we then take the last 40 characters of the resulting hexadecimal string and add “hx” to the beginning.
const sha3_256 = require("js-sha3").sha3_256;
const pubK =
"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1";
function getAddress(pubK) {
let address = null;
try {
address = "hx" + sha3_256(pubK).slice(-40);
} catch (err) {
console.log("Unexpected error parsing address");
console.log(err);
}
return address;
}
function hexKeyToBuffer(key) {
let keyAsBuffer = null;
try {
keyAsBuffer = Buffer.from(key, "hex");
} catch (err) {
console.log("Unexpected error parsing key into buffer");
console.log(err);
}
return keyAsBuffer;
}
const pubKAsBuffer = hexKeyToBuffer(pubK);
const address = getAddress(pubKAsBuffer);
console.log(`Address: ${address}`);
Generating a signature and sending an RPC JSON request
To generate a signature is necessary to execute the following steps:
- Serialized transaction data
- Create a transaction signature
- Hash the serialized transaction data
- Create a recoverable ECDSA signature
- Encode the generated recoverable ECDSA signature as a Base64-encoded string
- Add the signature to the RPC JSON request as a param.
Transaction data is serialized by concatenating key, value pairs in params
with .
as a delimiter. Adding the method name, icx_sendTransaction
, to the serialized string as a prefix completes the serialization process.
For a more detailed explanation of how to serialize transaction data you can visit the following tutorial.
An example of a serialized transaction would be the following:
“icx_sendTransaction.” +
“from.hx396031be52ec56955bd7bf15eacdfa1a1c1fe19e.” +
“nid.0x2.” +
“nonce.0x1.” +
“stepLimit.0x1e8480.” +
“timestamp.0x5f569aaf7c100.” +
“to.hx81765cee88107ca5d3de480d25778ec71d8ae65f.” +
“value.0xde0b6b3a7640000.” +
“version.0x3”
To generate the signature we hash the serialized data with the sha3-256
algorithm, convert that data into a Buffer and apply the ecdaSign
method of the secp256k1
library which will return a value of type Buffer. This data is then converted into a base64
string to have our signature.
const secp256k1 = require("secp256k1");
const sha3_256 = require("js-sha3").sha3_256;
function sign(serializedData, pk) {
const serializedHash = sha3_256(serializedData);
const msgAsBuffer = Buffer.from(serializedHash, "hex");
const signing = secp256k1.ecdsaSign(msgAsBuffer, pk);
const recovery = new Uint8Array(1);
recovery[0] = signing.recid;
return concatTypedArrays(signing.signature, recovery);
}
function concatTypedArrays(a, b) {
const c = new a.constructor(a.length + b.length);
c.set(a, 0);
c.set(b, a.length);
return c;
}
function hexKeyToBuffer(key) {
let keyAsBuffer = null;
try {
keyAsBuffer = Buffer.from(key, "hex");
} catch (err) {
console.log("Unexpected error parsing key into buffer");
console.log(err);
}
return keyAsBuffer;
}
const pk = "1111111111111111111111111111111111111111111111111111111111111111";
const serialized = "icx_sendTransaction.from.hx396031be52ec56955bd7bf15eacdfa1a1c1fe19e.nid.0x2.nonce.0x1.stepLimit.0x1e8480.timestamp.0x5f569aaf7c100.to.hx81765cee88107ca5d3de480d25778ec71d8ae65f.value.0xde0b6b3a7640000.version.0x3";
const PK1AsBuffer = hexKeyToBuffer(pk);
const signaturePk1 = sign(serialized, PK1AsBuffer);
const signatureAsBase64 = Buffer.from(signaturePk1).toString("base64");
console.log(signatureAsBase64);