탈중앙화 지갑의 사용자 인증 및 키 관리

탈중앙화 지갑은 사용자가 자신의 디지털 자산을 직접 제어할 수 있도록 하는 핵심적인 블록체인 애플리케이션입니다. 이러한 지갑의 사용자 인증 및 키 관리 설계는 보안과 사용자 경험이라는 두 가지 중요한 측면을 모두 고려해야 합니다.

1. 지갑 생성 방식

새로운 지갑을 생성하는 방식은 주로 두 가지 주요 형태로 제공되며, 모두 키의 안전한 보관을 목표로 합니다. 다중 암호화를 통해 키를 보호하면서도 사용자가 기억해야 할 정보를 단순화하는 데 중점을 둡니다.

  • 강력한 비밀번호 (Keystore 파일): 사용자가 설정한 강력한 비밀번호를 기반으로 개인 키를 암호화하여 Keystore 파일로 저장합니다. 이 파일은 사용자가 직접 관리해야 하며, 보통 JSON 형식입니다. @wallet/client와 같은 라이브러리를 사용하여 이 과정을 구현할 수 있습니다.
  • 니모닉 구문 (Mnemonic Phrase): 자동으로 생성된 개인 키를 12개 또는 24개의 무작위 단어 시퀀스(니모닉 구문)로 변환합니다. 이 구문은 지갑을 복원할 수 있는 마스터 키 역할을 하며, 사용자가 반드시 안전하게 보관해야 합니다.

2. 지갑 연결 프로토콜

탈중앙화 지갑은 다양한 dApp(분산 애플리케이션) 및 서비스에 연결하기 위한 표준화된 프로토콜을 사용합니다. 주요 프로토콜은 다음과 같습니다.

  • WalletConnect: 모바일 지갑과 웹 dApp 간의 안전한 연결을 가능하게 하는 개방형 프로토콜입니다. QR 코드 스캔을 통한 연결 및 다양한 기기 지원이 특징입니다. @walletconnect/ethereum-provider 라이브러리를 사용하여 구현할 수 있습니다.
  • Web3 Provider (브라우저 확장 프로그램): MetaMask와 같은 브라우저 확장 프로그램 지갑은 dApp에 Web3 공급자를 주입하여 브라우저 환경에서 직접 블록체인과 상호작용할 수 있도록 합니다.
    import Web3 from 'web3';
    
    async function connectWeb3Wallet(provider) {
      if (!provider) {
        console.error("Web3 Provider가 감지되지 않았습니다.");
        return;
      }
    
      try {
        const web3Client = new Web3(provider);
        await provider.request({ method: 'eth_requestAccounts' }); // 사용자에게 연결 승인 요청
        const accounts = await web3Client.eth.getAccounts(); // 연결된 계정 목록 가져오기
    
        if (accounts.length > 0) {
          console.log("연결된 계정:", accounts[0]);
          // 선택된 계정을 사용하여 거래 서명 등의 작업 수행
          return accounts[0];
        } else {
          console.log("계정을 찾을 수 없습니다.");
          return null;
        }
      } catch (error) {
        console.error("지갑 연결 중 오류 발생:", error);
        return null;
      }
    }
    
  • WalletLink (Coinbase Wallet): Coinbase Wallet에서 사용하는 오픈 소스 프로토콜로, dApp이 Coinbase Wallet 앱과 안전하게 통신할 수 있도록 합니다.

3. 지갑 키 관리 구현

3.1. Keystore 파일 생성

개인 키를 사용자 정의 비밀번호로 암호화하여 Keystore 파일을 생성합니다. 이 파일은 사용자가 다운로드하여 안전하게 보관해야 합니다.

import Wallet from 'ethereumjs-wallet';

async function createAndEncryptWallet(userPassword, encryptionOptions) {
  const newEthWallet = Wallet.generate(); // 새 이더리움 지갑 생성
  
  try {
    const encryptedKeystore = await newEthWallet.toV3(userPassword, {
      kdf: encryptionOptions.kdf || 'scrypt', // 키 유도 함수
      n: encryptionOptions.n || 262144      // scrypt 매개변수
    });
    
    const fileName = newEthWallet.getV3Filename();
    const keystoreBlob = new Blob([JSON.stringify(encryptedKeystore)], { type: 'application/json' });
    const downloadUrl = URL.createObjectURL(keystoreBlob);

    console.log("Keystore 파일 생성 완료:", fileName);
    // 사용자에게 downloadUrl을 제공하여 파일 다운로드 유도
    return { fileName, downloadUrl };

  } catch (error) {
    console.error("Keystore 생성 및 암호화 오류:", error);
    throw error;
  }
}

3.2. 니모닉 구문 기반 지갑

BIP-39 프로토콜을 사용하여 니모닉 구문을 생성하고, 이를 통해 HD(Hierarchical Deterministic) 지갑을 구현합니다.

import * as bip39 from 'bip39';
import { HDKey } from '@scure/bip32'; // '@scure/bip32' 또는 'hdkey' 라이브러리 사용

/**
 * 12단어 니모닉 구문 생성 (128비트 엔트로피)
 * @returns {string[]} 12개의 단어 배열
 */
function generateShortMnemonic() {
  return bip39.generateMnemonic(128).split(' ');
}

/**
 * 24단어 니모닉 구문 생성 (256비트 엔트로피)
 * @returns {string[]} 24개의 단어 배열
 */
function generateLongMnemonic() {
  return bip39.generateMnemonic(256).split(' ');
}

async function deriveKeyPairFromMnemonic(mnemonicPhrase, password, derivationPath, index) {
  if (!bip39.validateMnemonic(mnemonicPhrase)) {
    throw new Error("유효하지 않은 니모닉 구문입니다.");
  }

  // 1. 니모닉 구문으로부터 시드(Seed) 생성
  const seedBuffer = await bip39.mnemonicToSeed(mnemonicPhrase, password);
  
  // 2. 시드로부터 HDKey 마스터 노드 생성
  const masterKey = HDKey.fromMasterSeed(seedBuffer);

  // 3. 파생 경로 및 인덱스를 사용하여 특정 키 쌍 파생
  const specificPath = `${derivationPath}/${index}`; // 예: "m/44'/60'/0'/0/0"
  const derivedChildKey = masterKey.derive(specificPath);

  if (!derivedChildKey.privateKey) {
    throw new Error("개인 키를 파생할 수 없습니다.");
  }

  // 파생된 개인 키와 공개 키 반환
  return {
    privateKey: derivedChildKey.privateKey.toString('hex'),
    publicKey: derivedChildKey.publicKey.toString('hex'),
    address: '0x' + new Wallet(derivedChildKey.privateKey).getAddress().toString('hex') // 주소 파생 예시
  };
}

3.3. WalletConnect V2 프로토콜 연동

WalletConnect V2를 사용하여 dApp과 지갑 간의 연결을 초기화하고 이벤트를 처리합니다.

import { EthereumProvider } from '@walletconnect/ethereum-provider';

async function initWalletConnectProvider(projectId, chains, rpcMap) {
  const provider = await EthereumProvider.init({
    projectId: projectId, // WalletConnect Cloud에서 발급받은 프로젝트 ID
    chains: chains,       // 연결할 체인 ID (예: [1, 5])
    rpcMap: rpcMap,       // 각 체인 ID에 대한 RPC URL (예: { 1: 'https://mainnet.infura.io/v3/...' })
    showQrModal: true,    // 연결 시 QR 모달 표시 여부
    metadata: {
      name: 'My dApp',
      description: 'My dApp description',
      url: 'https://my-dapp.com',
      icons: ['https://my-dapp.com/logo.png'],
    },
  });

  provider.on('connect', ({ chainId }) => {
    console.log(`WalletConnect 연결됨. Chain ID: ${chainId}`);
    // 연결 후, `provider` 객체를 사용하여 트랜잭션 서명, 메시지 서명 등 수행 가능
  });

  provider.on('disconnect', () => {
    console.log('WalletConnect 연결 해제됨');
  });

  // 연결 시작
  await provider.connect();
  
  return provider;
}

4. 블록체인 트랜잭션 처리

4.1. 자산 교환 환율 조회

Changelly와 같은 암호화폐 환율 애그리게이터의 API를 사용하여 다양한 통화 쌍의 실시간 환율 정보를 조회합니다. 일반적으로 HTTP 요청 라이브러리(예: Axios)를 통해 API 엔드포인트에 요청을 보냅니다.

4.2. 트랜잭션 전송

web3.js 또는 ethers.js와 같은 라이브러리의 API를 사용하여 블록체인에 트랜잭션을 전송합니다. 여기에는 개인 키를 이용한 트랜잭션 서명 과정이 포함됩니다.

4.3. 트랜잭션 기록 조회

  • 온체인 데이터 직접 조회: web3.js 라이브러리를 사용하여 현재 블록부터 이전 블록까지 거슬러 올라가며 특정 주소와 관련된 트랜잭션을 필터링하여 조회할 수 있습니다. 이는 실시간성이 높지만, 대량의 데이터를 처리하기에는 비효율적일 수 있습니다.
  • 블록체인 탐색기 API 활용: Etherscan과 같은 블록체인 탐색기는 전체 노드 데이터를 수집하여 중앙화된 서비스로 제공합니다. 이들은 페이징 및 필터링 기능이 강화된 API를 제공하므로, 특정 주소의 과거 트랜잭션 기록을 효율적으로 조회할 수 있습니다.

5. 블록체인 네트워크 전환

다양한 블록체인 네트워크(예: 이더리움 메인넷, 테스트넷, BNB Smart Chain 등) 간의 손쉬운 전환 기능을 제공하여 사용자가 여러 블록체인 생태계를 탐색할 수 있도록 지원합니다. 이는 Web3 공급자 API를 통해 구현됩니다.

6. 지갑 보안 고려 사항

개인 키와 니모닉 구문의 보안은 탈중앙화 지갑에서 가장 중요합니다. 보안을 유지하면서도 사용자 편의성을 높이는 것이 핵심 과제입니다.

  1. 키 기록 강조 및 백업: 사용자 인터페이스는 니모닉 구문이나 개인 키를 안전하게 기록하고 보관하도록 강력하게 권장해야 합니다. Keystore 파일 다운로드 기능은 암호화된 상태로 제공되어야 합니다.
  2. 로컬 암호화 및 캐싱: 매번 개인 키나 니모닉 구문을 입력하는 불편함을 줄이기 위해, 사용자의 강력한 비밀번호로 암호화된 키 정보를 브라우저의 IndexedDB 또는 안전한 로컬 저장소에 저장할 수 있습니다. 이후 재접속 시 사용자가 비밀번호를 입력하면 키가 복호화되어 세션에 사용됩니다. 이 비밀번호는 어떠한 경우에도 서버로 전송되어서는 안 됩니다.
  3. 중앙화된 인증 서비스 연동 (선택 사항): 지갑 로그인과 별개로 애플리케이션의 사용자 인증(예: 이메일/비밀번호, 소셜 로그인)에 중앙화된 서비스를 사용할 수 있습니다. 이 경우, 중앙화 서비스는 JWT(JSON Web Token)와 같은 인증 토큰을 발행하여 사용자 세션을 관리합니다. 하지만 이 서비스는 사용자의 지갑 개인 키나 복호화 비밀번호를 절대 저장하거나 접근해서는 안 됩니다. 지갑 키 관리는 항상 클라이언트 측에서 이루어져야 합니다.
  4. 다중 서명 (Multi-signature) 지갑: 여러 개의 키가 있어야 거래를 승인할 수 있는 다중 서명 지갑은 보안 수준을 크게 높일 수 있습니다. 특히 팀 환경이나 대규모 자산 관리에 적합합니다.

요약하자면, 모든 보안 방식은 최소한 이중 보호를 제공해야 합니다. 모바일 앱에서는 생체 인식과 기기 암호화를, 웹 지갑에서는 Keystore 파일과 비밀번호를, 브라우저 확장 프로그램에서는 기기 인증과 비밀번호를 결합하여 사용합니다.

7. 주요 사용 라이브러리

  • bip39: 니모닉 구문 생성 및 유효성 검사에 사용됩니다.
  • hdkey (또는 @scure/bip32): 시드로부터 HD 지갑의 파생 경로를 따라 주소 및 키를 생성하는 데 사용됩니다.
  • @walletconnect/ethereum-provider: WalletConnect 프로토콜을 사용하여 dApp과 지갑 간의 연결을 설정합니다.
  • ethers.js / web3.js: 블록체인과 상호작용하고, 트랜잭션을 생성 및 서명하며, 스마트 컨트랙트를 호출하는 데 사용되는 주요 라이브러리입니다.
  • crypto-js: 니모닉 구문이나 개인 키를 로컬 저장소에 저장하기 전에 대칭 암호화하는 데 사용될 수 있습니다.
  • idb (IndexedDB 라이브러리): 브라우저 내에서 암호화된 키 정보와 같은 데이터를 안전하게 저장하는 데 사용될 수 있습니다.

태그: 블록체인 탈중앙화지갑 Web3 니모닉구문 Keystore

9월 25일 09:38에 게시됨