#4.1 Our First Block
Blockchain은 말 그대로 "block들의 chain"이다.
type block struct {
data string
hash string
prevHash string
}
구현할 blockchain은 데이터를 오직 block 안에만 저장한다.
데이터는 string이다.
이 block의 hash를 저장해줘야 한다. block의 특징이다. string 형식의 hash를 추가한다.
block 간의 연결 또한 hash로 되어 있지만, 이 경우에는 block의 이전 hash를 저장하는 것으로 구현한다.
hash function이란 one-way function를 기억할 것이다.
one-way function이란 단방향으로만 실행할 수 있고, 다른 방향으로 실행 불가능한 function이다.
"sexy" = h_fn(x) => "sad6d6s4f8ds918cv1dd8bg4fb4"
값을 hash function에 넘겨준다면, 랜덤한 값을 출력해 줄 것이다. 이것이 hash이다.
이 함수는 결정론적이다. 같은 입력값은 항상 같은 출력을 얻게 될 거라는 의미이다.
"sexyy" = h_fn(x) => "df654g56f4dg89fd1v8f1v81fdv1"
입력값을 조금이라도 바꾸면 정말 다른 출력값을 얻게 된다.
이 function을 단방향이라고 부르는 이유는 "sexy"라는 입력값으로 hash 값을 얻지만, 반대로는 값을 얻을 수 없다.
1)
B1
b1Hash = data + prevHash
2)
B1
b1Hash = (data + " ")
3)
B2
b2Hash = (data + b1Hash)
4)
B3
b3Hash = (data + b2Hash)
block들이 어떻게 다른 block들과 연결되는지 생각해보자.
block 1번의 hash는 block 1번의 data + 이전 block의 hash 값으로 계산된다.
그러나 이건 block 1번이다. 이전 block은 없다.
그러니 이전 block의 hash 값은 없음(" ")으로 표시된다.
block 2번은 block 1번과 비슷한데, block 2번 hash 값을 가지고 있고, data + block 1번의 hash 값으로 계산된다.
block 3번의 hash값은 block 3번의 data + block 2번의 hash값으로 계산된다.
지금 당장은 data에 관해서만 다루고 있다. 나중에는 transaction이라던지 다른 부분도 구현할 것이다.
여기서 볼 수 있듯이 이 block들은 서로 연결되어 있다.
B1
b1Hash = (data + "x")
B2
b2Hash = (data + b1Hash)
B3
b3Hash = (data + b2Hash)
block 1번의 hash가 바뀌었다면, block 2번의 hash또한 바뀐다. block 2번의 hash를 계산하기 위해선 block 1번의 hash가 필요하기 때문이다. block 1번의 데이터가 변경되었다면, block 3번의 데이터 또한 변경되어야 한다. 안그러면 block들이 유효하지 않다.
B1
b1Hash = (data + "")
B2
b2Hash = (data.. + b1Hash)
B3
b3Hash = (data + b2Hash)
block 5개로 구성된 blockchain 있다고 가정하고, 가운데에 있는 block을 변경했다고 가정하면, 우린 이 데이터를 hash에 넣는다. 뒤에 따라오는 모든 block들이 무효가 된다. 데이터가 변경되었으니, 다른 모든 block의 구조와 hash들이 변경된다. 이게 block들을 서로 연결하는 방법이다.
func main() {
genesisBlock := block{"Genesis Block", "", ""}
genesisBlock.hash = fn(genesisBlock.data + genesisBlock.prevHash)
}
func main() {
genesisBlock := block{"Genesis Block", "", ""}
genesisBlock.hash = SHA256(genesisBlock.data + genesisBlock.prevHash)
}
첫번째 block을 hash해보자. 그러나 chain을 생성하진 않을 것이다.
genesisBlock은 blockchain의 첫번째 block이다.
blockchain은 어디서든 시작을 해야 한다.
block을 초기화시켜주고, data가 필요하니 "Genesis Block"이라고 값을 넣는다.
hash가 필요하나 없으니 빈 값으로 둔다.
그리고 이전 hash값이 필요하다. 이전 block도 없으니 이 값도 빈 값으로 초기화한다.
함수를 만들어 genesisBlock 데이터와 이전 hash 값을 넘겨서 hash를 만들 것이다.
이전 hash 값은 없지만 같이 넘겨준다.
block을 생성하고 hash하는 방법이다.
많은 암호화폐들이 이용하는 hash방법은 SHA256 알고리즘을 사용한다.
Go언어는 이미 SHA256를 이미 구현해놨다.
우리가 할 수 있는건 sha256의 Sum256함수를 호출하고 이건 data를 hash할 수 있게 해준다.
인수로 string을 넘겨줄 수 없다. 이곳에 genesisBlock.data를 넘기면 컴파일러가 짜증을 낼 것이다.
genesisBlock.prevHash 값 또한 넘겨준다. 그러나 hash 불가능하다.
data와 prevHash를 hash할 수 없는 이유는 두 값들이 전부 string이기 때문이다.
이 function은 byte 배열을 받는다.byte의 slice.
byte의 배열을 return한다.
byte의 slice와 string의 차이점은 무엇인가? string도 byte의 slice의 일종이긴 하다.
string을 for loop로 반복하면 각각 분리된 byte를 볼 수 있다.
package main
import "fmt"
func main() {
for _, aByte := range "Genesis Block" {
fmt.Println(aByte)
}
}
https://go.dev/play/p/X1cG5GJackl
package main
import "fmt"
func main() {
for _, aByte := range "Genesis Block" {
fmt.Printf("%b",aByte)
}
}
이진법으로 출력한다.
이게 hash function이 실제로 원하는 값이다.
https://go.dev/play/p/zrR38log4vx
왜냐하면 Go언어의 string 값은 불변이다.
Immutable - 이 값을 우리가 변경할 수 없다. 더 크게 만들 수도 없고, 더 작게 만들 수도 없다.
마치 byte의 배열과 비슷하다. 배열은 고정된 길이를 가지고 있다.
sum256 함수 내에서 이 byte를 변경한다는 의미일 것이다.
string에서 byte의 slice를 만들어내는건 굉장히 쉽다.
string을 또는 genesisBlock.data를 byte의 slice로 변환하는 방법이다.
hello를 hash하고 싶다면, 괄호안에 Hello만 넣으면 된다.
그러나 이 함수는 아직 동작하지 않는다.
func main() {
genesisBlock := block{"Genesis Block", "", ""}
genesisBlock.hash = sha256.Sum256([]byte(genesisBlock.data+genesisBlock.prevHash))
}
우린 data와 prevHash값을 hash하고 싶은 것이다. byte constructor를 사용해서 string을 byte의 slice로 변환한다.
아직 함수가 불평을 하는데, GenesisBlock.hash는 string이다. 그리고 이 함수가 return하는 값은 byte의 32자리 배열이다.
이 32짜리 byte를 string으로 변환해줘야 한다.
func main() {
genesisBlock := block{"Genesis Block", "", ""}
hash := sha256.Sum256([]byte(genesisBlock.data + genesisBlock.prevHash))
fmt.Println(hash)
//genesisBlock.hash = sha256.Sum256([]byte(genesisBlock.data+genesisBlock.prevHash))
}
[137 235 10 192 49 166 61 36 33 205 5 162 251 228 31 62 163 95 92 55 18 202 131 156 191 107 133 196 238 7 183 163]
우리가 얻는 것은 이 byte들의 slice 배열을 얻을 수 있다.
우리가 관심있는 것은 이 값들에서 string을 얻어내는 것이다.
bitcoin explorer 나 ethereum explorer를 봤다면 거의 모든 hash는 16진수로 되어있다.
16진수란건, base16을 의미한다.
https://go.dev/play/p/Vzx2piBhy3S
func main() {
genesisBlock := block{"Genesis Block", "", ""}
hash := sha256.Sum256([]byte(genesisBlock.data + genesisBlock.prevHash))
fmt.Printf("%x", hash)
//genesisBlock.hash = sha256.Sum256([]byte(genesisBlock.data+genesisBlock.prevHash))
}
이 hash값을 16진수로 변경해보자 이를 위해 printf를 사용한다.
이 hash값을 "%x"로 포맷할 것이다.
89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3
16진수로 포맷된 string값.
hash를 "%x"를 통해 16진수로 출력한다.
func main() {
genesisBlock := block{"Genesis Block", "", ""}
hash := sha256.Sum256([]byte(genesisBlock.data + genesisBlock.prevHash))
hexHash := fmt.Sprintf("%x", hash)
genesisBlock.hash = hexHash
fmt.Println(genesisBlock)
}
Sprintf는 방금 전 콘솔에서 했던 것처럼 string을 format해주지만, 콘솔에 출력을 해주지는 않고 string을 return한다.
이제 genesisBlock.hash에 hexHash값을 대입할 수 있다
왜냐하면 우린 이 hash값의 byte값을 가져와야 하고 이 byte값을 string으로 변환해야 하며 이 hash값을 우리의 block 안에 저장한다.
{Genesis Block 89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3 }
첫번째 Block과 hash이다.
무언가의 byte를 string으로 변환해야 하는 일을 해야 한다.
자동화되어있지도 않다.
두번째 block을 만들고 싶다면 또 다시 코드를 짜야 한다.
출처 : 노마드코더 https://nomadcoders.co/nomadcoin
댓글 영역