[Caver] How to Update Klaytn Account Keys with Caver #2 — AccountKeyWeightedMultiSig

Tech at Klaytn
8 min readJul 1, 2021

--

See the list of articles here.

🇰🇷:[Caver] Caver로 Klaytn 계정의 키를 바꾸는 방법 #2 AccountKeyWeightedMultiSig
🇩🇪:[Caver] Klaytn-Kontoschlüssel Updaten mit Caver #2 AccountKeyWeightedMultiSig
🇫🇷: [Caver] Mettre à jour la clé de compte de Klaytn avec Caver #2 AccountKeyWeightedMultiSig

One of the special features of Klaytn is that it has decoupled keys from addresses, so that it’s possible to update your keys in the account. In this post, we will be explaining how to update account keys to AccountKeyWeightedMultiSig using caver-js and caver-java. For more details on the different types of AccountKey, please refer to Klaytn Docs.

This tutorial assumes that you have the necessary environment setup. If you haven’t yet set up the environment, please refer to caver-js — Prerequisites or caver-java — Prerequisites.

In each section, we will go through little snippets of the whole code; you can check the complete code in the links below:

1. Creating a keyring

First, create an Klaytn account to use for this tutorial. This account will be used for sending a transaction to make the update to the key stored on the network, so it must possess sufficient KLAY.

Caver uses a structure called Keyring to store private key(s) used in Klaytn addresses and accounts.

First, you can create a Keyring instance that stores Klaytn account information with caver-js like this:

// caver-js 
const senderKeyring = caver.wallet.keyring.create(senderAddress, senderPrivateKey)

If you have a keystore file instead of a private key string, you can create a keyring using caver.wallet.keyring.decrypt.

And you can create Keyring instance using caver-java as well:

// caver-java
SingleKeyring senderKeyring = caver.wallet.keyring.create(senderAddress, senderPrivateKey);

For caver-java, you can also create a keyring with caver.wallet.keyring.decrypt, if you have a keystore file.

2. Adding a keyring to caver in-memory wallet

In this exercise, we will be using an in-memory wallet. If you add a keyring to caver’s in-memory wallet, the key stored in that keyring will automatically be used to sign transactions even if you don’t designate a specific key.

This is how you add a keyring to in-memory wallet using caver-js:

// caver-js
caver.wallet.add(senderKeyring)

It is the same for caver-java:

// caver-java
caver.wallet.add(senderKeyring);

3. Creating new private keys

In order to update Klaytn account’s AccountKey to AccountKeyWeightedMultiSig, you need private keys for your Klaytn account. Here we will use randomly generated private keys via the generateMultipleKeys function. But if there are specific private keys that you want to use, that is fine.

Below is how you create multiple private key strings for your Klaytn account using caver-js:

// caver-js
const newKeys = caver.wallet.keyring.generateMultipleKeys(3)

You can also generate multiple private key strings using generateMultipleKeys with caver-java:

// caver-java
String[] newKeys = caver.wallet.keyring.generateMultipleKeys(3);

4. Creating a new keyring

Now that we have created new private keys, we will go on to create a Keyring instance to store them. You can start using the Keyring instance with the new keys once the AccountKey in the Klaytn account has been successfully updated.

You can create a Keyring instance that stores the new private keys using caver-js as shown below:

// caver-js
const newKeyring = caver.wallet.keyring.create(senderKeyring.address, newKeys)

The newKeyring that stores the new private keys will sign the transaction using newKeys.

Using caver-java, creating a Keyring instance for storing the new private keys looks like this:

// caver-java
MultipleKeyring newKeyring = caver.wallet.keyring.create(senderKeyring.getAddress(), newKeys);

5. Creating an Account instance

The Account class provided by caver contains the information required to update accounts. When making the update to AccountKeyWeightedMultiSig, you need the Klaytn account address that you want to update, as well as a new weighted multisig key. AccountKeyWeightedMultiSig stores the keys in the form of public keys and defines the threshold, as well as the weight of each public key.

You can create an Account instance by calling the toAccount function of the keyring that stores the newly created private keys. Here is what it looks like when you are using caver-js:

The threshold and the key’s weights of AccountKeyWeightedMultiSig are defined as below:

// caver-js
const account = newKeyring.toAccount({ threshold: 3, weights: [2, 1, 1] })

You can also create an Account instance by calling the keyring’s toAccount function with caver-java. The threshold and the key’s weights for AccountKeyWeightedMultiSig are defined using the WeightedMultiSigOptions class.

BigInteger[] weights = {BigInteger.valueOf(2), BigInteger.ONE, BigInteger.ONE};
WeightedMultiSigOptions options = new WeightedMultiSigOptions(BigInteger.valueOf(3), Arrays.asList(weights));
Account account = newKeyring.toAccount(options);

The Account instance will now store the Klaytn account address to be updated, as well as the keys (in the form of public keys) where the threshold and weights of each key are defined.

6. Creating a transaction

Once you have created an Account instance, you can use it to create an Account Update transaction.

Here is how you create a transaction using caver-js:

// caver-js
const accountUpdate = caver.transaction.accountUpdate.create({
from: senderKeyring.address,
account: account,
gas: 100000,
})

And here is how you do it using caver-java:

// caver-java
AccountUpdate accountUpdate = caver.transaction.accountUpdate.create(
TxPropertyBuilder.accountUpdate()
.setFrom(senderKeyring.getAddress())
.setAccount(account)
.setGas(BigInteger.valueOf(100000))
);

7. Signing the transaction

Once you have created an account update transaction, you need to sign it using the keyring that was added to the in-memory wallet. Caver’s in-memory wallet, caver.wallet, has a sign function, just for this purpose.

Here is how you sign a transaction using caver-js:

// caver-js
await caver.wallet.sign(senderKeyring.address, accountUpdate)

And for caver-java:

// caver-java
caver.wallet.sign(senderKeyring.getAddress(), accountUpdate);

If caver.wallet.sign has been successfully executed, you should see that the signature has been assigned to the signatures field of accountUpdate.

Now we are ready to send the transaction to the network.

8. Sending the transaction

Now that we have created and signed the transaction, let’s try sending it to the network. Once this transaction goes through on the network, the old key for your Klaytn account will no longer be usable. Since your old keyring will also no longer be usable, you have to use the new one that stores the new private key.

You can send the signed transaction to the network using caver.rpc.klay.sendRawTransaction.

Below is a sample that shows how to send a transaction with caver-js, using an EventEmitter:

// caver-js
caver.rpc.klay.sendRawTransaction(accountUpdate)
.on('transactionHash', hash => {
console.log(hash)
})
.on('receipt', receipt => {
console.log(receipt)
})

When you are sending a transaction using caver-js, you can get a receipt of the transaction using Promise with the code below:

// caver-js
const receipt = await caver.rpc.klay.sendRawTransaction(accountUpdate)

You can also send a transaction using caver-java like this:

// caver-java
Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(accountUpdate).send();
String txHash = sendResult.getResult();

When the above code is executed, you will get a transaction hash. The result of the transaction can be obtained with the code as shown below:

// caver-java
public String objectToString(Object value) throws JsonProcessingException {
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
return ow.writeValueAsString(value);
}
TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);
TransactionReceipt.TransactionReceiptData receiptData = receiptProcessor.waitForTransactionReceipt(txHash);System.out.println(objectToString(receiptData));

If the status in the receipt reads true, it means that the transaction has been successfully processed. Transaction type is TxTypeAccountUpdate and the updated AccountKeyWeightedMultiSigcountKeyPublic is returned in the key field in encoded form.

{
blockHash: ‘0xdf8b53b76fc1bc2dde099199026c48b7cae56724b752b1476ae2ab867598a1d5’,
blockNumber: ‘0x33c98c3’,
contractAddress: null,
from: ‘0xf3ecd45eeb2bb22d33d2d83dc2350d18691b5f80’,
gas: ‘0x186a0’,
gasPrice: ‘0x5d21dba00’,
gasUsed: ‘0x13c68’,
key: ‘0x04f86f03f86ce302a103d2df49e8d3b4da20b39a81f422c82c50e29a32b0a001b90d6de72a9cc76535f5e301a103b00854f13c6cb6cabab766c2c4b74bc568568a5600599be6ace18a789fc3b5dce301a1021968807b49413b84eea56d24bfe212166ccafdba2c1ae4d7c1e04a179f28dc5f’,
logs: [],
logsBloom: ‘0x00000…’,
nonce: ‘0x0’,
senderTxHash: ‘0x736f2a35cd4618f00ed4e6a76637b526b2b72e8fdc1a7dab35c3341a2c2027af’,
signatures: [
{ V: ‘0x7f5’, R: ‘0x89e33…’, S: ‘0x1c9a0…’ }
],
status: ‘0x1’,
transactionHash: ‘0x736f2a35cd4618f00ed4e6a76637b526b2b72e8fdc1a7dab35c3341a2c2027af’,
transactionIndex: ‘0x0’,
type: ‘TxTypeAccountUpdate’,
typeInt: 32
}

9. Confirming AccountKey

If the transaction was successful, the account key stored on the network should be updated. You can check the result using caver.rpc.klay.getAccountKey.

Confirm your new accountKey with caver-js like this:

// caver-js
const accountKey = await caver.rpc.klay.getAccountKey(senderKeyring.address)
console.log(accountKey)

Or with caver-java:

// caver-java
AccountKey accountKey = caver.rpc.klay.getAccountKey(senderKeyring.getAddress()).send();
System.out.println(objectToString(accountKey));

The code above will return the account keys stored on the Klaytn account. Since the key has been updated to AccountKeyWeightedMultiSig, the keyType, represented by the key type ID, is 4. For more details on Account Key Type IDs, please refer to Klaytn Docs.

{
keyType: 4,
key: {
threshold: 3,
keys: [
{
weight: 2,
key: { x: ‘0xd2df4…’, y: ‘0x813fe…’ }
},
{
weight: 1,
key: { x: ‘0xb0085…’, y: ‘0x478f5…’ }
},
{
weight: 1,
key: { x: ‘0x19688…’, y: ‘0x9d8b3…’ }
}
]
}
}

10. Updating in-memory wallet’s keyring

If your account key has been updated with the successfully processed transaction, you have to start using the updated key for signing transactions.

For now, the key that is used in the keyring, and stored in the in-memory wallet, is the old one before the update. Sending a transaction in this state will throw an error, as shown below:

Error: Returned error: invalid transaction v, r, s values of the sender

That is why you also have to update the keyring’s keys stored in the in-memory wallet after updating your Klaytn account key.

The private key used in keyring and stored in the in-memory wallet can be updated using caver.wallet.updateKeyring. The newKeyring that contains the updated Klaytn account address and new private key will be passed as parameter.

Below is how you can update in-memory wallet’s keyring using caver-js:

// caver-js
caver.wallet.updateKeyring(newKeyring)

And here is how you can do this using caver-java:

// caver-java
caver.wallet.updateKeyring(newKeyring);

11. Sending a transaction with the updated account

Now that your Klaytn account key has been updated, let’s try sending a transaction using the updated account.

Since the account key stored on the network has been updated, you have to sign the transaction using this new key. In section [10. Updating in-memory wallet’s keyring], we also updated the keys in the keyring in the in-memory wallet, so the transaction will be signed with the updated private keys.

Here we will just send a simple value transfer transaction to confirm the update.

Below is a sample that shows how to create, sign and send a value transfer transaction using caver-js:

// caver-js
const vt = caver.transaction.valueTransfer.create({
from: senderKeyring.address,
to: recipientAddress,
value: 1,
gas: 100000,
})

await caver.wallet.sign(senderKeyring.address, vt)

const vtReceipt = await caver.rpc.klay.sendRawTransaction(vt)

Create a transaction using caver.transaction.valueTransfer, and sign it using the caver.wallet.sign function as already demonstrated. Since the keyring in caver.wallet has been updated in [10. Updating in-memory wallet’s keyring], the transaction will be signed using the new private key. After that, the transaction will be sent to the network using caver.rpc.klay.sendRawTransaction. In this tutorial, the transaction result is returned using Promise.

Below is a sample of the same process, but for caver-java:

// caver-javaValueTransfer vt = caver.transaction.valueTransfer.create(
TxPropertyBuilder.valueTransfer()
.setFrom(sednerKeyring.getAddress())
.setTo(recipientAddress)
.setValue(BigInteger.valueOf(1))
.setGas(BigInteger.valueOf(100000))
);
caver.wallet.sign(senderKeyring.getAddress(), vt);Bytes32 vtResult = caver.rpc.klay.sendRawTransaction(vt).send();TransactionReceipt.TransactionReceiptData vtReceiptData = receiptProcessor.waitForTransactionReceipt(vtResult.getResult());

Create a ValueTransfer transaction and sign it using the caver.wallet.sign function. The transaction will be sent to the network with caver.rpc.klay.sendRawTransaction. You can confirm the transaction status using the returned transaction hash.

In this post, we have shown you how to update an account key to AccountKeyWeightedMultiSig. We hope you could follow each step without any problems. In our next post, we will be looking at how to update your key to AccountKeyRoleBased. The process is pretty much similar to this one, but only with a different key type. If you had no problems following this tutorial, the next one should also be a breeze!

If you have any questions or comments, leave them below or visit Klaytn Developers Forum.

Thank you for reading and stay tuned for more!

The complete code used in this tutorial:

--

--