본문 바로가기

Blockchain

BSC Testnet Web3j 스터디 (4) Private Key로 서명 생성

- 네트워크: 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. 추출된 주소와 원본 주소 비교로 검증
         * 
         * 블록체인에서의 활용:
         * - 트랜잭션 서명: 자산 전송 시 소유권 증명
         * - 메시지 서명: 신원 인증 및 데이터 무결성 보장
         * - 스마트 컨트랙트: 함수 호출 권한 검증
         */

 

  • 메인 메서드 호출 결과

 

 

이 코드는 아래와 같은 순서로 동작한다.

  1. 개인키로 Credentials 생성
  2. 임의의 메시지에 서명(Sign)
  3. 서명 구성요소(r, s, v) 출력
  4. 서명으로부터 공개키 복구
  5. 복구된 주소와 원본 주소 비교

실행 코드 설명

① 개인키로 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) 를 가능하게 하는 핵심이다.