본문 바로가기

Blockchain

BSC Testnet Web3j 스터디 (1) 지갑 주소 생성 (Private Key, Public Key)

회사에서 온보딩으로 진행한 Web3j 학습 내용을 정리한 글입니다. 

- 네트워크: BSC Testnet
- 체인 ID: 97

---

1. 지갑 주소 생성 (Private Key, Public Key)

 

이더리움 지갑을 만든다고 하면 흔히 메타마스크 같은 지갑 앱을 떠올리지만, 사실 지갑의 핵심은 단순하다.
지갑은 결국 Private Key, Public Key, Address 이 세 가지 값으로 정의된다. 

자바에서는 web3j 라이브러리를 사용하여 이를 코드로 구현할 수 있다. 


지갑 생성 과정

이더리움 지갑은 기본적으로 다음 과정을 거쳐 만들어진다.

  1. 키 쌍 생성
    • 타원곡선 암호화(SECP-256k1)를 사용해 개인키와 공개키를 생성한다.
    • 개인키는 서명에 사용되고, 공개키는 이를 검증하거나 주소를 만들 때 사용된다.
  2. 주소 도출
    • 공개키를 Keccak-256 해시 함수에 넣는다.
    • 나온 값에서 마지막 20바이트를 추출하고, 앞에 0x를 붙이면 이더리움 주소가 된다.
  3. Credentials 객체 생성
    • web3j는 Credentials라는 객체로 개인키, 공개키, 주소를 한꺼번에 관리한다.
    • 이 객체 하나만 있으면 거래 서명, 잔액 조회 등 다양한 작업을 할 수 있다.

코드 예제

ECKeyPair ecKeyPair = Keys.createEcKeyPair(); // 1. 키 쌍 생성
Credentials credentials = Credentials.create(ecKeyPair); // 2. Credentials 생성

String walletAddress = credentials.getAddress(); // 3. 주소 도출

System.out.println("지갑 주소: " + walletAddress);
System.out.println("Private Key: " + Numeric.toHexStringWithPrefix(ecKeyPair.getPrivateKey()));
System.out.println("Public Key: " + Numeric.toHexStringWithPrefix(ecKeyPair.getPublicKey()));

출력 결과 예시

지갑 주소: 0x658b8a1ae242d0460d4777e17c9af438daab4f77
Private Key: 0x5a1b...
Public Key: 0x376db746fb37...

코드를 실행하면 다음과 같은 값들이 출력된다.

  • 지갑 주소 (Ethereum Address)
    0x 접두사가 붙은 20바이트(40자리 16진수) 주소이다.
    예: 0x658b8a1ae242d0460d4777e17c9af438daab4f77
  • Private Key
    32바이트의 개인키이다. 절대 외부에 공개하면 안 된다.
    개인키를 통해 언제든 동일한 공개키와 주소를 재생성할 수 있다.
  • Public Key
    64바이트의 공개키이다. 주소 생성에 사용된다.

매 실행마다 서로 다른 값이 생성되며, 이는 암호학적 난수를 기반으로 한다.


주요 특징

  • 이더리움 지갑은 데이터베이스에 저장된 것이 아니라, 수학적으로 생성되는 키 쌍이다.
  • 개인키만 있으면 언제든 같은 지갑을 복원할 수 있다.
  • 지갑 주소는 단순히 공개키에서 파생된 값이다.

 

중요한 점

  1. 개인키 관리가 곧 지갑 관리이다
    지갑 파일이나 주소 자체보다 중요한 것은 개인키이다. 개인키만 있으면 같은 지갑을 언제든지 복원할 수 있다.
  2. web3j가 내부 과정을 대신 처리한다
    개발자는 복잡한 암호학 과정을 직접 구현할 필요가 없다.
    Keys.createEcKeyPair()와 Credentials.create()만 호출하면 지갑 생성이 끝난다.
  3. 매번 실행할 때마다 새로운 지갑이 생성된다
    이 과정은 암호학적으로 안전한 난수를 기반으로 하기 때문에, 실행할 때마다 전혀 다른 지갑 주소가 나온다.

정리

  • 이더리움 지갑은 결국 개인키, 공개키, 주소 세 가지 요소로 구성된다.
  • web3j를 사용하면 이 모든 과정을 단 몇 줄의 코드로 구현할 수 있다.
  • 실무에서 가장 중요한 것은 개인키 보관이며, 주소는 공개되어도 상관없다.

 

 

 

package wallet;

import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Keys;
import org.web3j.utils.Numeric;

public class WalletGenerate {

    /**
     * 1. 지갑 주소 생성 (Private Key, Public Key)
     * 
     * 지갑 생성 과정:
     * 1. ECDSA 키페어 생성 (Private Key + Public Key)
     * 2. Public Key에서 Keccak-256 해시로 주소 도출
     * 3. 0x 접두사 + 20바이트 주소 완성
     */

    public static void main(String[] args)
        throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {

        // [[1. 지갑 주소 셍성 (Private Key, Public Key)]]

        System.out.println("=== 이더리움 지갑 생성 시작 ===");

        // SECP-256k1 타원곡선 암호화로 키페어 생성
        // 비트코인과 동일한 암호화 알고리즘 사용
        ECKeyPair ecKeyPair = Keys.createEcKeyPair();

        // Public Key에서 Keccak-256 해시로 주소 생성
        // 0x + 20바이트(40자리 16진수) 형태
        String walletAddress = Credentials.create(ecKeyPair).getAddress();

        System.out.println("생성된 지갑 주소: " + walletAddress);
        System.out.println("=== 지갑 주소 생성 완료 ===");

        System.out.println("\n=== 키 정보 출력 ===");

        // BigInteger를 0x 접두사가 있는 16진수 문자열로 변환
        String privateKey = Numeric.toHexStringWithPrefix(ecKeyPair.getPrivateKey());
        String publicKey = Numeric.toHexStringWithPrefix(ecKeyPair.getPublicKey());

        // Private Key: 32바이트 (비밀키, 절대 공개 금지)
        System.out.println("Private Key: " + privateKey);
        
        // Public Key: 64바이트 (공개키, 주소 생성용)
        System.out.println("Public Key: " + publicKey);


        /*
         * 실행 결과 예시:
         * 
         * 지갑 주소: 0x658b8a1ae242d0460d4777e17c9af438daab4f77 (20바이트)
         * Private Key: 0x--- (32바이트)
         * Public Key: 0x376db746fb37456556a2c0018d876fbc5e61f9d60dec7f9f21132b2d7a19e71d5a7b81bcd29ded77b7b489f2e0e043998e385809f9779133938065190f351a91 (64바이트)
         * 
         * 주요 특징:
         * - 매번 실행시 다른 값 생성 (암호학적 난수 사용)
         * - Private Key로 언제든 동일한 Public Key와 주소 재생성 가능
         */
    }
}

 

[참고] Numeric 클래스와 toHexStringWithPrefix() 사용하는 이유

Numeric 클래스란?

Web3j의 Numeric 클래스는 블록체인 데이터 형변환을 위한 유틸리티 클래스다. 블록체인에서는 다양한 숫자 형태(BigInteger, byte[], String 등)를 16진수 문자열로 변환하거나 그 반대 작업을 자주 해야 하는데, Numeric 클래스가 이런 번거로운 작업을 간편하게 해준다.

// 이런 변환 작업들을 쉽게 해준다
BigInteger number = new BigInteger("12345");
String hex = Numeric.toHexString(number);                        // "3039" 
String hexWithPrefix = Numeric.toHexStringWithPrefix(number);    // "0x3039"
byte[] bytes = Numeric.hexStringToByteArray(hex);                // [48, 57]

toHexStringWithPrefix()를 쓰는 이유

1. 블록체인 표준 형식 준수

이더리움을 비롯한 대부분의 블록체인에서는 16진수 데이터 앞에 "0x" 접두사를 붙이는 것이 표준이다.

// 표준 형식
"0x1a2b3c4d..."  

// 비표준 형식  
"1a2b3c4d..."

2. RPC 호출 호환성

이더리움 노드와 통신할 때 JSON-RPC API는 16진수 값에 반드시 "0x" 접두사를 요구한다.

{
  "method": "eth_sendTransaction",
  "params": [{
    "from": "0x742d35cc6633c0532925a3b8d1e57b5c4f6b12ab",
    "value": "0x9184e72a000"  // "0x" 접두사 필수!
  }]
}

3. 다른 도구들과의 호환성

MetaMask, Etherscan 등 대부분의 이더리움 도구들이 "0x" 접두사가 있는 형식을 기대한다.

실제 출력 결과 비교

// toHexStringWithPrefix() 사용
System.out.println("Private Key: " + Numeric.toHexStringWithPrefix(ecKeyPair.getPrivateKey()));
// 결과: Private Key: 0x4c08....

// toHexString() 사용 (접두사 없음)
System.out.println("Private Key: " + Numeric.toHexString(ecKeyPair.getPrivateKey()));
// 결과: Private Key: 4c08....

추가 팁: Numeric 클래스 활용법

// BigInteger를 16진수로
BigInteger value = new BigInteger("1000000000000000000"); // 1 ETH in wei
String hexValue = Numeric.toHexStringWithPrefix(value);     // "0xde0b6b3a7640000"

// 16진수를 BigInteger로
String hexString = "0xde0b6b3a7640000";
BigInteger weiAmount = Numeric.toBigInt(hexString);         // 1000000000000000000

// byte 배열과 16진수 변환
byte[] data = "Hello".getBytes();                         // [72, 101, 108, 108, 111]
String hexData = Numeric.toHexString(data);                 // "48656c6c6f"