[Caver] Caver로 Klaytn 계정의 키를 바꾸는 방법 #3 — AccountKeyRoleBased
전체 포스팅 목록은 여기에서 확인하세요.
English: How to Update Klaytn Account Keys with Caver #3 — AccountKeyRoleBased
- Caver로 Klaytn 계정의 키를 바꾸는 방법 #1 AccountKeyPublic
- Caver로 Klaytn 계정의 키를 바꾸는 방법 #2 AccountKeyWeightedMultiSig
- Caver로 Klaytn 계정의 키를 바꾸는 방법 #3 AccountKeyRoleBased
Klaytn의 특별한 점 중 하나는 키와 주소를 분리하여 계정에서 사용하는 키를 변경할 수 있다는 건데요. 이 포스팅에서는 caver-js와 caver-java를 사용하여 계정의 키를 AccountKeyRoleBased으로 변경하는 방법에 대해서 설명합니다. 다양한 AccountKey에 대한 설명은 Klaytn Docs를 참고하세요.
이 문서는 실습 환경을 전제로 설명이 진행됩니다. 실습 환경이 아직 구축되지 않았다면 caver-js — Prerequisites 혹은 caver-java — Prerequisites를 참고하세요.
각 파트에서는 코드의 일부분을 설명하며, 전체 코드는 아래 링크에서 확인할 수 있습니다.
1. Keyring 만들기
먼저 실습에서 사용될 Klaytn 계정을 생성합니다. 이 계정은 트랜잭션을 실제 전송하여 네트워크에 저장된 계정의 키를 업데이트하기 때문에 충분한 KLAY를 가지고 있어야 합니다.
Caver에서는 Klaytn 계정의 주소와 계정에서 사용하는 private key(s)를 저장하는 구조로 Keyring을 사용합니다.
먼저 caver-js를 사용하여 Klaytn 계정의 정보를 저장하는 Keyring 인스턴스 생성 방법에 대해서 설명합니다.
// caver-js
const senderKeyring = caver.wallet.keyring.create(senderAddress, senderPrivateKey)
만약 private key string이 아닌 keystore 파일을 가지고 있는 경우, caver.wallet.keyring.decrypt
를 사용하여 keyring을 생성할 수 있습니다.
caver-java를 사용하여 Klaytn 계정의 정보를 저장하는 Keyring 인스턴스 생성 방법은 아래와 같습니다.
// caver-java
SingleKeyring senderKeyring = caver.wallet.keyring.create(senderAddress, senderPrivateKey);
caver-java도 마찬가지로 keystore 파일을 가지고 있는 경우, caver.wallet.keyring.decrypt
를 사용하여 keyring을 생성할 수 있습니다.
2. Caver in-memory wallet에 keyring 추가하기
이 실습에서는 in-memory wallet을 사용합니다. Caver의 in-memory wallet에 keyring이 추가되어 있으면, 트랜잭션에 서명할 때 따로 사용할 키를 지정하지 않아도 in-memory wallet에 저장된 keyring의 키로 서명합니다.
caver-js를 사용하여 in-memory wallet에 keyring을 추가하는 방법은 아래와 같습니다.
// caver-js
caver.wallet.add(senderKeyring)
caver-java도 마찬가지로 아래와 같이 in-memory wallet에 keyring을 추가할 수 있습니다.
// caver-java
caver.wallet.add(senderKeyring);
3. 새로운 개인 키 생성하기
Klaytn 계정의 AccountKey를 AccountKeyRoleBased으로 업데이트하기 위해서는 Klaytn 계정에서 새롭게 사용할, 역할에 따라 정의된 여러 개의 private key가 필요합니다. 여기서는 generateRoleBasedKeys
함수를 통해 각 역할별로 랜덤 생성된 private keys를 사용합니다. 만약 따로 사용하고자 하는 역할 별로 정의된 private key들이 있는 경우, 이를 사용해도 됩니다. 각 역할에 대한 자세한 설명은 Klaytn Docs를 참고해 주세요.
아래는 caver-js를 사용하여 내 Klaytn 계정에서 사용할 역할 별로 정의된 여러 개의 새로운 private key string을 생성하는 방법입니다. 파라미터로 전송하는 배열에는 각 역할에서 사용할 키의 개수를 정의합니다. 아래 예제의 경우 RoleTransaction키로 2개의 private key를 생성하고, RoleAccountUpdate키는 1개, 마지막으로 RoleFeePayer 키로 3개의 private key를 생성합니다.
// caver-js
const newRoleBasedKeys = caver.wallet.keyring.generateRoleBasedKeys([2, 1, 3])
caver-java도 아래와 같이 generateRoleBasedKeys
를 사용하여 내 Klaytn 계정에서 사용할 역할 별로 정의된 여러 개의 새로운 private key string을 생성할 수 있습니다.
List<String[]> newRoleBasedKeys = caver.wallet.keyring.generateRolBasedKeys(new int[]{2, 1, 3});
4. 새로운 Keyring 생성하기
위에서 Klaytn 계정에서 사용할 새로운 private key를 다수 생성했다면, 이제 역할 별로 정의된 새로운 private keys를 저장하는 Keyring 인스턴스를 생성해보겠습니다. 역할 별로 정의된 새로운 private keys를 저장하는 Keyring 인스턴스는 Klaytn 계정의 AccountKey가 성공적으로 변경된 이후에 사용할 수 있습니다.
먼저 caver-js를 사용하여 아래와 같이 역할 별로 사용할 키가 정의된 Keyring 인스턴스를 생성할 수 있습니다.
// caver-js
const newKeyring = caver.wallet.keyring.create(senderKeyring.address, newRoleBasedKeys)
역할 별로 사용할 private keys를 저장하는 newKeyring
은 newRoleBasedKeys
를 사용하여 트랜잭션에 서명합니다.
caver-java를 사용하여 역할 별로 정의된 새로운 private keys를 저장하는 Keyring 인스턴스 생성 방법은 아래와 같습니다.
RoleBasedKeyring newKeyring = caver.wallet.keyring.create(senderKeyring.getAddress(), newRoleBasedKeys);
5. Account 인스턴스 만들기
Caver에서 제공하는 Account 클래스는 계정을 업데이트할 때 필요한 정보를 포함합니다. AccountKeyRoleBased
으로 업데이트하는 경우, 업데이트할 Klaytn 계정의 주소와 새롭게 사용하고자 하는 역할 별로 사용할 키가 정의된 role based key가 필요합니다. AccountKeyRoleBased
는 내부에 각 역할에 사용할 키를 public key의 형태로 저장하며, 한 역할에서 여러 키를 사용하는 경우 threshold와 각 public key의 weight를 정의할 수 있습니다.
위에서 생성한, 역할 별로 정의된 새로운 private keys를 저장하고 있는 keyring의 toAccount
함수를 호출하여 Account 인스턴스를 생성할수 있습니다. caver-js로 Account 인스턴스를 생성하는 방법은 아래와 같습니다.
// caver-js
const account = newKeyring.toAccount([{ threshold: 2, weights: [1, 1] }, {}, { threshold: 3, weights: [2, 1, 1] }])
업데이트 이후에 사용할 newKeyring
에서 RoleTransaction키로는 2개, RoleFeePayer키로는 3개를 사용하므로 각 역할에서 사용할 키들의 threshold와 weights를 정의할 수 있습니다. toAccount
함수를 호출할 때에 각 역할 별로 weighted multisig options이 정의된 오브젝트를 파라미터로 넘겨줍니다. 배열의 첫 번째 요소로는 RoleTransaction키에서 사용할 2개의 키에 대해서 threshold와 weights를 정의한 객체이며, 두 번째의 경우 RoleAccountUpdate
키에서는 1개의 키만 사용하므로 빈 오브젝트를 넘겨줍니다. 마지막 배열 요소의 경우 RoleFeePayer키에서 사용하는 3개의 키에 대해서 threshold와 weights를 정의한 객체입니다.
caver-java도 마찬가지로, 역할 별로 정의된 새로운 private keys를 저장하고 있는 keyring의 toAccount
함수를 호출하여 Account 인스턴스를 생성할 수 있습니다. AccountKeyRoleBased
의 threshold와 각 키의 weight는 WeightedMultiSigOptions
클래스를 사용하여 정의합니다.
// caver-java
BigInteger[][] optionWeight = {
{BigInteger.ONE, BigInteger.ONE},
{},
{BigInteger.valueOf(2), BigInteger.ONE, BigInteger.ONE},
};WeightedMultiSigOptions[] options = {
new WeightedMultiSigOptions(BigInteger.valueOf(2), Arrays.asList(optionWeight[0])),
new WeightedMultiSigOptions(),
new WeightedMultiSigOptions(BigInteger.valueOf(3), Arrays.asList(optionWeight[2])),
};Account account = newKeyring.toAccount(Arrays.asList(options));
caver-java도 마찬가지로 toAccount
함수에 각 역할별로 WeightedMultiSigOptions
가 정의된 배열을 파라미터로 넘겨주는 방식으로 Account 인스턴스를 생성할 수 있습니다.
이렇게 생성된 Account 인스턴스는 업데이트할 Klaytn 계정의 주소와 각 역할 별로 사용할 키들이 public key 형태로 저장되며 해당 키의 threshold와 weights가 정의된 키를 저장합니다.
6. 트랜잭션 만들기
Account 인스턴스를 생성했다면, 이를 사용하여 AccountUpdate
트랜잭션을 간단하게 생성할 수 있습니다.
먼저 caver-js를 사용하여 트랜잭션을 생성하는 방법은 아래와 같습니다.
// caver-js
const accountUpdate = caver.transaction.accountUpdate.create({
from: senderKeyring.address,
account: account,
gas: 150000,})
caver-java를 사용하여 트랜잭션을 생성하는 방법은 아래와 같습니다.
// caver-java
AccountUpdate accountUpdate = caver.transaction.accountUpdate.create(
TxPropertyBuilder.accountUpdate()
.setFrom(senderKeyring.getAddress())
.setAccount(account)
.setGas(BigInteger.valueOf(150000))
);
7. 트랜잭션 서명하기
AccountUpdate
트랜잭션을 생성했다면, in-memory wallet에 추가한 keyring을 사용하여 트랜잭션에 서명해야 합니다. Caver에서 제공하는 in-memory wallet인 caver.wallet
에서는 sign
함수를 제공합니다.
먼저 caver-js를 사용하여 트랜잭션을 서명하는 방법은 아래와 같습니다.
// caver-js
await caver.wallet.sign(senderKeyring.address, accountUpdate)
caver-java를 사용하여 트랜잭션에 서명하는 방법은 아래와 같습니다.
// caver-java
caver.wallet.sign(senderKeyring.getAddress(), accountUpdate);
caver.wallet.sign
이 성공적으로 수행되면 accountUpdate
의 signatures
필드에 생성된 서명이 할당된 것을 확인할 수 있습니다.
이제 생성된 트랜잭션을 네트워크로 전송하는 작업만 남아있습니다.
8. 트랜잭션 전송하기
트랜잭션을 생성하고 서명했다면 이를 실제 네트워크에 전송해 봅시다. 이 트랜잭션이 네트워크에서 처리되면 Klaytn 계정의 키가 변경되므로 기존 키는 더이상 사용할 수 없습니다. 그러므로 기존 키를 저장하고 있는 keyring은 사용할 수 없으며, 새로운 private key를 저장하고 있는 새로운 keyring을 사용해야 합니다.
caver.rpc.klay.sendRawTransaction
을 사용하여 서명된 트랜잭션을 네트워크에 전송할 수 있습니다.
아래는 caver-js를 사용하여 트랜잭션을 전송하는 예제로, Event Emitter를 사용합니다.
// caver-js
caver.rpc.klay.sendRawTransaction(accountUpdate)
.on(‘transactionHash’, hash => {
console.log(hash)
})
.on(‘receipt’, receipt => {
console.log(receipt)
})
caver-js를 사용하여 트랜잭션을 전송할 때, Promise를 사용하여 트랜잭션 처리 결과가 담긴 receipt을 받는 방법은 아래와 같습니다.
// caver-js
const receipt = await caver.rpc.klay.sendRawTransaction(accountUpdate)
아래는 caver-java를 사용하여 트랜잭션을 전송하는 예제입니다.
// caver-java
Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(accountUpdate).send();
String txHash = sendResult.getResult();
위 코드가 실행되면 트랜잭션 해시를 구할 수 있으며, 해당 트랜잭션의 처리 결과는 아래의 방법으로 얻을 수 있습니다.
// 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));
위의 결과로 리턴되는 receipt을 보면 아래와 같이 status가 true로 트랜잭션이 성공적으로 처리된 것을 확인할 수 있습니다. 트랜잭션 타입은 TxTypeAccountUpdate
이고 key필드에는 새롭게 업데이트한 AccountKeyRoleBased
이 인코딩된 형태로 리턴되었습니다.
{
blockHash: ‘0xe1a010ffef58727d47ed34d071a523c21a661f0c50a0074f8ec2bb8389bf7775’,
blockNumber: ‘0x33ca958’,
contractAddress: null,
from: ‘0x344de28e3e3089c3d7b9076f30dbbafcb329176f’,
gas: ‘0x249f0’,
gasPrice: ‘0x5d21dba00’,
gasUsed: ‘0x226c8’,
key: ‘0x05f8e8b84e04f84b01f848e301a102e2b8b818b0668e26651ed7fa199eccdc9b77e40775db80db6391244175ad6e5ee301a1033c79532bdd5b2c7265754df31f3665ed13a8556c24525a69cf0eedbbf5ff7ef1a302a102670a11eba2c17d92c01dfe272263db6c6d3eed5b1119401f9a62b7023cde6a6ab87204f86f01f86ce301a102104b2f85f43abc7b295cfd3ce91f6cb2c68e47c76d4bcc765b28c6ef2e0a86e8e301a1021190e282cf2a5066013784e08e29fb7821b3044a5f500059575bc003e926528be301a10289a57b9501c831e6b41537d3adbcaeca07ad5685d2963601b282def87ee619f2’,
logs: [],
logsBloom: ‘0x00000…’,
nonce: ‘0x0’,
senderTxHash: ‘0x826e5fbe3f33fd19c9a07b0f315eda2066ce2150b17656825e926374d32ef39a’,
signatures: [
{ V: ‘0x7f5’, R: ‘0xa952b…’, S: ‘0x936ad…’ }
],
status: ‘0x1’,
transactionHash: ‘0x826e5fbe3f33fd19c9a07b0f315eda2066ce2150b17656825e926374d32ef39a’,
transactionIndex: ‘0x0’,
type: ‘TxTypeAccountUpdate’,
typeInt: 32
}
9. AccountKey 확인하기
트랜잭션이 성공적으로 처리되었다면 네트워크에 저장된 계정의 키가 변경됩니다. caver.rpc.klay.getAccountKey
를 사용하여 이를 확인할 수 있습니다.
아래는 caver-js를 사용하여 계정의 accountKey를 확인하는 방법입니다.
// caver-js
const accountKey = await caver.rpc.klay.getAccountKey(senderKeyring.address)console.log(accountKey)
아래는 caver-java를 사용하여 계정의 accountKey를 확인하는 방법입니다.
// caver-java
AccountKey accountKey = caver.rpc.klay.getAccountKey(senderKeyring.getAddress()).send();System.out.println(objectToString(accountKey));
위의 결과를 출력하면 아래와 같이 Klaytn 네트워크에 저장된 계정의 키를 알 수 있습니다. 계정의 키를 AccountKeyRoleBased으로 업데이트했기 때문에 caver.rpc.klay.getAccountKey
결과의 keyType은 5로 출력되는 것을 확인할 수 있습니다. Account Key Type ID에 대한 자세한 내용은 Klaytn Docs를 참고해 주세요.
{
keyType: 5,
key: [
{
keyType: 4,
key: {
threshold: 1,
keys: [
{
weight: 1,
key: { x: ‘0xe2b8b…’, y: ‘0xc8004…’ }
},
{
weight: 1,
key: { x: ‘0x3c795…’, y: ‘0x18799…’ }
}
]
}
},
{
keyType: 2,
key: { x: ‘0x670a1…’, y: ‘0x1fe76…’ }
},
{
keyType: 4,
key: {
threshold: 1,
keys: [
{
weight: 1,
key: { x: ‘0x104b2…’, y: ‘0xa388d…’ }
},
{
weight: 1,
key: { x: ‘0x1190e…’, y: ‘0x3c23b…’ }
},
{
weight: 1,
key: { x: ‘0x89a57…’, y: ‘0xfedd0…’ }
}
]
}
}
]
}
10. In-memory wallet의 keyring 업데이트
트랜잭션이 성공적으로 처리되어 계정의 키가 업데이트 되었다면, 이제 트랜잭션에 서명할 때 업데이트된 키를 사용해야 합니다.
지금 in-memory wallet에 저장되어 있는, keyring에서 사용하는 키는 업데이트되기 전의 키이므로 트랜잭션을 전송하면 실패하게 됩니다. In-memory wallet의 keyring에서 사용하는 키가 업데이트되지 않은 상태로 트랜잭션을 전송하면 아래와 같은 에러가 발생하게 됩니다.
Error: Returned error: invalid transaction v, r, s values of the sender
그렇기 때문에 Klaytn 계정의 키를 업데이트했다면 in-memory wallet에 별도 저장된 keyring의 키도 업데이트해야 합니다.
caver.wallet.updateKeyring
를 사용하면 in-memory wallet에 저장된, keyring에서 사용하는 private key를 업데이트할 수 있습니다. 업데이트된 Klaytn 계정의 주소와 역할 별로 사용할 새로운 private keys를 담고있는 newKeyring
은 파라미터 형태로 전송합니다.
아래는 caver-js를 사용해서 in-memory wallet의 keyring을 업데이트하는 방법입니다.
// caver-js
caver.wallet.updateKeyring(newKeyring)
아래는 caver-java를 사용해서 in-memory wallet의 keyring을 업데이트하는 방법입니다.
// caver-java
caver.wallet.updateKeyring(newKeyring);
11. 업데이트된 계정으로 트랜잭션 전송하기
Klaytn 계정의 키가 정상적으로 업데이트되었다면, 이제 업데이트된 계정으로 트랜잭션을 전송해 볼까요?
네트워크에 저장되는 계정의 키가 업데이트되었기 때문에, 트랜잭션을 전송할 때는 업데이트된 키를 사용하여 서명해야 합니다. [10. In-memory wallet의 keyring 업데이트]에서 in-memory wallet에 있는 keyring의 키도 업데이트 했으므로, 트랜잭션을 전송하면 새롭게 업데이트된 키로 서명하여 전송됩니다.
여기서는 간단하게 AccountKey가 업데이트된 Klaytn 계정으로 Value Transfer 트랜잭션을 전송하는 예제에 대해서 설명합니다.
아래에는 caver-js를 사용하여 Value Transfer 트랜잭션을 생성하고 서명하여 전송하는 방법에 대해서 설명합니다.
// 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)
caver.transaction.valueTransfer
로 트랜잭션을 생성한 후, 앞에서 서명한 방식과 동일하게 caver.wallet.sign
함수를 사용하여 트랜잭션에 서명합니다. 이 때 caver.wallet
내부에 저장된 keyring은 [10. In-memory wallet의 keyring 업데이트]에서 업데이트되었으므로 역할 별로 정의된 새로운 private keys로 트랜잭션에 서명합니다. 이 예제에서는 ValueTransfer 트랜잭션에 sender로써 서명하는 경우이므로 RoleTransaction
키에 정의된 private keys를 사용하여 서명합니다. 그 이후 서명된 트랜잭션은 caver.rpc.klay.sendRawTransaction
을 사용하여 네트워크로 전송됩니다. 이 예제에서는 Promise를 사용하여 트랜잭션 처리 결과를 받도록 구현되어 있습니다.
그럼 이제 caver-java로 Value Transfer 트랜잭션을 생성하고 서명한 뒤 네트워크에 전송하는 방법에 대해서 설명해볼까요?
// caver-javaValueTransfer vt = caver.transaction.valueTransfer.create(
TxPropertyBuilder.valueTransfer()
.setFrom(senderKeyring.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());
ValueTransfer
트랜잭션을 생성한 후, 앞서 진행한 방식과 동일하게 caver.wallet.sign
함수로 트랜잭션에 서명합니다. 서명된 트랜잭션은 caver.rpc.klay.sendRawTransaction
을 사용하여 네트워크에 전송하고 리턴받은 트랜잭션 해시를 통하여 트랜잭션 처리 결과를 조회합니다.
이상으로 위의 실습을 통해 계정의 키를 AccountKeyRoleBased
으로 변경하는 방법을 알아보았습니다. 쉽게 이해하셨나요?
이 포스팅을 마지막으로 Klaytn 계정의 키를 업데이트하는 방법에 대해서 설명을 마칩니다. 다음 포스팅은 새로운 주제에 대해서 말씀드릴 예정입니다. 혹시 이 문서의 실습을 따라하는 데에 어려움이 있거나 질문이 있다면 Klaytn Forum에 글 남겨주시기 바랍니다.
포스팅에 사용된 전체 소스코드 링크