블록체인
[Java] 이더리움 / 클레이튼 퍼스널 사인 검증하기 Verifying Ethereum / Klaytn signed messages
찐코딩
2022. 8. 17. 15:17
먼저 web3j 라이브러리를 추가해주어야 한다
gradle 기준
dependencies {
implementation group: 'org.web3j', name: 'core', version: '4.8.4'
}
버전은 여러가지가 있지만 나는 그냥 사용자가 제일 많은 기준으로~
이더리움
import java.math.BigInteger;
import java.util.Arrays;
import org.web3j.crypto.ECDSASignature;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Sign;
import org.web3j.crypto.Sign.SignatureData;
import org.web3j.utils.Numeric;
public class EthECRecoverTest {
/**
* Ethereum에서 사용자 정의한 모든 서명 메시지는 다음 문자로 시작합니다.
* Reference resources eth_sign in https://github.com/ethereum/wiki/wiki/JSON-RPC
*/
private static final String PERSONAL_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n";
/**
* For signed messages , Original news , The account number and address are authenticated , Judge whether the signature is valid
* @param signature
* @param message
* @param address
* @return
*/
private boolean testRecoverAddressFromSignature(String signature, String message, String address) {
// Reference resources eth_sign in https://github.com/ethereum/wiki/wiki/JSON-RPC
// The sign method calculates an Ethereum specific signature with:
// sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))).
//
// 메시지에 접두사를 추가하면 계산된 서명이 이더리움 특정 서명으로 인식될 수 있습니다.
// 이것은 악의적인 dapp이 임의의 데이터(예: 트랜잭션)에 서명하고 서명을 사용하여 피해자를 가장할 수 있는 오용을 방지합니다.
String prefix = PERSONAL_MESSAGE_PREFIX + message.length();
byte[] msgHash = Hash.sha3((prefix + message).getBytes());
byte[] signatureBytes = Numeric.hexStringToByteArray(signature);
byte v = signatureBytes[64];
if (v < 27) {
v += 27;
}
SignatureData sd =
new SignatureData(
v,
(byte[]) Arrays.copyOfRange(signatureBytes, 0, 32),
(byte[]) Arrays.copyOfRange(signatureBytes, 32, 64));
String addressRecovered = null;
boolean match = false;
BigInteger publicKey = null;
String pubKey = null;
// Iterate for each possible key to recover
for (int i = 0; i < 4; i++) {
publicKey = Sign.recoverFromSignature(
(byte) i,
new ECDSASignature(new BigInteger(1, sd.getR()), new BigInteger(1, sd.getS())),
msgHash);
if (publicKey != null) {
addressRecovered = "0x" + Keys.getAddress(publicKey);
if (addressRecovered.equals(address)) {
match = true;
break;
}
}
}
pubKey = "0x"+publicKey.toString(16);
System.out.println("지갑 주소 : " + addressRecovered);
System.out.println("publicKey : " + pubKey);
return match;
}
public static void main(String[] args) {
EthECRecoverTest EthECRecoverTest = new EthECRecoverTest();
String hashResult = "{사인 결과값. 0x 부터 입력}"; // sign 결과 해쉬값 입력
String msg = "Example `personal_sign` message"; // 사인 메세지
String address = "{지갑 주소 0x부터 입력}"; // 지갑 주소입력
EthECRecoverTest.testRecoverAddressFromSignature(hashResult, msg, address);
}
}
결과
지갑 주소 : {지갑주소}
publicKey : {퍼블릭 키 해쉬값}
+추가) 클레이튼
import java.math.BigInteger;
import java.util.Arrays;
import org.web3j.crypto.ECDSASignature;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Sign;
import org.web3j.crypto.Sign.SignatureData;
import org.web3j.utils.Numeric;
public class KlaytnECRecoverTest {
/**
* Klaytn에서 사용자 정의한 모든 서명 메시지는 다음 문자로 시작합니다.
* https://ko.docs.klaytn.foundation/dapp/sdk/caver-js/v1.4.1/api-references/caver.klay.accounts
*/
private static final String PERSONAL_MESSAGE_PREFIX = "\u0019Klaytn Signed Message:\n";
/**
* For signed messages , Original news , The account number and address are authenticated , Judge whether the signature is valid
* @param signature
* @param message
* @param address
* @return
*/
private boolean testRecoverAddressFromSignature(String signature, String message, String address) {
String prefix = PERSONAL_MESSAGE_PREFIX + message.length();
byte[] msgHash = Hash.sha3((prefix + message).getBytes());
byte[] signatureBytes = Numeric.hexStringToByteArray(signature);
byte v = signatureBytes[64];
if (v < 27) {
v += 27;
}
SignatureData sd =
new SignatureData(
Arrays.copyOfRange(signatureBytes, 64, 65),
Arrays.copyOfRange(signatureBytes, 0, 32),
Arrays.copyOfRange(signatureBytes, 32, 64));
String addressRecovered = null;
boolean match = false;
BigInteger publicKey = null;
String pubKey = null;
// Iterate for each possible key to recover
for (int i = 0; i < 4; i++) {
publicKey = Sign.recoverFromSignature(
(byte) i,
new ECDSASignature(new BigInteger(1, sd.getR()), new BigInteger(1, sd.getS())),
msgHash);
if (publicKey != null) {
addressRecovered = "0x" + Keys.getAddress(publicKey);
if (addressRecovered.equals(address)) {
match = true;
break;
}
}
}
pubKey = "0x"+publicKey.toString(16);
System.out.println("지갑 주소 : " + addressRecovered);
System.out.println("publicKey : " + pubKey);
return match;
}
public static void main(String[] args) {
KlaytnECRecoverTest KlaytnECRecoverTest = new KlaytnECRecoverTest();
String hashResult = "{사인 결과값. 0x 부터 입력}"; // sign 결과 해쉬값 입력
String msg = "Example `personal_sign` message"; // 사인 메세지
String address = "{지갑 주소 0x부터 입력}"; // 지갑 주소입력
KlaytnECRecoverTest.testRecoverAddressFromSignature(hashResult, msg, address);
}
}
타원곡선 알고리즘(ECDSA)에 의해 디코딩하여 검증한다!
참고
https://ethereum.stackexchange.com/questions/55454/recover-address-from-signature-using-web3j
https://javamana.com/2022/01/202201201454093896.html