블록체인에서는 공개키 암호화 방식을 이용해 사용자를 구분하고 사용자가 전송한 데이터가 유효한 데이터인지 검증하게 된다.
사용자는 자신의 Private key를 이용해 데이터를 서명을 하면 블록체인에서는 사용자의 Public key를 이용해 서면된 데이터를 검증하게 된다.
이때 사용되는 서명 알고리즘이 타원 곡선 알고리즘을 이용한 타원 곡선 디지털 서명 알고리즘(ECDSA)이다
타원 곡선 알고리즘을 이용한 디지털 서명 알고리즘
타원 곡선의 두 점을 지나는 직선의 값을 알더라도 곡선상의 대척점에 위치한 값을 알지는 못하는 이산 로그 문제 기반의 알고리즘
소인수분해 문제 기반의 RSA가 암호화 강도를 높이기 위해 키 길이를 늘릴 경우 계산 속도가 느려지는 문제가 있는 반면
타원 곡선 알고리즘의 경우 키 길이가 길어지더라도 속도는 영향이 없지만 암호화 강도는 기하 급수적으로 늘어나는 장점이 있다.
Private key를 생성한다. 생성된 Private key는 랜덤함 값의 32 bytes의 길이를 가지는 Hex 데이터로 이루어진다
Public key는 ECDSA를 이용해 64 bytes의 공개키를 생성한다
Public key를 Hash 알고리즘인 Keccak-256으로 암호화한 Hash 데이터 32bytes 중 마지막 20 bytes에 Address prefix 값을 붙여 21 bytes의 주소를 생성할 수 있다
Address prefix의 경우 Ethereum, Klaytn은 0x, Icon의 경우 hx를 사용한다.
Private key를 보다 안전하게 보관 하기 위해 암호화된 내용으로 저장하게 되며 주요 블록체인의 keystore 파일의 내용은 다음과 같다.
{
"address": "aec00476b3eb165e7f27cfd7a6719415220807c6",
"crypto": {
"cipher": "aes-128-ctr",
"cipherparams": {
"iv": "4bdf21c5bab4dc0a46eaa23384685278"
},
"ciphertext": "6b51af57edcf74925584e6de93e4f61717a2075c0183b71ed103033830d04e74",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 8192,
"p": 1,
"r": 8,
"salt": "8bc74c04ef0abd806fa422591612809ff6493cba2cf43a3190c9065ea292b99a"
},
"mac": "352a248eaed1887699f8b357420f79943c939316c2653eadd7dcc0179896029b"
},
"id": "8bbcbe0e-0f62-4890-8545-b49dd13d9f0a",
"version": 3
}{
"address": "0xaec00476b3eb165e7f27cfd7a6719415220807c6",
"id": "66e7acc5-f583-4544-9cd1-95a516141860",
"keyring": [
{
"cipher": "aes-128-ctr",
"cipherparams": {
"iv": "3a54bd1cf3381997988bd5cf7334eec7"
},
"ciphertext": "4924d8cabf715e2778568efd1a1140fb62e855d345f6796294cf7ed10f4146f8",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 4096,
"p": 1,
"r": 8,
"salt": "d7203dc9276d18caea89d53ae393976b0020d7529dde0661bf6ccd63e2dad184"
},
"mac": "29e5332b8d90576a831dbb512b628f83807d487da670f961e665d0619f185700"
}
],
"version": 4
}{
"address": "hx50f71d4aaa49f989db9214d5fc2c9d2d7e23f34c",
"coinType": "icx",
"crypto": {
"cipher": "aes-128-ctr",
"cipherparams": {
"iv": "22c1e822e754d4bb0dd44b3bb509d250"
},
"ciphertext": "21b67001a9b0f0b73e7f0d8f9a2544c149519037045c4f21a599839ffeb02812",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 16384,
"p": 8,
"r": 1,
"salt": "c06d8cfc314ce2a3493a0feac599f729"
},
"mac": "e0dbbed9d99b4f520fb3e84794c3edcdab262aa7c5361ae4b72a8fc5ae32ab9f"
},
"id": "955b9951-eeb4-42bd-a12e-ed9da371b129",
"version": 3
}keystore의 password를 kdf에 정의된 블록 암호화를 이용해 Hash 값을(Derived Key) 얻어낸다
kdf는 일반적으로 scrypt를 사용하지만 pbkdf2를 사용 하기도 한다.
앞서 만들어진 Derived Key를 이용해 Private key를 cipher에 정의된 암호화 방식으(aes-128-ctr)로 암호화 한다
이렇게 만들어진 데이터는 cipertext가 된다.
즉 Derived Key가 Private key를 암호화하는 실제 password가 되는 것이다.
Derived Key의 마지막 16 bytes와 cipertext를 이어 붙인 48 bytes를 SHA3-256를 이용해 32 bytes의 Hash data를 만들며
이 Hash data는 mac 이 된다.
입력받은 password를 kdf에 정의된 블록 암호화를 이용해 Hash 값을(Derived Key) 얻어낸다.
입력한 password가 올바른 값인지 확인하기위해 cipertext와 Derived key로 새로운 mac을 생성한 후 keystore 파일에 저장된 mac과 비교해 같을 경우 복호화 한다.
Derived key를 이용해 암호화된 cipertext를 복호화해 Private key를 얻어낸다.
// 니모닉 코드 생성
const mnemonic = bip39.generateMnemonic(
256,
randomBytes,
bip39.wordlists.korean,
);
// 니모닉 코드를 seed로 사용해 private key 생성
const seed = bip39.mnemonicToSeed(mnemonic);
const node = bip32.fromSeed(seed);
const child = node.derivePath("m/44'/60'/0'/0/0");
const ecpair = bitcoinjs.ECPair.fromPrivateKey(child.privateKey, {
compressed: false,
});
const privateKey = ecpair.privateKey.toString("hex");
// 아이콘 wallet
const iconKeyWallet = IconService.IconWallet.loadPrivateKey(privateKey);
console.log("ICON Address : ", +iconKeyWallet.getAddress());
console.log("ICON Private Key : " + iconKeyWallet.getPrivateKey());
// klaytn wallet
const klaytnKey = caver.wallet.keyring.createFromPrivateKey(privateKey);
console.log("Klaytn Address : " + klaytnKey.address);
console.log("Klaytn Private Key : " + klaytnKey.key.privateKey);
// ethereum account
const ethKey = accounts.privateKeyToAccount(privateKey);
console.log("Ethereum Address : " + ethKey.address);
console.log("Ethereum Private Key : " + ethKey.privateKey);
// cosmos-sdk account
console.log(
"Cosmos Address : " +
bech32.encode("cosmos", bech32.toWords(child.identifier)),
);
console.log("Cosmos Private Key : " + privateKey);