#ethereum

Signing and verifying in ethereum

이더리움에서 eth 를 송금하거나 스마트 컨트랙을 부르는 등의 transaction 을 일으키기 위해서는 signing 이 필요하며, 블록체인 상에서 사용자를 인증하거나 메세지의 유효성을 검증하기 위해 signing 과 validation 이 사용된다.

가령, Alice 가 Bob 에게 어떤 메세지를 보내는 상황을 전제해 보자. 여기서 Bob 이 메세지를 받았다면, 인터넷 네트워크에서는 누구든지 메세지를 보낼 수 있고, 나쁜 의도를 가진 다수의 사용자들이 존재하기 때문에, 진짜 그 메세지가 Alice 로 부터 온 것인지 또, 메세지의 내용이 Alice 가 보낸 그 내용이 맞는지에 대해 의문을 품을 것이다. 이 때문에 블록체인 에서는 signing 의 개념이 존재하며 여기서 그 유효성을 검증하고자 하는 대상을 message 라고 한다.

그렇다면 이제 어떻게 signgingvalidation 이 진행되는 지를 알아보자.

먼저 위 사례에서 Alice 는 자신의 Private Key 로 해당 메세지에 서명을 한다. 이더리움에서 제공하는 web3 를 통해 다음과 같은 간략한 명령을 통해 이런 signing 이 진행되게 된다.

1
web3.eth.accounts.sign(message, privateKey);

위 에서 message 는 유효성을 검증하기 위한 메세지로 String 타입이다.

위 과정을 거치면 사용자가 서명을 한 메세지를 의미하는 Signature Object 가 나오게 되는데 그 형식은 다음과 같다.

1
2
3
4
5
6
7
8
9
{
message: 'Some data',
messageHash: '0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655',
v: '0x1c',
r: '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd',
s: '0x6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029',
signature: '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f\
5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c'
}

위에서 알 수 있듯이 Signature 에는 사용자의 원래 메세지 뿐만 아니라 messageHash 라는 "\x19Ethereum Signed Message:\n" + message.length + message 의 양식으로 포맷팅된 후 keccak256(SHA3) 으로 해쉬된 해시값을 비롯하여 signature 를 구성하는 v, r, s 와 실제 서명인 Signature 가 포함된다. 여기서 r 과 s 는 각각 Signature의 첫 32byte 와 뒤 32byte 를 나타낸다.

* 위의 MessageHash 는 web3 가 제공하는 hashMessage(message) 함수 호출을 통해서도 얻을 수 있다.

위처럼 sign 을 완료했다면, 해당 Signature 를 받은 사용자는 다음과 같은 recover 함수 호출을 통해 올바른 사용자인지를 판별 할 수 있다.

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
web3.eth.accounts.recover(signatureObject);
web3.eth.accounts.recover(message, signature [, preFixed]);
web3.eth.accounts.recover(message, v, r, s [, preFixed]);

web3.eth.accounts.recover({
messageHash: '0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655',
v: '0x1c',
r: '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd',
s: '0x6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029'
})
> "0x2c7536E3605D9C16a7a3D7b1898e529396a65c23"

// message, signature
web3.eth.accounts.recover('Some data', '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c');
> "0x2c7536E3605D9C16a7a3D7b1898e529396a65c23"

// message, v, r, s
web3.eth.accounts.recover('Some data', '0x1c', '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd', '0x6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029');
> "0x2c7536E3605D9C16a7a3D7b1898e529396a65c23"

EIP712

What is EIP712

EIP 712 란 Ethereum Improvement Proposals 712 의 약어로 이더리움에서 향후 지원하게 될 다양한 제안들중 하나이다. 과거 사용자가 어떤 거래를 함에 있어 sign 을 할때에는 sign 의 대상이 되는 message 가 hash 화 되어 존재하기 때문에, 서명을 하는 사용자가 자신이 서명하는 내용에 대해 잘 알기 힘든 문제가 있었으며, 가령 유사한 해시값을 가진 피싱 사이트로 유도하여 사이닝을 유도한다던가 하는 다양한 위험에 노출되어 있었다.

이를 해결하기 위해 안전하고 값이 변조되지 않는 해싱을 보장하면서도 readability 를 가질 수 있는 서명방법을 고안하게 되었으며 그 제안내용이 EIP712 에 제안되었다.

EIP712 를 통해 사용자는 자신이 서명하는 정보에 대해 명확하게 인지할 수 있게 된다고 볼 수 있다.

가령, 위에서처럼 일반적인 signing의 과정에서는 sign 을 요청하는 사람이 제공하는 message값(주로 hash값) 만을 보고 사용자가 sign 을 진행하지만 EIP712 에서는 json 데이터를 보고 자신이 서명하는 데이터를 명확히 알 수 있다.

다음은 EIP712 를 통해 수행되는 서명의 방법을 나타낸다.

먼저 서명을 하고자 하는 데이터의 형태를 정의하는데 이러한 typed structured data 를 먼저 정의하는 것으로 서명이 시작된다. 아래와 같이 서명을 하고자 하는 메세지를 json 형태로 정의를 한다. 서명자는 아래와 같은 데이터를 통해 어떤 내용에 자신이 서명을 하는지 알 수 있다.(아래의 메세지는 meta mask 등의 지갑 어플리케이션에서 파싱되어 보여지며, EIP712 는 이러한 지갑 어플리케이션에서 제공하는 기능을 말미암에 제공된다.)

컨트랙트 도메인 타입의 정의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const domain = [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
{ name: "salt", type: "bytes32" },
];

const bid = [
{ name: "amount", type: "uint256" },
{ name: "bidder", type: "Identity" },
];

const identity = [
{ name: "userId", type: "uint256" },
{ name: "wallet", type: "address" },
];
1
2
3
4
5
6
7
var message = {
amount: 100,
bidder: {
userId: 323,
wallet: "0x3333333333333333333333333333333333333333"
}
};

위처럼 서명하고자 하는 message 를 정의하였다면, 해당 메세지가 어떤 도메인 사업자(즉 DAPP 사업자로 이해할 수 있다) 에서부터 온 메세지인지를 나타내는 구분자인 Domain Separator 를 정의해 준다.

1
2
3
4
5
6
7
const domainData = {
name: "My amazing dApp",
version: "2",
chainId: parseInt(web3.version.network, 10),
verifyingContract: "0x1C56346CD2A2Bf3202F771f50d3D14a367B48070",
salt: "0xf2d857f4a3edcb9b78b4d503bfe733db1e3f6cdc2b7971ee739626c97e86a558"
};

name: DAPP 혹은 Protocol 의 이름

version: DAPP 혹은 Platform version

chainId: 테스트 넷인지 메인넷인지 등을 구분하는 chain id 로 EIP155 에서 제안되었다.

verifyingContract: 해당 signature 를 verify 할 스마트 컨트랙트의 주소

salt: 32 바이트의 hard code 된 유니크한 값으로 컨트랙트와 DAPP 사이에 공유되어 다른 DAPP 과 구분되는 최후의 보루이다.

message type 과 Domain separator 가 정의되었다면 다음과 같이 Json 형태의 data 를 만들고 이를 stringily 시켜 sign 을 요청한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const data = JSON.stringify({
types: {
EIP712Domain: domain,
Bid: bid,
Identity: identity,
},
domain: domainData,
primaryType: "Bid",
message: message
});

web3.currentProvider.sendAsync(
{
method: "eth_signTypedData_v3",
params: [signer, data],
from: signer
},
function(err, result) {
if (err) {
return console.error(err);
}
const signature = result.result.substring(2);
const r = "0x" + signature.substring(0, 64);
const s = "0x" + signature.substring(64, 128);
const v = parseInt(signature.substring(128, 130), 16);
// The signature is now comprised of r, s, and v.
}
);

data 내의 types 필드는 스마트 컨트랙트 내에서의 데이터 구조를 나타내며 반드시 struct name 과 정확히 일치해야한다. 또한 PrimaryType 은 데이터 구조에서의 최상위 자료 구조형이 무엇인지 명시한다.

Validation in Smart contract

클라이언트에서 signing 을 위해 formatting 과 hashing 을 거친 것처럼 같은 내용의 코드가 Smart Contract 에도 포함되어야 한다. 이 과정을 통해 ecrecover 함수를 통해 해당 서명에 사인한 account 의 address 를 알 수 있다.

인증을 위한 Contract 를 위해 제일 먼저 EIP712 에서 앞서 정의한 data type 을 struct 로 정의해야 한다.

1
2
3
4
5
6
7
8
9
struct Identity {
uint256 userId;
address wallet;
}

struct Bid {
uint256 amount;
Identity bidder;
}

그 다음으로는 위 data structure 에 맞는 type hash 를 정의해야 하며 그 코드는 다음과 같다.

1
2
string private constant IDENTITY_TYPE = "Identity(uint256 userId,address wallet)";
string private constant BID_TYPE = "Bid(uint256 amount,Identity bidder)Identity(uint256 userId,address wallet)";

여기서 comma 와 bracket 사이에 공백이 들어가지 않는것을 유념하자. 또한, parameter 의 이름과 자료형이 클라이언트의 자료형과 변수명과 완벽하게 일치해야 한다.

또한 다음과 같이 Domain Separator 도 다음과 같이 hashify 되어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
uint256 constant chainId = 1;
address constant verifyingContract = 0x1C56346CD2A2Bf3202F771f50d3D14a367B48070;
bytes32 constant salt = 0xf2d857f4a3edcb9b78b4d503bfe733db1e3f6cdc2b7971ee739626c97e86a558;
string private constant EIP712_DOMAIN = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)";
bytes32 private constant DOMAIN_SEPARATOR = keccak256(abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256("My amazing dApp"),
keccak256("2"),
chainId,
verifyingContract,
salt
));

아래와 같이 각 data 를 hashify 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hashIdentity(Identity identity) private pure returns (bytes32) {
return keccak256(abi.encode(
IDENTITY_TYPEHASH,
identity.userId,
identity.wallet
));
}

function hashBid(Bid memory bid) private pure returns (bytes32){
return keccak256(abi.encodePacked(
"\\x19\\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(
BID_TYPEHASH,
bid.amount,
hashIdentity(bid.bidder)
))
));
}

마지막으로 다음과 같이 signature 를 verify 하는 함수를 작성해 준다.

1
2
3
function verify(address signer, Bid memory bid, sigR, sigS, sigV) public pure returns (bool) {
return signer == ecrecover(hashBid(bid), sigV, sigR, sigS);
}

Keccak256 hashing

이더리움에서는 SHA3 해싱을 위해 keccak256 을 사용한다.

Getting started with ethereum

Kinds of accounts

이더리움에는 두가지 종류의 account 가 있는데, External AccountContract Account 가 그것이다.

먼저 External Account 는 공개키 방식의 계정이며, address 역할을 하는 public keynonce 로 구성이 되며, Contract Account 는 일반적인 smart contract 이다.

여기서 account 와 account 사이에 자금이 이동하면 그것을 transaction 이라고 말한다.

Transaction

이더리움에서 transaction 이란 하나의 EOA 로부터 다른 account 로 전송되는 message 를 포함한 signed data packet 을 의미하며 다음과 같은 정보들을 담고 있으며, EOA 로 부터 EOA 로 가는 경우는 이더를 송금하는 것을 내포하고, 만약 수신자가 Contract Account 라면 해당 contract 가 어떤 코드를 실행하도록 trigger 하는 것이 된다.

  • receipient
  • signature
  • value
  • data: which can contain the message sent to a contract

여기서 data 는 contract 가 실행에 필요한 정보를 들고 있으며 Contract ABI(Application Binary Interface) 에 따라 컨트랙트와 소통한다. 이 ABI 는 외부 와컨트랙트 간의 상호작용에 필요할 뿐만 아니라 contract 간의 상호작용에도 적용된다.

따라서 data 는 다음의 규약에 따라 인코딩 되어 사용되며 ABI(Application Binary Interface 에 따라 정의된다.

함수 선택자

data 의 함수 시그니처의 Keccak-256 (SHA-3) hash 의 첫 4바이트는 어떤 함수를 호출할지를 나타내며 function 시그니처로 부터 도출된다.

1
2
3
4
5
6
7
pragma solidity >=0.4.16 <0.6.0;

contract Foo {
function bar(bytes3[2] memory) public pure {}
function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
function sam(bytes memory, bool, uint[] memory) public pure {}
}

위의 예제에서 baz 함수를 호출하고자 하는 transaction 의 data의 함수선택자는 0xcdcd77c0 인데 이는 method 의 id 로 ASCII format 으로 작성된 function signature 인 baz(uint32,bool 의 Keccak hash(sha3) 의 첫번째 4 바이트이다.

함수 Parameter

또한 해당 함수의 parameter 로 69와 true 를 제공한다면 69를 32바이트 로 패딩한 0x0000000000000000000000000000000000000000000000000000000000000045 가 값이 된다. 0x0000000000000000000000000000000000000000000000000000000000000001

ABI(Application Binary Interface)

컴퓨터 과학에서 흔히 말하는 ABI 는 두가지 종류의 binary 프로그램 모듈 사이의 인터페이스이다. 쉬운 예로는 libraryoperating 시스템의 경우가 있는데, 하나의 라이브러리는 여러 시스템 상에서 동작해야 하므로 해당 소스코드가 다양한 운영체제에서 동작하기 위해서는 약속된 인터페이스가 필요하다. 또 한 예로는 유저에 의해 실행되는 프로그램의 예가 있는데 가령 여러 운영체제에서 해당 프로그램을 실행하여도 동작하기 위해서는 이러한 ABI 가 정의되어 있어야 한다.

이와 같은 맥락에서 이더리움 에서의 ABI 란 특정 함수가 이더리움에서 실행되기 위한 포맷팅의 규약으로써 이해될 수 있다.

아래는 스마트 컨트랙트 내에서 특정 데이터를 ABI 인코딩 하는 것을 보여준다.

1
abi.encode("AAAA")

위 코드는 아래와 같이 3단어로 구성된 96(32byte *3) 사이즈의 바이트 문자열을 리턴한다.

1
2
3
0x0000000000000000000000000000000000000000000000000000000000000020
0x0000000000000000000000000000000000000000000000000000000000000004
0x4141414100000000000000000000000000000000000000000000000000000000

한 줄씩 살펴보면 먼저 첫번째 라인은 해당 문자열의 starting offset(32 in decimal) 를 32byte 문자열에 padding 한 것이며, 두번째 문자열은 인코딩 하는 데이터의 길이인 4를 32바이트 문자열에 padding 하여 나타낸다. 마지막으로 세번째 문자열은 실제 우리의 데이터인 “AAAA” 를 UTF-8 인코딩하여 32 바이트 문자열에 padding 하여 나타내어 준다.

위의 encoding 방식보다 다소 간편하게 인코딩을 하고 싶다면 abi.encodePacked("AAAA") 라는 함수를 사용할 수 있다. 이 함수는 32바이트보다 작은 문자는 그냥 해당 문자열을 바이트로 출력하고 32바이트에 padding 하지도 않는다.

한가지 흥미로운 사실이 있는데, keccak256 을 사용하여 hashing 을 할때 복수개의 인자를 전달하면 이것을 내부적으로 abi.encodePacked 함수로 인코딩 하여 해싱을 한다는 것이다. 현재는 복수개의 인자를 전달하면 warning 을 출력하기는 하지만, 알아두도록 하자.

다음은 Keccak256 에서 abi.encodePacked 를 사용하는 예이다. 아래의 두 hashing 은 정확히 동일한 역할을 수행한다.

1
keccak256("AAAA", "BBBB", 42);
1
keccak256(abi.encodePacked("AAAA", "BBBB", 42));

토큰 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pragma solidity ^0.4.20;

contract MyToken {
event Transfer(address indexed from, address indexed to, uint256 value);
/* This creates an array with all balances */
mapping (address => uint256) public balanceOf;
string public name;
string public symbol;
uint8 public decimals;
/* Initializes contract with initial supply tokens to the creator of the contract */
function MyToken(uint256 initialSupply, string tokenName, string tokenSymbol, uint8 decimalUnits) public {
balanceOf[msg.sender] = initialSupply; // Give the creator all initial tokens
name = tokenName; // Set the name for display purposes
symbol = tokenSymbol; // Set the symbol for display purposes
decimals = decimalUnits; // Amount of decimals for display purposes
}

/* Send coins */
function transfer(address _to, uint256 _value) public {
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
require(balanceOf[_to] + _value >= balanceOf[_to]); // Check for overflows
balanceOf[msg.sender] -= _value; // Subtract from the sender
balanceOf[_to] += _value; // Add the same to the recipient
/* Notify anyone listening that this transfer took place */
emit Transfer(msg.sender, _to, _value);
}
}

먼저 crowd sale을 진행하기 전에 crowd sale에서 사용할 토큰을 생성하고, crowd sale의 shares address에 등록해 준다.

crowd sale contract

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
pragma solidity ^0.4.16;

interface token {
function transfer(address receiver, uint amount) external;
}

contract Crowdsale {
address public beneficiary;
uint public fundingGoal;
uint public amountRaised;
uint public deadline; //마감 기한
uint public price;
token public tokenReward;
mapping(address => uint256) public balanceOf;
bool fundingGoalReached = false;
bool crowdsaleClosed = false;

event GoalReached(address recipient, uint totalAmountRaised);
event FundTransfer(address backer, uint amount, bool isContribution);

/**
* Constructor function
*
* Setup the owner
*/
function Crowdsale(
address ifSuccessfulSendTo, //
uint fundingGoalInEthers, //이더 단위의 모금 목표액
uint durationInMinutes, //분 단위의 모금 기간
uint etherCostOfEachToken, //각 토큰 당 단위 가격(이더 기준)
address addressOfTokenUsedAsReward //모금의 보상으로 주어질 위에서 만든 토큰의 주소
) public {
beneficiary = ifSuccessfulSendTo;
fundingGoal = fundingGoalInEthers * 1 ether;
deadline = now + durationInMinutes * 1 minutes;
price = etherCostOfEachToken * 1 ether;
tokenReward = token(addressOfTokenUsedAsReward);
}

/**
* Fallback function
*
* The function without name is the default function that is called whenever anyone sends funds to a contract
*/
function () payable public {
require(!crowdsaleClosed);
uint amount = msg.value;
balanceOf[msg.sender] += amount;
amountRaised += amount;
tokenReward.transfer(msg.sender, amount / price);
emit FundTransfer(msg.sender, amount, true);
}

modifier afterDeadline() { if (now >= deadline) _; }

/**
* Check if goal was reached
*
* Checks if the goal or time limit has been reached and ends the campaign
*/
function checkGoalReached() afterDeadline public {
if (amountRaised >= fundingGoal){
fundingGoalReached = true;
emit GoalReached(beneficiary, amountRaised);
}
crowdsaleClosed = true;
}


/**
* Withdraw the funds
*
* Checks to see if goal or time limit has been reached, and if so, and the funding goal was reached,
* sends the entire amount to the beneficiary. If goal was not reached, each contributor can withdraw
* the amount they contributed.
*/
function safeWithdrawal() afterDeadline public {
if (!fundingGoalReached) {
uint amount = balanceOf[msg.sender];
balanceOf[msg.sender] = 0;
if (amount > 0) {
if (msg.sender.send(amount)) {
emit FundTransfer(msg.sender, amount, false);
} else {
balanceOf[msg.sender] = amount;
}
}
}

if (fundingGoalReached && beneficiary == msg.sender) {
if (beneficiary.send(amountRaised)) {
emit FundTransfer(beneficiary, amountRaised, false);
} else {
//If we fail to send the funds to beneficiary, unlock funders balance
fundingGoalReached = false;
}
}
}
}

기본 contract 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pragma solidity ^0.4.20;

contract MyToken {
event Transfer(address indexed from, address indexed to, uint256 value);
/* This creates an array with all balances */
mapping (address => uint256) public balanceOf;
string public name;
string public symbol;
uint8 public decimals;
/* Initializes contract with initial supply tokens to the creator of the contract */
function MyToken(uint256 initialSupply, string tokenName, string tokenSymbol, uint8 decimalUnits) public {
balanceOf[msg.sender] = initialSupply; // Give the creator all initial tokens
name = tokenName; // Set the name for display purposes
symbol = tokenSymbol; // Set the symbol for display purposes
decimals = decimalUnits; // Amount of decimals for display purposes
}

/* Send coins */
function transfer(address _to, uint256 _value) public {
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
require(balanceOf[_to] + _value >= balanceOf[_to]); // Check for overflows
balanceOf[msg.sender] -= _value; // Subtract from the sender
balanceOf[_to] += _value; // Add the same to the recipient
/* Notify anyone listening that this transfer took place */
emit Transfer(msg.sender, _to, _value);
}
}

상속을 이용한 보다 심플란 contract 작성하기

is owned를 통해 상속을 하여 owner 변수와 onlyOwner modifier에 접근할 수 있게 되었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
pragma solidity ^0.4.20;

contract owned {
address public owner;

function owned() {
owner = msg.sender;
}

modifier onlyOwner {
require(msg.sender == owner);
_;
}

function transferOwnership(address newOwner) onlyOwner {
owner = newOwner;
}
}

contract MyToken is owned{
event Transfer(address indexed from, address indexed to, uint256 value);
/* This creates an array with all balances */
mapping (address => uint256) public balanceOf;
string public name;
string public symbol;
uint8 public decimals;
/* Initializes contract with initial supply tokens to the creator of the contract */
function MyToken(
uint256 initialSupply,
string tokenName,
uint8 decimalUnits,
string tokenSymbol,
address centralMinter
) {
if(centralMinter != 0 ) owner = centralMinter;
}

/* Send coins */
function transfer(address _to, uint256 _value) public {
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
require(balanceOf[_to] + _value >= balanceOf[_to]); // Check for overflows
balanceOf[msg.sender] -= _value; // Subtract from the sender
balanceOf[_to] += _value; // Add the same to the recipient
/* Notify anyone listening that this transfer took place */
emit Transfer(msg.sender, _to, _value);
}
}

#

향상된 최종 코인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
pragma solidity ^0.4.16;

contract owned {
address public owner;

function owned() public {
owner = msg.sender;
}

modifier onlyOwner {
require(msg.sender == owner);
_;
}

function transferOwnership(address newOwner) onlyOwner public {
owner = newOwner;
}
}

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; }

contract TokenERC20 {
// Public variables of the token
string public name;
string public symbol;
uint8 public decimals = 18;
// 18 decimals is the strongly suggested default, avoid changing it
uint256 public totalSupply;

// This creates an array with all balances
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;

// This generates a public event on the blockchain that will notify clients
event Transfer(address indexed from, address indexed to, uint256 value);

// This notifies clients about the amount burnt
event Burn(address indexed from, uint256 value);

/**
* Constrctor function
*
* Initializes contract with initial supply tokens to the creator of the contract
*/
function TokenERC20(
uint256 initialSupply,
string tokenName,
string tokenSymbol
) public {
totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount
balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens
name = tokenName; // Set the name for display purposes
symbol = tokenSymbol; // Set the symbol for display purposes
}

/**
* Internal transfer, only can be called by this contract
*/
function _transfer(address _from, address _to, uint _value) internal {
// Prevent transfer to 0x0 address. Use burn() instead
require(_to != 0x0);
// Check if the sender has enough
require(balanceOf[_from] >= _value);
// Check for overflows
require(balanceOf[_to] + _value > balanceOf[_to]);
// Save this for an assertion in the future
uint previousBalances = balanceOf[_from] + balanceOf[_to];
// Subtract from the sender
balanceOf[_from] -= _value;
// Add the same to the recipient
balanceOf[_to] += _value;
emit Transfer(_from, _to, _value);
// Asserts are used to use static analysis to find bugs in your code. They should never fail
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}

/**
* Transfer tokens
*
* Send `_value` tokens to `_to` from your account
*
* @param _to The address of the recipient
* @param _value the amount to send
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}

/**
* Transfer tokens from other address
*
* Send `_value` tokens to `_to` in behalf of `_from`
*
* @param _from The address of the sender
* @param _to The address of the recipient
* @param _value the amount to send
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]); // Check allowance
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}

/**
* Set allowance for other address
*
* Allows `_spender` to spend no more than `_value` tokens in your behalf
*
* @param _spender The address authorized to spend
* @param _value the max amount they can spend
*/
function approve(address _spender, uint256 _value) public
returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}

/**
* Set allowance for other address and notify
*
* Allows `_spender` to spend no more than `_value` tokens in your behalf, and then ping the contract about it
*
* @param _spender The address authorized to spend
* @param _value the max amount they can spend
* @param _extraData some extra information to send to the approved contract
*/
function approveAndCall(address _spender, uint256 _value, bytes _extraData)
public
returns (bool success) {
tokenRecipient spender = tokenRecipient(_spender);
if (approve(_spender, _value)) {
spender.receiveApproval(msg.sender, _value, this, _extraData);
return true;
}
}

/**
* Destroy tokens
*
* Remove `_value` tokens from the system irreversibly
*
* @param _value the amount of money to burn
*/
function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
balanceOf[msg.sender] -= _value; // Subtract from the sender
totalSupply -= _value; // Updates totalSupply
emit Burn(msg.sender, _value);
return true;
}

/**
* Destroy tokens from other account
*
* Remove `_value` tokens from the system irreversibly on behalf of `_from`.
*
* @param _from the address of the sender
* @param _value the amount of money to burn
*/
function burnFrom(address _from, uint256 _value) public returns (bool success) {
require(balanceOf[_from] >= _value); // Check if the targeted balance is enough
require(_value <= allowance[_from][msg.sender]); // Check allowance
balanceOf[_from] -= _value; // Subtract from the targeted balance
allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance
totalSupply -= _value; // Update totalSupply
emit Burn(_from, _value);
return true;
}
}

/******************************************/
/* ADVANCED TOKEN STARTS HERE */
/******************************************/

contract MyAdvancedToken is owned, TokenERC20 {

uint256 public sellPrice;
uint256 public buyPrice;

mapping (address => bool) public frozenAccount;

/* This generates a public event on the blockchain that will notify clients */
event FrozenFunds(address target, bool frozen);

/* Initializes contract with initial supply tokens to the creator of the contract */
function MyAdvancedToken(
uint256 initialSupply,
string tokenName,
string tokenSymbol
) TokenERC20(initialSupply, tokenName, tokenSymbol) public {}

/* Internal transfer, only can be called by this contract */
function _transfer(address _from, address _to, uint _value) internal {
require (_to != 0x0); // Prevent transfer to 0x0 address. Use burn() instead
require (balanceOf[_from] >= _value); // Check if the sender has enough
require (balanceOf[_to] + _value >= balanceOf[_to]); // Check for overflows
require(!frozenAccount[_from]); // Check if sender is frozen
require(!frozenAccount[_to]); // Check if recipient is frozen
balanceOf[_from] -= _value; // Subtract from the sender
balanceOf[_to] += _value; // Add the same to the recipient
emit Transfer(_from, _to, _value);
}

/// @notice Create `mintedAmount` tokens and send it to `target`
/// @param target Address to receive the tokens
/// @param mintedAmount the amount of tokens it will receive
function mintToken(address target, uint256 mintedAmount) onlyOwner public {
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
emit Transfer(0, this, mintedAmount);
emit Transfer(this, target, mintedAmount);
}

/// @notice `freeze? Prevent | Allow` `target` from sending & receiving tokens
/// @param target Address to be frozen
/// @param freeze either to freeze it or not
function freezeAccount(address target, bool freeze) onlyOwner public {
frozenAccount[target] = freeze;
emit FrozenFunds(target, freeze);
}

/// @notice Allow users to buy tokens for `newBuyPrice` eth and sell tokens for `newSellPrice` eth
/// @param newSellPrice Price the users can sell to the contract
/// @param newBuyPrice Price users can buy from the contract
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner public {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}

/// @notice Buy tokens from contract by sending ether
function buy() payable public {
uint amount = msg.value / buyPrice; // calculates the amount
_transfer(this, msg.sender, amount); // makes the transfers
}

/// @notice Sell `amount` tokens to contract
/// @param amount amount of tokens to be sold
function sell(uint256 amount) public {
require(address(this).balance >= amount * sellPrice); // checks if the contract has enough ether to buy
_transfer(msg.sender, this, amount); // makes the transfers
msg.sender.transfer(amount * sellPrice); // sends ether to the seller. It's important to do this last to avoid recursion attacks
}
}

Greeter Contract 만들기

Greeter.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
contract Mortal {
/* Define variable owner of the type address */
address owner;

/* This function is executed at initialization and sets the owner of the contract */
function Mortal() { owner = msg.sender; }

/* Function to recover the funds on the contract */
function kill() { if (msg.sender == owner) selfdestruct(owner); }
}

contract Greeter is Mortal {
/* Define variable greeting of the type string */
string greeting;

/* This runs when the contract is executed */
function Greeter(string _greeting) public {
greeting = _greeting;
}

/* Main function */
function greet() constant returns (string) {
return greeting;
}
}

solidity compiler 설치 및 컴파일 하기

solcjs 설치하기

1
npm install -g solc

컴파일 하기

1
solcjs -o target --bin --abi Greeter.sol

js 스크립트 작성

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var greeterFactory = eth.contract(<contents of the file Greeter.abi>)

var greeterCompiled = "0x" + "<contents of the file Greeter.bin>"

var _greeting = "Hello World!"

var greeter = greeterFactory.new(_greeting,{from:eth.accounts[0],data:greeterCompiled,gas:47000000}, function(e, contract){
if(e) {
console.error(e); // If something goes wrong, at least we'll know.
return;
}

if(!contract.address) {
console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");

} else {
console.log("Contract mined! Address: " + contract.address);
console.log(contract);
}
})

geth console 에서 스크립트 실행

1
loadScript("main.js")

채굴 시작 및 greeter 실행

1
miner.start()

채굴이 완료되면 contract에 address가 부여되고 계약이 발행된다.

이제는 블록체인 상에 greeter라는 봇이 생성되었으므로 다음과 같이 자유롭게 호출하여 사용이 가능하다.

1
greeter.greet();

블록체인이란?

블록체인이란 수정 불가능한 분산형 원장을 의미하며, 특정 정보의 목록이 다수의 컴퓨터에 동시에 저장되고 수정될 수 있는 시스템을 의미합니다.
기존의 시스템은 단일 서버에 모든 정보가 저장되기 때문에 서비스 사용자들이 정보를 알 수 없고, 기업이나 혹은 악한 의도를 가진 사용자에 의해 플랫폼이 훼손될 수 있어 안전하지 못했다면, 블록체인은 수정이 불가능하기 때문에 신뢰할 수 있는 특징이 있습니다.

이러한 블록체인은 반드시 BFT를 가져야 하는데

기본 개념

이더리움에서의 기본 단위는 account 이며 이러한 account는 다음과 같이 두 종류로 나뉘게 됩니다.

  1. EOA(External Owned Account)
  2. Countract Account

여기서 EOA란 계정정보를 포함한 계약을 의미하며 외부 사용자가 private key 를 통해 제어합니다.
이러한 EOA 는 Contract Account의 내부 함수들을 사용할 수 있으며 Contract Account의 내부 함수들은 오직 EOA에 의해서만 호출될 수 있습니다.

Install ethereum command line interface (Ethereum CLI)

  1. 윈도우 go-ethereum(geth) 다운로드
    다운로드

  2. geth 실행

    1
    geth console

테스트 네트워크 구축하기

  1. genesis.json 생성하기

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    "config": {
    "chainId": 15,
    "homesteadBlock": 0,
    "eip155Block": 0,
    "eip158Block": 0
    },
    "difficulty": "20",
    "gasLimit": "2100000",
    "alloc": {
    "7df9a875a174b3bc565e6424a0050ebc1b2d1d82":
    { "balance": "300000" },
    "f41c74c9ae680c1aa78f42e5647a62f353b7bdde":
    { "balance": "400000" }
    }
    }
  2. 블록체인 초기화 하기

    1
    geth init genesis.json --datadir ethereum_private
  3. geth console 로 geth 노드 열기

    1
    geth --networkid 123 --datadir ethereum_private console
  4. account 만들기

    1
    personal.newAccount("testuser1")
  5. mining 시작하기

    1
    miner.start()
  6. 자신이 위치한 노드 확인하기

    1
    admin.nodeInfo.enode
  7. 다른 노드 연결하기
    위에서 받은 nodeurl을 받아 다른 클라이언트에서 다음 명령어를 실행한다.

    1
    admin.addPeer("_nodeurl_")

이를 통해 노드와 노드가 연결될 수 있다.

  1. 다른 클라이언트들의 로그 확인
    1
    geth console 2>>geth.log

참조 블로그

이더리움 공식 가이드

geth 명령어

geth 가동

1
2
3
geth --datadir ~/.ethereum_private init ~/dev/genesis.json

geth --fast --cache 512 --ipcpath ~/Library/Ethereum/geth.ipc --networkid 1234 --datadir ~/.ethereum_private console

계좌 생성

1
2
personal.newAccount("testuser1")
personal.newAccount("testuser2")

계좌 확인

1
eth.accounts

잔고 확인

1
eth.getBalance(eth.accounts[0])

블록 수 확인

1
eth.blockNumber

채굴 시작하기

1
miner.start()

채굴 정지

1
miner.stop()

트랜잭션 발행

1
2
3
4
5
6
7
8
eth.sendTransaction({from: 'id', to:'id', value:web3.toWei(1,"ether")})
please unlocak account id

or

eth.sendTransaction({from:eth.accounts[0], to:eth.accounts[1], value:web3.toWei(1,"ether")})

트랜잭션 발행하는 계좌의 암호를 입력

geth 기동

1
2
mkdir eth_data
geth --networkid "123" --rpc --rpcaddr "222.239.251.75" --rpcport 8545 --rpccorsdomain "*" --rpcapi "net,eth,web3,personal" --datadir "eth_data" --olympic console

기본 원리

proof of work -> 어려운 문제를 푼 사람이 토큰을 가져간다.
풀기는 어렵지만 증명은 쉬운 해쉬를 사용한다.

컴퓨터는 nonce에 무작위값을 계속 대입하여 해쉬를 풀고 이를 mining 이라고 한다.
mine이 된 이후에는 점점 난이도가 올라간다.

이더리움의 활용

이더리움은 개인과 개인간의 약속에 기반한 모든 경제적 모델을 온라인 상에서 도입하는 것을 매우 간편하게 만들어 준다.
가령 국가가 특정 공약을 내세우고 이를 위한 자금을 이더리움에서 모금한다고 하면, 해당 공약이 이루어지지 않을 경우 스마트 계약에 따라 참여자에게 돈이 재분배 되게 되는 방식을 사용하는 예처럼 스스로에게 약속 불이행시 제약을 둠으로써 신뢰도 높은 거래를 할 수 있다.

즉, 이더리움은 실제 경제모델과 정치모델을 모두 온라인 상에서 신뢰도있게 구현이 가능하다는 측면에서 그 가치가 높다.


Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×