- 네트워크: BSC Testnet
- 체인 ID: 97
---
1. 지갑 주소 생성 (Private Key, Public Key)
1.1 Private Key로 Public Key 추출
1.2 생성된 주소 메타마스크에 등록해보기
1.3 Private Key로 서명 생성
Web3j로 개인키 서명(Signature) 생성 및 검증하기
이더리움 지갑의 개인키를 이용해 디지털 서명(Digital Signature) 을 생성하고, 공개키를 복구하여 검증하는 과정은 블록체인 트랜잭션이 동작하는 기본 원리이다.
디지털 서명이란?
디지털 서명은 메시지의 무결성과 서명자의 신원을 보장하기 위한 기술이다.
이더리움에서는 ECDSA(Elliptic Curve Digital Signature Algorithm) 방식을 사용한다.
서명은 크게 세 가지 역할을 수행한다.
메시지 인증 | 메시지를 보낸 사람이 실제 서명자임을 증명한다. |
무결성 보장 | 전송 중 데이터가 변조되지 않았음을 보장한다. |
부인 방지 | 서명자는 본인이 보낸 메시지를 부인할 수 없다. |
코드 구조
예제 파일: PrivateKeySignGenerate.java
package wallet;
import java.math.BigInteger;
import java.security.SignatureException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Sign;
import org.web3j.crypto.Sign.SignatureData;
import org.web3j.utils.Numeric;
public class PrivateKeySignGenerate {
/**
* 1.3 Private Key로 서명 생성
*
* 디지털 서명의 목적:
* - 메시지 인증: 서명자가 실제로 메시지를 작성했음을 증명
* - 무결성 보장: 메시지가 변조되지 않았음을 보장
* - 부인 방지: 서명자가 서명 사실을 부인할 수 없음
*/
public static void main(String[] args) throws SignatureException {
String privateKey = System.getenv("PRIVATE_KEY");
// Private key로 서명을 위한 Credentials 생성
Credentials credentials = Credentials.create(privateKey);
System.out.println("서명자 주소: " + credentials.getAddress());
// 메시지 서명 (ECDSA 알고리즘 사용)
String message = "This message is for test.";
System.out.println("원본 메시지: " + message);
SignatureData signatureData = Sign.signMessage(message.getBytes(), credentials.getEcKeyPair());
System.out.println("=== 서명 구성 요소 ===");
System.out.println("r (32바이트) : " + Numeric.toHexString(signatureData.getR()));
// r : 서명의 첫 번째 구성 요소 (타원곡선 점의 x좌표)
System.out.println("s (32바이트) : " + Numeric.toHexString(signatureData.getS()));
// s : 서명의 두 번째 구성 요소 (스칼라 값)
System.out.println("v (1바이트) - 10진수: " + signatureData.getV()[0]); // 10진수
System.out.println("v (1바이트) - 16진수 : " + Numeric.toHexString(signatureData.getV())); // 16진수
// v : 복구 ID (서명에서 사용된 공개키를 복구하는 데 사용되는 값)
// 16진수 문자열로 서명 얻기 (65바이트)
byte[] signatureByteArr = new byte[65];
System.arraycopy(signatureData.getR(), 0, signatureByteArr, 0, 32);
System.arraycopy(signatureData.getS(), 0, signatureByteArr, 32, 32);
System.arraycopy(signatureData.getV(), 0, signatureByteArr, 64, 1);
String signature = Numeric.toHexString(signatureByteArr);
System.out.println("signature = " + signature);
// 서명자의 공개키를 복구하여 주소로 변환하기
// (서명 -> 공개키 복구 가능, 공개키 -> 주소 추출 가능)
BigInteger publicKeyBigInt = Sign.signedMessageToKey(message.getBytes(), signatureData);
// Keys.getAddress() 메서드는 0x 접두사 없이 반환
String address = Numeric.prependHexPrefix(Keys.getAddress(publicKeyBigInt));
System.out.println("\n=== 서명 검증 ===");
System.out.println("복구된 주소: " + address);
System.out.println("원본 주소: " + credentials.getAddress());
// 서명자의 주소가 복구된 주소와 일치하는지 확인
if (address.equals(credentials.getAddress())) {
System.out.println("✓ 서명 검증 성공: 주소가 일치합니다.");
} else {
System.out.println("✗ 서명 검증 실패: 주소가 일치하지 않습니다.");
}
}
}
/*
* 디지털 서명 과정 요약:
* 1. 메시지 + 개인키 → ECDSA 서명 생성 (r, s, v)
* 2. 서명 + 메시지 → 공개키 복구
* 3. 공개키 → 주소 추출
* 4. 추출된 주소와 원본 주소 비교로 검증
*
* 블록체인에서의 활용:
* - 트랜잭션 서명: 자산 전송 시 소유권 증명
* - 메시지 서명: 신원 인증 및 데이터 무결성 보장
* - 스마트 컨트랙트: 함수 호출 권한 검증
*/
- 메인 메서드 호출 결과
이 코드는 아래와 같은 순서로 동작한다.
- 개인키로 Credentials 생성
- 임의의 메시지에 서명(Sign)
- 서명 구성요소(r, s, v) 출력
- 서명으로부터 공개키 복구
- 복구된 주소와 원본 주소 비교
실행 코드 설명
① 개인키로 Credentials 생성
String privateKey = System.getenv("PRIVATE_KEY");
Credentials credentials = Credentials.create(privateKey);
System.out.println("서명자 주소: " + credentials.getAddress());
- 환경변수(PRIVATE_KEY)에서 개인키를 읽는다.
- Web3j의 Credentials.create()는 개인키 기반의 지갑 객체를 생성한다.
- 이 객체는 서명·트랜잭션 서명 등 모든 키 관련 연산을 담당한다.
② 메시지 서명 생성
String message = "This message is for test.";
SignatureData signatureData = Sign.signMessage(message.getBytes(), credentials.getEcKeyPair());
- Sign.signMessage() 메서드는 개인키로 메시지를 ECDSA 서명한다.
- 결과로 서명 데이터(SignatureData) 를 반환한다.
서명은 세 가지 값으로 구성된다.
구성요소 설명
r | 서명 타원곡선 점의 x좌표 (32바이트) |
s | 서명의 스칼라 값 (32바이트) |
v | 복구 ID (1바이트, 공개키 복원용) |
System.out.println("r : " + Numeric.toHexString(signatureData.getR()));
System.out.println("s : " + Numeric.toHexString(signatureData.getS()));
System.out.println("v : " + signatureData.getV()[0]);
③ 최종 서명 문자열 생성
r(32바이트) + s(32바이트) + v(1바이트) = 총 65바이트 서명이다.
byte[] signatureByteArr = new byte[65];
System.arraycopy(signatureData.getR(), 0, signatureByteArr, 0, 32);
System.arraycopy(signatureData.getS(), 0, signatureByteArr, 32, 32);
System.arraycopy(signatureData.getV(), 0, signatureByteArr, 64, 1);
String signature = Numeric.toHexString(signatureByteArr);
System.out.println("signature = " + signature);
④ 공개키 복구 및 검증
이더리움의 서명은 공개키를 복원할 수 있도록 설계되어 있다.
즉, “서명 + 메시지”로 누가 서명했는지를 역추적할 수 있다.
BigInteger publicKeyBigInt = Sign.signedMessageToKey(message.getBytes(), signatureData);
String address = Numeric.prependHexPrefix(Keys.getAddress(publicKeyBigInt));
System.out.println("복구된 주소: " + address);
System.out.println("원본 주소: " + credentials.getAddress());
⑤ 검증 결과
if (address.equals(credentials.getAddress())) {
System.out.println("✓ 서명 검증 성공: 주소가 일치합니다.");
} else {
System.out.println("✗ 서명 검증 실패: 주소가 일치하지 않습니다.");
}
- 복구된 주소가 서명자의 주소와 일치하면 서명 검증 성공이다.
- 즉, 이 메시지는 해당 개인키 소유자가 서명했음을 증명한다.
블록체인에서의 활용
이 디지털 서명 과정은 블록체인에서 거의 모든 곳에 사용된다.
트랜잭션 서명 | 송금 또는 스마트 컨트랙트 호출 시 지갑 소유권 증명 |
스마트 컨트랙트 호출 권한 검증 | 특정 함수 호출이 서명된 사용자만 가능하도록 제한 |
메시지 인증 | 블록체인 외부 서비스에서도 데이터 무결성 보장 |
개인키는 '서명', 공개키는 '검증'에 사용된다.
이 두 과정이 블록체인의 신뢰 없는 거래(Trustless Transaction) 를 가능하게 하는 핵심이다.
'Blockchain' 카테고리의 다른 글
BSC Testnet Web3j 스터디 (5) 단위 변환 (wei, gwei, eth) (0) | 2025.10.12 |
---|---|
BSC Testnet Web3j 스터디 (3) 생성된 주소 메타마스크에 등록해보기 (1) | 2025.10.09 |
BSC Testnet Web3j 스터디 (2) Private Key로 Public Key 추출 (0) | 2025.09.13 |
BSC Testnet Web3j 스터디 (1) 지갑 주소 생성 (Private Key, Public Key) (0) | 2025.08.31 |
블록체인의 이해 (1) | 2025.08.24 |