본문으로 바로가기

이제 지난번에 git clone 하여 준비한 litecoin 소스를 가지고 알트코인을 만들어보도록 하겠습니다.

먼저 만들고자 하는 코인명으로 litecoin 디렉토리명을 변경합니다. 집에서 애완용으로 키우고 있는 고슴도치를 생각해서 dochicoin 으로 하였습니다.

코인 폴더의 src 폴더에서 작업을 시작하도록 하고, 순서대로 다음과 같이 진행을 합니다.


1. litecoin 이름을 만들고자 하는 알트코인명으로 변경

다음과 같이 소스에서 litecoin 명을 dochicoin 으로 변경한후에 이전의 빌드커맨드로 빌드하여 에러없이 dochicoind 가 생성됨을 확인합니다.


$ find . -type f -print0 | xargs -0 sed -i 's/litecoin/dochicoin/g'
$ find . -type f -print0 | xargs -0 sed -i 's/Litecoin/Dochicoin/g'
$ find . -type f -print0 | xargs -0 sed -i 's/LiteCoin/DochiCoin/g'
$ find . -type f -print0 | xargs -0 sed -i 's/LITECOIN/DOCHICOIN/g'
$ find . -type f -print0 | xargs -0 sed -i 's/LTC/DHC/g'


2. P2P 포트와 RPC 포트 변경

각각의 포트가 있는 소스코드는 다음과 같습니다.

- P2P port: protocol.h and init.cpp

- RPC port: bitcoinrpc.cpp and init.cpp


$ find . -type f -print0 | xargs -0 sed -i 's/9333/2333/g' // P2P port
$ find . -type f -print0 | xargs -0 sed -i 's/9332/2332/g' // RPC port



3. 생성되는 주소의 접미부를 변경 (litecoin을 의미하는 'L' 에서 원하는 접미부로 변경)

라이트코인의 경우 주소생성시에 항상 주소의 첫글자는 Litecoin을 위미하는 L로 시작하는 주소가 생성됩니다.  따라서 자신이 생성하고자 하는 코인명으로 주소가  생성되도록 하기의 테이블을 참고로 prefix를 변경해줍니다. 

https://en.bitcoin.it/wiki/List_of_address_prefixes


base58.h : 275  line

PUBKEY_ADDRESS : mainnet address

PUBKEY_ADDRESS_TEST : testnet address


enum
{
PUBKEY_ADDRESS = 30, // Dochicoin addresses start with L
SCRIPT_ADDRESS = 5,
PUBKEY_ADDRESS_TEST = 90,
SCRIPT_ADDRESS_TEST = 196,
};


4. alert system 동작을 위한 mainnet, testnet, genesis block의  key 값 변경

전체 시스템의 중지가 필요한 긴급상황에서 클라이언트 노드에게 메세지를 띠우기위한 alert system에 사용되는 키값을 변경합니다.

다음 커멘트를 실행하여 각각 mainnet, testnet, genesis 블록의 키값을 생성합니다.


openssl ecparam -genkey -name secp256k1 -out alertkey.pem
openssl ec -in alertkey.pem -text > alertkey.hex
openssl ecparam -genkey -name secp256k1 -out testnetalert.pem
openssl ec -in testnetalert.pem -text > testnetalert.hex
openssl ecparam -genkey -name secp256k1 -out genesiscoinbase.pem
openssl ec -in genesiscoinbase.pem -text > genesiscoinbase.hex


3개의 hex 화일을 생성한후에 각각의 hex 화일을 보면 다음과 같습니다.


ihpark92@ubuntu:~/alert$ cat alertkey.hex
Private-Key: (256 bit)
priv:
00:8c:2e:72:d3:d7:d2:8f:f6:da:7a:dd:7b:17:4c:
d3:cf:d8:9c:77:6f:bd:da:15:86:e8:6c:ef:88:a5:
56:b5:b2
pub:
04:8b:6c:96:93:b8:54:aa:4e:a6:2b:bc:88:81:de:
96:e8:d7:9c:74:cb:54:95:e3:19:a1:1a:2b:0d:4c:
2f:95:d1:07:e0:23:4d:79:1e:08:b1:25:c5:e1:55:
a9:62:ac:f9:9b:22:9b:b3:a6:95:e0:e9:3a:09:1c:
e1:59:d1:28:3c
ASN1 OID: secp256k1
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIIwuctPX0o/22nrdexdM08/YnHdvvdoVhuhs74ilVrWyoAcGBSuBBAAK
oUQDQgAEi2yWk7hUqk6mK7yIgd6W6NecdMtUleMZoRorDUwvldEH4CNNeR4IsSXF
4VWpYqz5myKbs6aV4Ok6CRzhWdEoPA==
-----END EC PRIVATE KEY-----


여기서 필요한 값은 pub: 에 해당하는 키값이며 위의 예에서는 : 으로 구분된 키값에서 : 을 제거하여 하나의 문자열로 만들어서 사용합니다.

먼저 alert.cpp 코드의 mainnet과 testnet의 키값을 변경합니다.


static const char* pszMainKey = "048b6c9693b854aa4ea62bbc8881de96e8d79c74cb5495e319a11a2b0d4c2f95d107e0234d791e08b125c5e155a962acf99b229bb3a695e0e93a091ce159d1283c";
static const char* pszTestKey = "04beef71aed1a52cd019b8988c1cc0265cf501545308c4c5b33c05b8618f46456ace7c3ccdc31ef1f197feef0755369ea61f579ae86a3f1f71ad00ab43c43dc5f9";


그리고, main.cpp 코드의 2788 라인의 genesis 블럭의 키값을 변경합니다.


txNew.vout[0].scriptPubKey = CScript() << ParseHex("04d7aa3cf36e9367d70cc0a0213224451856afc71eec292fc83ebe5abee1993c75fd872cb527e924fc41652d33bf58744287625b498880dfe89d3de194b814dc31") << OP_CHECKSIG;



5.  Message의 peer magic값 변경

mainnet과 testnet의 메세지에 포함되는 magic 넘버값 변경합니다. magic 넘버는 해당 코인의 메세지가 다른 코인의 네트워크에 전송되는것을 방지하기 위한 용도로 사용이 됩니다.


main.cpp 코드의 2743 라인의 다음 testnet 접속주소를 원하는 값으로 변경합니다.  라이트코인의 경우 비트코인의 값에서 2 증가된 값으로 변경되었다고 명시되어 있으니 원하는 코인의 값도 적당하게 변경을 합니다.


if (fTestNet)
{
pchMessageStart[0] = 0xfc;
pchMessageStart[1] = 0xc1;
pchMessageStart[2] = 0xb7;
pchMessageStart[3] = 0xdc;


main.cpp 코드의 3082 라인의 mainnet magic 넘버를 변경합니다. 


unsigned char pchMessageStart[4] = { 0xfb, 0xc0, 0xb6, 0xdb }; // Dochicoin: increase each by adding 2 to bitcoin's value.


6. 하드코딩되어있는 DNS seed 주소를 삭제 또는 변경

net.cpp 코드의 1175 라인에 포함되어있는 litecoin의 dns seed 를 삭제하거나 원하는 seed node의 주소를 지정해줍니다.


// DNS seeds
// Each pair gives a source name and a seed name.
// The first name is used as information source for addrman.
// The second name should resolve to a list of seed addresses.
static const char *strMainNetDNSSeed[][2] = {
{NULL, NULL}
};
static const char *strTestNetDNSSeed[][2] = {
{NULL, NULL}
};


그리고 다음의 pnSeed 값도 기존의 값을 삭제합니다.


unsigned int pnSeed[] =
{
0x0
};


7. 채굴시 보상코인의 갯수, 난이도 재설정 주기, 블록생성시간, 전체 발생코인 갯수를 변경

main.cpp 코드의 1088 라인의 하기 코드에서 여러 설정값을 변경할수 있습니다.


int64 static GetBlockValue(int nHeight, int64 nFees)
{
int64 nSubsidy = 20 * COIN;
// Subsidy is cut in half every 840000 blocks, which will occur approximately every 4 years
nSubsidy >>= (nHeight / 10000); // Dochicoin: 840k blocks in ~4 years
return nSubsidy + nFees;
}
static const int64 nTargetTimespan = 1 * 24 * 60 * 60; // Dochicoin: 3.5 days
static const int64 nTargetSpacing = 5 * 60; // Dochicoin: 2.5 minutes


nSubsidy : 블록생성시 보상코인의 갯수

보상코인이 반으로 줄어드는 기간

(라이트코인의 경우 840000블록마다 보상코인이 절반으로 줄었고, 변경되는 코인에서는 10000 블록마다 절반으로 줄도록 하였습니다)

nTargetTimespan : 난이도가 재설정되는 주기 (위의 예에서는 1일마다 난이도 재설정되도록 변경)

nTargetSpacing : 블록이 생성되는 시간(기존 라이트코인의 2.5분에서 5분으로 변경)


main.h 에서 전체 발생코인 갯수를 설정합니다. (아래는 라이트코인의 갯수로 8400만개)


/** No amount larger than this (in satoshi) is valid */
static const int64 MAX_MONEY = 84000000 * COIN;


하기 설정값은 채굴시에 해당되는 설정값으로 하기의 10이 의미하는 값은 채굴자가 보상으로 받은 코인의 경우 10 컨펌을 받은후에(10 블록이 생성된후) 거래에 사용할수 있다는 의미입니다. 


/** Coinbase transaction outputs can only be spent after this number of new blocks (network rule) */
static const int COINBASE_MATURITY = 10;


그밖에도 여러 설정값이 있으니 원하는 설정값으로 변경하시기 바랍니다.


8. Genesis block 의 설정값 변경 : Timestamp, Nonce, 머클루트, 블럭해쉬

이제 Genesis 블록의 설정값을 변경하도록 하겠습니다.


먼저 main.cpp  의 2782 라인의 하기 코드에서 pszTimestamp에 문자열과 보상코인 갯수를 변경합니다

문자열은 옵션사항입니다..


const char* pszTimestamp = "My first Dochi Coin";
CTransaction txNew;
txNew.vin.resize(1);
txNew.vout.resize(1);
txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp));
txNew.vout[0].nValue = 20 * COIN;


그리고 다음 코드의 mainnet과 testnet의 nTime값을 각각 설정합니다.

nTime값은 콘솔상에서 "date +%s" 커맨드 실행하여 나온 결과값을 사용하면 됩니다.


block.nTime = 1523973960;
block.nBits = 0x1e0ffff0;
block.nNonce = 2084524493;
if (fTestNet)
{
block.nTime = 1523974024;
block.nNonce = 385270584;
}


그다음 merkle root 값을 얻기위해 먼저 다음과 같이  merkle root 값을 변경하여 빌드하고 생성된 dochicoind 를 실행합니다. 


assert(block.hashMerkleRoot == uint256("0x"));
block.print();
assert(hash == hashGenesisBlock);


그러면 다음과 같이 coredump가 발생합니다.

ihpark92@ubuntu:~/dochicoin/src$ ./dochicoind
dochicoind: main.cpp:2809: bool InitBlockIndex(): Assertion `block.hashMerkleRoot == uint256("0x")' failed.
중지됨 (core dumped)


/home/.dochicoin 폴더에 가보면 debug.log 화일에 다음과 같은 정보가 있고, 가장 마지막값이 merkle root값입니다.

이값을 위에서 0x로  변경했던 mergleRoot 값에 할당합니다.


...
2018-04-17 14:13:33 Opened LevelDB successfully
2018-04-17 14:13:33 LoadBlockIndexDB(): last block file = 0
2018-04-17 14:13:33 LoadBlockIndexDB(): transaction index disabled
2018-04-17 14:13:33 Initializing databases...
2018-04-17 14:13:33 81e218c7f8bbcd4ad76cb48600237e2765d63cea0b1ab7847e50bbead231e40d
2018-04-17 14:13:33 12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2
2018-04-17 14:13:33 a86ce0fe6024206585a5047c17f64ef303cd556da5cf49cb90dfa1a34722a768


assert(block.hashMerkleRoot == uint256("0xa86ce0fe6024206585a5047c17f64ef303cd556da5cf49cb90dfa1a34722a768"));
block.print();
assert(hash == hashGenesisBlock);



이제 genesis block의 hash 값을 확인하기 위해 이와같이 coredump를 발생시켜 debug.log 화일에서 필요한 정보를 확인하여 사용하도록 코드를 하나 추가하도록 하겠습니다.


if (false && block.GetHash() != hashGenesisBlock)
{
printf("Searching for genesis block...\n");
// This will figure out a valid hash and Nonce if you're
// creating a different genesis block:
uint256 hashTarget = CBigNum().SetCompact(block.nBits).getuint256();
uint256 thash;
char scratchpad[SCRYPT_SCRATCHPAD_SIZE];
loop
{
#if defined(USE_SSE2)
// Detection would work, but in cases where we KNOW it always has SSE2,
// it is faster to use directly than to use a function pointer or conditional.
#if defined(_M_X64) || defined(__x86_64__) || defined(_M_AMD64) || (defined(MAC_OSX) && defined(__i386__))
// Always SSE2: x86_64 or Intel MacOS X
scrypt_1024_1_1_256_sp_sse2(BEGIN(block.nVersion), BEGIN(thash), scratchpad);
#else
// Detect SSE2: 32bit x86 Linux or Windows
scrypt_1024_1_1_256_sp(BEGIN(block.nVersion), BEGIN(thash), scratchpad);
#endif
#else
// Generic scrypt
scrypt_1024_1_1_256_sp_generic(BEGIN(block.nVersion), BEGIN(thash), scratchpad);
#endif
if (thash <= hashTarget)
break;
if ((block.nNonce & 0xFFF) == 0)
{
printf("nonce %08X: hash = %s (target = %s)\n", block.nNonce, thash.ToString().c_str(), hashTarget.ToString().c_str());
}
++block.nNonce;
if (block.nNonce == 0)
{
printf("NONCE WRAPPED, incrementing time\n");
++block.nTime;
}
}
printf("block.nTime = %u \n", block.nTime);
printf("block.nNonce = %u \n", block.nNonce);
printf("block.GetHash = %s\n", block.GetHash().ToString().c_str());
}


위의 코드를 main.cpp의 2803 라인에 추가하고 첫라인의 false 조건을 true로 변경합니다.

그리고 main.cpp의 38, 2749 라인의 mainnet, testnet의 genesis block hash값을 모두 0x로 변경하고 빌드하여 다시 dochicoind 를 실행하면 일정시간이 지난후에 coredump가 발생합니다.  mainnet의 경우 ./dochicoind 만 실행하면 되고, testnet으로 실행시에는 "./dochicoind -testnet" 으로 실행하면 됩니다.

mainnet의 경우 ./dochicoin 폴더에 debug.log가 생성되고, testnet의 경우 ./dochicoin/testnet3 폴더에 debug.log가 생성됩니다.


다음과 같이 각각의 debug.log 화일에 포함되어있는 genesis block의 nonce값과 hash값을 코드에 적용합니다.


2018-04-17 14:41:54 block.nTime = 1523975835
2018-04-17 14:41:54 block.nNonce = 2085126083
2018-04-17 14:41:54 block.GetHash = 4acaa03eae615ce1ba938a9a3b917419bbe4f70a6adff98d4ebf63a4d7a6a1b2


block.nBits = 0x1e0ffff0;
block.nNonce = 2085126083;
if (fTestNet)
{
block.nTime = 1523975855;
block.nNonce = 385406737;
}


마지막으로 checkpoints.cpp 코드의 36번 라인을 mainnet과 testnet의 timestamp와 block hash 값으로 다음과 같이 변경합니다.


// What makes a good checkpoint block?
// + Is surrounded by blocks with reasonable timestamps
// (no blocks before with a timestamp after, none after with
// timestamp before)
// + Contains no strange transactions
static MapCheckpoints mapCheckpoints =
boost::assign::map_list_of
( 0, uint256("0x4acaa03eae615ce1ba938a9a3b917419bbe4f70a6adff98d4ebf63a4d7a6a1b2"))
;
static const CCheckpointData data = {
&mapCheckpoints,
1523975835, // * UNIX timestamp of last checkpoint block
0, // * total number of transactions between genesis and last checkpoint
// (the tx=... number in the SetBestChain debug.log lines)
1.0 // * estimated number of transactions per day after checkpoint
};
static MapCheckpoints mapCheckpointsTestnet =
boost::assign::map_list_of
( 0, uint256("0x5c4b9c7b20ac8acb4ecc053605807d93dd38165bbe66e2284ca62fb18dcaa183"))
;
static const CCheckpointData dataTestnet = {
&mapCheckpointsTestnet,
1523975855,
0,
1.0
};


여기까지 수정후에는 강제로 coredump를 발생시키기 위해 추가한 코드의 조건식에서 true 값을 false로 다시 원복시키고 빌드합니다.

이제 기본적으로 알트코인을 만들기위한 기본적인 코드수정이 완료되었습니다. 


마지막으로 qt 빌드를 하여 ui를 가지는 wallet을 생성하도록 하겠습니다.


ihpark92@ubuntu:~/dochicoin$ qmake
ihpark92@ubuntu:~/dochicoin$ make -j8


모든수정이 완료된후 실행된 화면은 다음과 같습니다.


다음과 같이 dochicoin명으로 실행되며, 코인심볼 DHC로 생성되었습니다.



Receive 의 경우 생성되는 주소는 mainnet 의 경우 하기와같이 Dochicoin 으로 설정한 prefix 인 D로 시작하고 있습니다.




단 이렇게 수정한걸로는 UI 이미지는 기존 라이트코인을 사용하기 떄문에 다음과같이 이미지 리소스는 라이트코인으로 보여집니다.

정식으로 배포를 하기위해서는 이미지 리소스 화일들도 모두 자신이 디자인한 이미지로 교체하여야 합니다.




이와같이 기본적인 수정사항을 통해 자신만의 알트코인을 만드는 방법을 알아보았습니다.

다음 포스팅에서 이렇게 생성한 알트코인으로 채굴을 통해 코인을 생성하는 방법을 알아보도록 하겠습니다.

'블록체인 > 암호화폐' 카테고리의 다른 글

알트코인 마이닝 하기  (2) 2018.04.18
비트코인 소스 기반의 알트코인 만들기 - 1  (14) 2018.04.16