Caver-java role base key관련하여 문의드립니다

Klaytn DOC

https://medium.com/klaytn/caver-caver%EB%A1%9C-klaytn-%EA%B3%84%EC%A0%95%EC%9D%98-%ED%82%A4%EB%A5%BC-%EB%B0%94%EA%BE%B8%EB%8A%94-%EB%B0%A9%EB%B2%95-3-accountkeyrolebased-88c20b405f18

위 2개의 사이트를 참고해서 Fee Delegated Smart Contract Execution" 호출하는 스터디를 하고 있습니다.

tx, account에 privateKey1, feepayer에 privateKey2로 키링을 생성하고 "Fee Delegated Smart Contract Execution"을 호출하려고 할 때 privateKey1, privateKey2 둘다 있어야 호출이 되는건가요?

1 Like

안녕하세요, 주신 링크를 확인해보니 계정키와 관련된 문서인 것 같은데요
혹시 하고자 하는 작업이 어떤 것 일까요?

만약 FeeDelegatedSmartContractExecution 트랜잭션을 전송하고 싶은 경우에는 트랜잭션의 전송자인 from에 해당하는 private key와 트랜잭션 수수료를 대납하는 feePayer에 해당하는 private key가 모두 있어야 트랜잭션에 모두 서명하고 네트워크로 전송할 수 있습니다.
가지고 있는 private key로 키링을 생성하여 in-memory wallet에 추가한 뒤, caver.wallet.sign 그리고 caver.wallet.signAsFeePayer 함수를 사용하여 각각 트랜잭션 전송인, 그리고 수수료 대납자의 서명을 추가할 수 있습니다.

만약 사용하는 계정의 키를 변경하고자 하는 경우에는 위에 주신 링크를 바탕으로 계정키를 변경하여 사용하면 되지만, 단순히 FeeDelegatedSmartContractExecution 트랜잭션을 전송해보고 싶은 경우에는 계정키 업데이트가 필수는 아닙니다.
수수료대납 트랜잭션을 전송하는 방법은 이 문서에 나와있으며, 예제에서 사용하는 트랜잭션만 FeeDelegatedSmartContractExecution 타입으로 하시면 됩니다.

직접 트랜잭션을 생성하여 서명하고 전송하는 경우, 직접 트랜잭션의 input에 들어가는 값을 인코딩하는 작업이 필요한데요, caver-java에서는 스마트 컨트랙트를 쉽게 사용할 수 있도록 Contract 클래스를 제공합니다.
Contract 클래스를 사용하는 방법은 이 문서를 참고하면 되는데요, Contract 를 통하여 수수료 대납을 하는 경우에는 이 문서의 예제를 참고하시면 됩니다.

1 Like

@Jamie
답변감사합니다

알려주신 문서를 통해 Contract 클래스를 이용해서 FeeDelegatedSmartContractExecution은 성공한 상황이고
사용자가 privateKey를 노출 했을 경우 role base key를 사용해서 보안을 강화시키려는 목적으로 알아보았습니다.

1 Like

아, 네네 그럼 계정의 키를 AccountKeyRoleBased로 업데이트 하려면 AccountUpdate (혹은 FeeDelegatedAccountUpdate / FeeDelegatedAccountUpdateWithRatio)를 사용하면 됩니다.

계정의 키를 업데이트 하는 방법은 주신 테크블로그 참고하시면 됩니다.

수수료 대납이 아닐 경우(AccountUpdate)에는 트랜잭션 전송인의 private key만 있으면 되며, 수수료 대납(FeeDelegatedAccountUpdate / FeeDelegatedAccountUpdateWithRatio)의 경우에는 전송인과 수수료 대납자의 private key가 모두 필요합니다.

그리고 계정키를 업데이트한 이후에는 기존의 키는 사용할 수 없으니, 각 역할에 사용하는 새로운 private key(s)를 잘 저장해 두셔야 합니다.

1 Like

@Jamie

답변해주신 내용을 보면서 드는 궁금한게 생겼는데

String[][] privateKeyArr = {
                            {},     //tx
                            {},     //account
                            { feePayer EOA 지갑 PrivateKey }
                    };
List<String[]> expectedKeyList = Arrays.asList(privateKeyArr);
KeyringFactory.createWithRoleBasedKey(feePayer EOA 주소, expectedKeyList);

이렇게 생상한 keyring을 caver.wallet.add(feePayerKeyring);추가 후 Contract클래스의 "getMethod().call()"하여 트랜잭션 해시값을 받아 스코프에서 확인하면 정상적인 트랜잭션을 확인했습니다.
그리고 privateKey를 재사용해도 정상적인 트랜잭션으로 확인했는데

이거는 말씀해주신거하고 다른 내용인가요??

코드 상에 "createWithRoleBasedKey"이 메소드가 있어서 문의드렸습니다.

1 Like

좀 더 정확한 답변을 위해서는 다른 부분의 코드도 필요할 것 같은데요,

주신 코드는 수수료 대납을 할 때 feePayer EOA 지갑 PrivateKey 키를 사용하는 키링을 생성한 코드입니다.
KeyringFactory.createWithRoleBasedKey(feePayer EOA 주소, expectedKeyList); 으로 생성된 키링은 수수료대납용으로 사용할 키만 정의되어 있을 뿐, feePayer EOA 주소가 전송하는 일반적인 트랜잭션이나 계정키 업데이트 트랜잭션을 전송할 때 사용하는 키는 정의되어 있지 않은 키링입니다.

createWithRoleBasedKey함수는 caver에서 사용하는 인스턴스를 생성하는 함수이며, 네트워크에 아무런 트랜잭션을 전송하지 않습니다. (저 함수만 호출한다고 해서 계정의 키가 업데이트 되지 않습니다.)
그러므로 계정의 키를 업데이트 하기 위해서는 네트워크로 트랜잭션을 전송해야 합니다.
이 부분에 대한 설명은 미디엄 테크블로그에 다 나와있으므로 참고해 주세요.

업데이트를 하지 않은 경우 feePayer EOA 지갑 PrivateKey를 다른 역할(일반적인 트랜잭션 전송 혹은 계정키 업데이트 트랜잭션 전송)에도 사용이 가능합니다.
역할을 강제로 막기 위해서는 계정키를 무조건 업데이트 하셔야 합니다.
플로우는 아래에 간단히 써놨으며, 자세한 내용은 위 테크블로그 글 읽어보시기 바랍니다.

  1. 계정 A (Addr A - (Pub A - Prv A) ) → 트랜잭션전송 Prv A / 계정키업데이트 Prv A / 수수료대납 Prv A
  2. 키링 생성 및 caver.wallet에 추가
  3. caver.transaction.accountUpdate 트랜잭션 생성 (일반적인 트랜잭션 전송, 계정키 업데이트 트랜잭션 전송 그리고 수수료대납할 때 사용할 키를 각각 정의할 수 있음. 여기서는 설명을 위하여 트랜잭션전송 Prv B / 계정키업데이트 Prv C / 수수료대납 Prv D로 업데이트 한다고 가정. )
  4. caver.wallet.sign 서명 → 계정키업데이트 트랜잭션에 사용해야하는 Prv A를 사용해서 서명 (업데이트 되기 전이므로 아직 Prv A를 사용해야 함)
  5. 트랜잭션 전송 (caver.rpc.klay.sendRawTransaction)
  6. 트랜잭션 처리 성공 → 계정 A의 계정키가 (Addr A - [(Pub B - Prv B), (Pub C - Prv C) , (Pub D - Prv D) ]) 이렇게 변경됨. 더이상 Prv A는 사용할 수 없음.
  7. caver.wallet.updateKeyring을 사용하여 in-memory wallet에 저장된 키링을 변경된 키를 저장하고 있는 최신 키링 객체로 업데이트
1 Like

@Jamie

밑에 작성해주신 플로우를 읽었을 때 플로우는 이해한 것은
1~7까지 플로우를 수행하고 "sendRawTransaction"를 실행할때는 [(Pub B - Prv B), (Pub C - Prv C) , (Pub D - Prv D) ]를 서명키로 사용해야 하는것으로 이해했습니다.

그런데 윘부분에 “키링은 수수료대납용으로 사용할 키만 정의” 이 부분이 잘 이해가가질 않습니다.

1 Like

KeyringFactory.createWithRoleBasedKey는 키링의 주소와 각각 역할에 사용될 키를 파라미터로 받습니다.
네트워크에 저장되어 있는 계정키와는 별개로 caver에서 트랜잭션에 서명할 때 사용하는 키이며, 코드에 보시면 마지막 수수료대납할 때 사용할 키만 정의가 되어있기 때문에 생성되는 키링은 수수료 대납할 때 사용할 키만 가지고있는 키링이라는 의미입니다.

그리고 sendRawTransaction은 "이미 서명된 트랜잭션"를 네트워크에 전송하는 과정이며, 이 과정에서는 따로 서명이 필요하지 않습니다.
서명은 4번 과정에서 이미 다 마쳤으며, 계정키를 업데이트 하기 전이므로(트랜잭션이 실제 네트워크로 전송되지 않은 상태) 기존의 키인 Prv A로 4번에서 서명합니다.

그리고 sendRawTransaction으로 전송한 트랜잭션이 처리가 완료되면 실제로 네트워크에 저장되어 있는 계정키가 변경된 것이므로 이 이후로는 Prv B, Prv C, Prv D를 사용해야만 합니다. Prv A를 사용하는 경우 validation에서 실패합니다.

1 Like

@Jamie

답변감사합니다.

제가 구현한 방식과 조금 차이가 있는 것 같아서 상세하게 소스를 작성해서 질문을 드려야 했네요…
죄송합니다…

[CaverJavaSend.java]

Caver caver = setCaver(accessKey, secretAccessKey, chainId);
Contract tokenContract = CustomToken.create(caver, tokenContractAddress);

SingleKeyring senderKeyring = KeyringFactory.createFromPrivateKey(senderPrivateKey);
caver.wallet.add(senderKeyring);

AbstractKeyring feePayerKeyring = null;
if(feePayerExists){
    String feePayerWalletPrivateKey = RSATools.decrypt(feePayerWalletAddrPrivateKeyEnc, pair.getPrivate());

    String[][] privateKeyArr = {
            {},     //tx
            {},     //account
            { feePayerWalletPrivateKey }
    };

    List<String[]> expectedKeyList = Arrays.asList(privateKeyArr);
    feePayerKeyring = KeyringFactory.createWithRoleBasedKey(feePayerWallet, expectedKeyList);
    caver.wallet.add(feePayerKeyring);
}

SendOptions sendOptions = new SendOptions(senderKeyring.getAddress());
sendOptions.setGas(GAS_LIMIT);

if(feePayerExists){
    sendOptions.setFeeDelegation(true);
    sendOptions.setFeePayer(feePayerKeyring.getAddress());
}else{
    sendOptions.setFeeDelegation(false);
}

BigInteger blockStatus = tokenContract.customContractMethod(address);

[CustomToken.java]
List<Type> result = this.getMethod("CUSTOM_CONTRACT_METHOD_NAME").call(Arrays.asList(recipient));

위와 같이 컨트랙트클래스를 동적으로 구현해서 그 컨트랙트에 제가 배포한 메소드를 호출하는 방식으로 구현했습니다.
그 컨트랙트 클래스 내부에서 아래와 같이 호출할 때는 RoleBaseKey를 사용할 수 있을까요??

1 Like

코드만 봤을 때에는 feePayerWalletPrivateKey 계정의 키가 업데이트가 되지 않은 것 같은데요, 맞을까요?
컨트랙트 호출과는 별개로 어떠한 한 계정에서(여기서는 수수료대납 계정) 계정키를 AccountKeyRoleBased로 사용하려면 위에서 말씀드렸다시피 AccountUpdate 트랜잭션을 네트워크에 전송해야 합니다.

위의 코드는 단순하게 Caver에서 수수료대납 키링을 생성할 때 수수료대납에 사용하는 키만 정의해준 것으로 보이는데요, 이 경우에는 계정키는 업데이트 되지 않은 상태이므로 feePayerWalletPrivateKey로 수수료대납이 아닌 다른 트랜잭션들도 전송자로써의 서명이 가능합니다.

계정키가 AccountKeyRoleBased로 업데이트 되었다면 네트워크에서 트랜잭션을 검증할 때 수수료대납에 사용되어야 하는 키로 서명이 된 것인지 검증이 가능하게 됩니다.
미디엄 테크블로그에 나와있는 것처럼 계정키를 업데이트 하는 것이 올려주신 코드보다 먼저 선행되어야 합니다.

1 Like

@Jamie

네 계정업데이트 코드는 따로 작성하지 않았습니다.

말씀해주신걸 토대로 보면 현재 제가 작성한 소스는 AccountUpdate가 되지 않았고 feePayerWalletPrivateKey가 노출이 되면 다른 사람(다른 트랜잭션)들도 서명이 가능해서 배포한 토큰을 이체시킬 수도 있는 가능성이 있겠네요

AccountUpdate를 호출하면 기존의 feePayerWalletPrivateKey는 사용을 못하게 되고 성공할 결과값으로 서명을 해야한다는 거군요

감사합니다!

2 Likes

네네 맞습니다.

소스만 보자면 업데이트 되지 않았기 때문에 feePayerWalletPrivateKey로 토큰 이체하는 트랜잭션에 서명이 가능합니다. 말씀하신대로 이를 강제로 수수료 대납만 가능하게 하려면 AccountUpdate 트랜잭션을 전송해야 합니다.

해당 트랜잭션이 네트워크에서 처리가 완료되면 AccountUpdate 트랜잭션 정의된 각 역할 별로 사용할 키만 사용 가능하므로 기존의 키는 사용할 수 없게 됩니다.
감사합니다.

1 Like