티스토리 뷰

본 게시글에서는 저서 '밑바닥부터 시작하는 비트코인'의 Python으로 작성된 예제 코드를 Go로 컨버팅 하여 작성하였습니다.

📺 시리즈

2023.08.25 - [Go/Blockchain] - 밑바닥부터 시작하는 비트코인 - 1장 유한체

2023.08.27 - [Go/Blockchain] - 밑바닥부터 시작하는 비트코인 - 2장 타원곡선

2023.08.30 - [Go/Blockchain] - 밑바닥부터 시작하는 비트코인 - 3장 타원곡선 암호


🐱‍👤 전체 코드

 

GitHub - piatoss3612/bitcoin-from-scratch: 밑바닥부터 시작하는 비트코인을 읽고 Go로 구현해보는 프로젝

밑바닥부터 시작하는 비트코인을 읽고 Go로 구현해보는 프로젝트. Contribute to piatoss3612/bitcoin-from-scratch development by creating an account on GitHub.

github.com


🚝 직렬화

 직렬화(serialization)는 데이터 구조 또는 객체를 로컬 또는 다른 컴퓨터 환경에 전달 및 저장하고, 필요할 때 불러와 기존의 데이터 구조 또는 객체를 재구성할 수 있는 형식으로 변환하는 과정입니다. 일반적으로 바이트(byte) 단위로 데이터를 주고받는 바이트스트림(byte stream)을 사용하고 Go에서는 바이트 슬라이스([]byte)가 주로 사용됩니다. 반대로 일련의 바이트로부터 데이터 구조를 추출하는 것을 역직렬화(deserialization)라고 합니다.


⛺ 빅엔디언과 리틀엔디언

 빅엔디언과 리틀엔디언은 숫자를 저장할 때 바이트 스트림으로 변환하는 두 가지 방법입니다. 

 

  그림 오른쪽의 빅엔디언은 아라비아 숫자를 표기하는 것과 마찬가지로 가장 큰 자리의 바이트를 바이트 스트림의 왼쪽에서 오른쪽으로 채워넣는 방식입니다. 이 방식은 디버깅이 용이하다는 장점이 있습니다.

 

 그림 왼쪽의 리틀엔디언은 가장 큰 자리의 바이트를 바이트 스트림의 오른쪽에서부터 왼쪽으로 채워넣는 방식입니다. 이 방식은 컴퓨터의 연산이 용이하다는 장점이 있습니다.

 

 비트코인은 두 가지 방식은 모두 사용하고 있으나, 어디서 무엇을 왜 사용해야 하는지에 대한 명확한 규칙은 없습니다.


🚈 SEC 형식 직렬화

 SEC(Standards for Efficient Cryptography)는 ECDSA 공개키를 직렬화하는 표준안입니다. 

 

 SEC 형식에는 비압축식과 압축식 두 가지가 있습니다.

 

1) 비압축식

  점 P = (x, y)를 비압축식으로 직렬화하는 방법은 다음과 같습니다.

  1. 0x04의 1바이트를 접두부로 시작합니다.
  2. x 좌표를 32바이트 빅엔디언 정수로 표현합니다.
  3. y 좌표를 32바이트 빅엔디언 정수로 표현합니다.

 비압축식은 총 65바이트 크기를 사용합니다. 여기서  x 좌표와 y 좌표를 32바이트 빅엔디언으로 표현하기 위해 FillBytes 메서드를 사용해서 비어있는 자리를 0x00으로 채웠습니다.

// secp256k1 타원곡선의 점의 직렬화 함수
func (p s256Point) SEC() []byte {
	return append([]byte{0x04},
		append(
			p.x.Num().FillBytes(make([]byte, 32)),
			p.y.Num().FillBytes(make([]byte, 32))...)...)
}

연습문제

더보기
package main

import (
	"chapter04/ecc"
	"encoding/hex"
	"fmt"
	"math/big"
)

func main() {
	e1 := big.NewInt(5000)
	pvk1, err := ecc.NewS256PrivateKey(e1.Bytes())
	if err != nil {
		panic(err)
	}

	fmt.Println(hex.EncodeToString(pvk1.Point().SEC()))

	e2 := big.NewInt(0).Exp(big.NewInt(2018), big.NewInt(5), nil)
	pvk2, err := ecc.NewS256PrivateKey(e2.Bytes())
	if err != nil {
		panic(err)
	}

	fmt.Println(hex.EncodeToString(pvk2.Point().SEC()))

	e3, _ := big.NewInt(0).SetString("deadbeef12345", 16)
	pvk3, err := ecc.NewS256PrivateKey(e3.Bytes())
	if err != nil {
		panic(err)
	}

	fmt.Println(hex.EncodeToString(pvk3.Point().SEC()))
}
$ go run main.go 
04ffe558e388852f0120e46af2d1b370f85854a8eb0841811ece0e3e03d282d57c315dc72890a4f10a1481c031b03b351b0dc79901ca18a00cf009dbdb157a1d10
04027f3da1918455e03c46f659266a1bb5204e959db7364d2f473bdf8f0a13cc9dff87647fd023c13b4a4994f17691895806e1b40b57f4fd22581a4f46851f3b06
04d90cd625ee87dd38656dd95cf79f65f60f7273b67d3096e68bd81e4f5342691f842efa762fd59961d0e99803c61edba8b3e3f7dc3a341836f97733aebf987121

2) 압축식

 압축식으로 직렬화하는 경우에는 1바이트 접두부와 x 좌표만을 사용하므로 총 33바이트 크기만을 사용합니다.

 

 그런데 타원곡선의 y2 항으로 인해 동일한 x 좌푯값을 가지는 점이  두 개 존재합니다. 이렇게 되면 x 좌푯값만 가지고는 역직렬화를 할 때 y 좌푯값이 둘 중 어떤 것인지 알 수가 없습니다. 따라서 접두부 1바이트로 y 좌푯값이 짝수인지 홀수인지를 표시해줘야 합니다.

 

 우리는 유한체에서 정의된 타원곡선을 사용하고 있고 동일한 x 좌푯값에 대해 (x, y), (x, p - y)라는 두 개의 점이 존재합니다. 유한체의 위수 p는 소수이기에 홀수이기도 합니다. 만약 y가 홀수라면 p - y는 짝수가 될 것이고, 짝수라면 p - y는 홀수가 되겠죠. p와 p - y 중 하나는 홀수이고 하나는 짝수여야 하며, y 좌표가 홀수인지 짝수인지만 알 수 있다면 x 좌표를 사용해서 계산하면 됩니다.

 

 y 좌푯값을 홀수인지 짝수인지로 표시하는 접두부 1바이트로 압축하였으므로 이를 압축식 SEC 형식이라고 합니다.

 

 점 P = (x, y)를 압축식으로 직렬화하는 방법은 다음과 같습니다.

  1. y값이 짝수면 0x02, 홀수면 0x03인 1바이트 접두부로 시작합니다.
  2. x 좌표를 32바이트 빅엔디언 정수로 표현합니다.
// secp256k1 타원곡선의 점의 직렬화 함수
func (p s256Point) SEC(compressed bool) []byte {
	if compressed {
		// y좌표의 LSB가 0인 경우, 0x02를 prefix로 사용
		if p.y.Num().Bit(0) == 0 {
			return append([]byte{0x02}, p.x.Num().FillBytes(make([]byte, 32))...)
		}
		// y좌표의 LSB가 1인 경우, 0x03을 prefix로 사용
		return append([]byte{0x03}, p.x.Num().FillBytes(make([]byte, 32))...)
	}

	return append([]byte{0x04},
		append(
			p.x.Num().FillBytes(make([]byte, 32)),
			p.y.Num().FillBytes(make([]byte, 32))...)...)
}

3) 역직렬화

비압축식

  1. 비압축 SEC 형식은 순서대로 x, y 값을 읽으면 됩니다.

압축식

  1. 압축 SEC 형식은 y2 = x3 + 7에 x 값을 대입하여 y2 값을 구합니다.
  2. y2의 제곱근 beta를 구한 뒤, p - beta를 구하여 짝수와 홀수를 구분해 줍니다.
  3. y 값이 짝수인지 홀수인지를 확인하고 일치하는 값으로 좌표를 생성하여 반환합니다.
// sec 바이트 슬라이스를 타원곡선 위의 점으로 변환하는 함수
func Parse(sec []byte) (Point, error) {
	// prefix가 0x04인 경우, 비압축 포맷
	if sec[0] == 0x04 {
		x, err := NewS256FieldElement(new(big.Int).SetBytes(sec[1:33]))
		if err != nil {
			return nil, err
		}

		y, err := NewS256FieldElement(new(big.Int).SetBytes(sec[33:65]))
		if err != nil {
			return nil, err
		}

		return NewS256Point(x, y)
	}

	// prefix가 0x02 또는 0x03인 경우, 압축 포맷
	if sec[0] == 0x02 || sec[0] == 0x03 {
		x, err := NewS256FieldElement(new(big.Int).SetBytes(sec[1:]))
		if err != nil {
			return nil, err
		}

		// y^2 = x^3 + 7
		alpha := addBN(powBN(x.Num(), big.NewInt(3), P), big.NewInt(int64(B)), P)
		// y = sqrt(alpha)
		beta := sqrtBN(alpha, P)

		var even, odd *big.Int

		// y의 LSB가 짝수인지 홀수인지 확인
		if byte(beta.Bit(0)) == 0x00 {
			even = beta
			odd = subBN(P, beta, P)
		} else {
			odd = beta
			even = subBN(P, beta, P)
		}

		// prefix가 0x02인 경우, y의 LSB가 짝수인 값을 사용
		if sec[0] == 0x02 {
			y, err := NewS256FieldElement(even)
			if err != nil {
				return nil, err
			}

			return NewS256Point(x, y)
		}

		// prefix가 0x03인 경우, y의 LSB가 홀수인 값을 사용
		y, err := NewS256FieldElement(odd)
		if err != nil {
			return nil, err
		}

		return NewS256Point(x, y)
	}

	return nil, errors.New("invalid sec format")
}

// 원소값의 제곱근을 구하는 함수
func sqrtBN(x, mod *big.Int) *big.Int {
	return big.NewInt(0).ModSqrt(x, mod)
}

Python 예제와의 차이점

  • big.Int 타입의 자체 메서드를 사용해서 y2의 제곱근을 구했습니다.

🚄 DER 서명 형식

 DER(Distinguished Encoding Rules)은 서명을 직렬화하는 표준안입니다.

 

 서명은 s 값을 r 값에서부터 온전히 유도할 수 없기 때문에 SEC 형식을 사용할 수 없습니다.

 

 DER 서명 형식은 아래처럼 정의됩니다.

  1. 0x03 바이트로 시작합니다.
  2. 서명의 길이를 붙입니다. 보통 0x44 또는 0x45(68 또는 69)가 됩니다.
  3. r 값의 시작을 가리키는 표식으로 0x02를 붙입니다.
  4. 빅엔디언 정수로 r 값을 표현합니다. 그 결과의 첫 번째 바이트가 0x80보다 크거나 같으면 0x00을 앞에 붙입니다. 그리고 바이트 단위의 길이를 그 앞에 붙입니다. 이렇게 구한 값을 3의 뒤에 붙입니다.
  5. s 값의 시작을 가리키는 표식으로 0x02를 붙입니다.
  6. 빅엔디언 정수로 s 값을 표현합니다. 그 결과의 첫 번째 바이트가 0x80보다 크거나 같으면 0x00을 앞에 붙입니다. 그리고 바이트 단위의 길이를 그 앞에 붙입니다. 이렇게 구한 값을 5의 뒤에 붙입니다.

 4번과 5번에서 첫 번째 바이트가 0x80보다 크거나 같은 경우 0x00을 추가하는 이유는 DER 형식이 음숫값(최상위 비트가 1인 경우 음수를 의미)도 수용 가능한 일반 형식이기 때문입니다. ECDSA 서명에서 모든 수는 양수이므로 0x00을 추가하여 최상위 비트를 0으로 만들어 양수로 인식하게 합니다.

 

 DER 형식은 최대 72바이트까지 길어질 수 있습니다.

// secp256k1 서명을 DER 형식으로 반환하는 함수
func (sig s256Signature) DER() []byte {
	r := sig.r.Bytes()
	s := sig.s.Bytes()

	// r, s의 비어있는 바이트를 제거
	r = bytes.TrimLeftFunc(r, func(r rune) bool {
		return r == 0x00
	})
	s = bytes.TrimLeftFunc(s, func(r rune) bool {
		return r == 0x00
	})

	// r의 첫번째 바이트가 0x80 이상이면 0x00을 추가
	if r[0]&0x80 != 0 {
		r = append([]byte{0x00}, r...)
	}

	// s의 첫번째 바이트가 0x80 이상이면 0x00을 추가
	if s[0]&0x80 != 0 {
		s = append([]byte{0x00}, s...)
	}

	// r, s의 길이를 1바이트로 표현
	rLen := byte(len(r))
	sLen := byte(len(s))

	r = append([]byte{0x02, rLen}, r...)
	s = append([]byte{0x02, sLen}, s...)

	// r, s를 연결
	result := append(r, s...)

	// DER 형식의 서명을 반환
	return append([]byte{0x30, byte(len(result))}, result...)
}

서명을 파싱하는 함수 (2023.09.12 추가)

// 서명을 파싱하는 함수
func ParseSignature(der []byte) (Signature, error) {
	buf := bytes.NewBuffer(der) // der을 읽기 위한 버퍼 생성

	compound := buf.Next(1)[0]

	// 서명이 0x30으로 시작하는지 확인
	if compound != 0x30 {
		return nil, errors.New("invalid compound byte")
	}

	length := buf.Next(1)[0] // 서명의 길이

	// 서명의 길이가 der의 길이와 일치하는지 확인
	if int(length)+2 != len(der) {
		return nil, errors.New("invalid length")
	}

	marker := buf.Next(1)[0] // r의 시작을 나타내는 마커
	if marker != 0x02 {      // r의 시작을 나타내는 마커는 0x02 이어야 함
		return nil, errors.New("invalid marker for r")
	}

	rLength := buf.Next(1)[0]   // r의 길이
	r := buf.Next(int(rLength)) // r

	marker = buf.Next(1)[0] // s의 시작을 나타내는 마커
	if marker != 0x02 {     // s의 시작을 나타내는 마커는 0x02 이어야 함
		return nil, errors.New("invalid marker for s")
	}

	sLength := buf.Next(1)[0]   // s의 길이
	s := buf.Next(int(sLength)) // s

	// 서명의 길이가 der의 길이와 일치하는지 확인
	if len(der) != 6+int(rLength)+int(sLength) {
		return nil, errors.New("invalid signature")
	}

	// S256Signature 타입의 인스턴스를 생성하여 반환
	return NewS256Signature(
		big.NewInt(0).SetBytes(r),
		big.NewInt(0).SetBytes(s),
	), nil
}

연습문제

더보기
package main

import (
	"chapter04/ecc"
	"fmt"
	"math/big"
)

func main() {
	r, _ := new(big.Int).SetString("37206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c6", 16)
	s, _ := new(big.Int).SetString("8ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec", 16)

	sig := ecc.NewS256Signature(r, s)

	der := sig.DER()

	fmt.Printf("DER: %x\n", der)
}
$ go run main.go 
DER: 3045022037206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c60221008ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec

🚅 비트코인 주소 및 WIF 형식

1) 비트코인 주소

 Alice가 Bob에게 돈을 송금하려면 Bob의 계좌번호를 알아야 합니다. 비트코인에서 이 계좌번호의 역할을 하는 것이 비트코인 주소입니다. 비트코인 주소는 공개키를 사용합니다. 그러나 공개키를 SEC 형식으로 직렬화하여 사용하기에는 길이가 너무 길고(33 또는 65) 바이너리 형식으로 가독성이 떨어집니다. 이에 사토시 나카모토는 Base58 부호화를 도입합니다.

2) Base58 부호화

 숫자 0과 알파벳 O, 알파벳 l과 알파벳 I를 제외한 아라비아 숫자 9개와 알파벳 대소문자 49개, 총 58개의 문자로 숫자를 표현하는 방식입니다. 

 

 Base58 부호화의 장점은 다음과 같습니다.

  1. SEC 형식을 16진수로 표현하면 4비트마다 하나의 숫자를 표현하는 것과 달리, Base58 부호화는 5.86비트마다 하나의 숫자를 표현하여 전체 길이를 줄일 수 있습니다.
  2. 유사한 문자들(0, O, l, I)을 제외하여 가독성이 높습니다.
  3. (비트코인 주소를 Base58 부호화를 하기 전에) sha256, ripemd160 해시 함수를 사용하여 보안성이 높습니다.

 그러나 이마저도 그다지 편리한 방식은 아니므로 점점 사장되고 있으며 더 좋은 방법으로 Bech32 표준이 제안되었습니다. 이는 13장 '세그윗'에서 다룹니다.

var base58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" // base58 인코딩에 사용할 문자열

// 바이트 슬라이스를 base58로 인코딩하는 함수
func EncodeBase58(s []byte) string {
	// 앞에 몇 바이트가 0x00인지 확인
	count := 0
	for _, c := range s {
		if c == 0x00 {
			count++
		} else {
			break
		}
	}

	n := big.NewInt(0).SetBytes(s) // 바이트 슬라이스를 big.Int로 변환

	// 0x00이 몇개 있는지에 따라 0x01을 count만큼 반복하여 prefix를 만듬
	// 이 prefix는 pay-to-pubkey-hash(P2PKH)에서 필요함 (6장에서 설명)
	prefix := strings.Repeat("1", count)

	result := strings.Builder{}

	// n을 58로 나눈 나머지에 해당하는 문자를 base58Alphabet에서 찾아서 문자열을 만듦
	for n.Cmp(big.NewInt(0)) == 1 {
		mod := big.NewInt(0)
		n.DivMod(n, big.NewInt(58), mod)
		result.WriteByte(base58Alphabet[mod.Int64()])
	}

	// prefix를 붙임
	result.WriteString(prefix)

	// 문자열을 뒤집음
	resultBytes := ReverseBytes(StringToBytes(result.String()))

	return BytesToString((resultBytes))
}

// 바이트 슬라이스를 뒤집는 함수
func ReverseBytes(b []byte) []byte {
	result := make([]byte, len(b))

	for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
		result[i], result[j] = b[j], b[i]
	}

	return result
}

3) 비트코인 주소 형식

 비트코인 주소를 생성하는 방법은 아래와 같습니다.

  1. 메인넷 주소는 0x00, 테스트넷 주소는 0x6f로 시작합니다.
  2. SEC 형식 주소를 sha256 해시함수에 넣어 나온 출력을 다시 ripemd160 해시함수에 넣어 160비트 출력을 얻습니다. 이 방법을 hash160이라고 합니다.
  3. 1의 접두 바이트와 2의 해시값을 합칩니다. (8비트 + 160비트 = 168비트)
  4. 3의 결과를 hash256 함수에 넣어 나온 출력의 첫 4바이트를 체크섬으로 취합니다.
  5. 3의 결과에 4를 붙이고(168비트 + 32비트 = 200비트)  이를 Base58 부호화합니다.
  6. 길이가 34((200 / 5.86 = 34)인 문자열을 반환합니다.
ripemd160 해시함수를 사용하기 위해서는 다음 패키지를 설치해야 합니다.
go get golang.org/x/crypto
// 바이트 슬라이스의 체크섬을 추가하여 base58로 인코딩하는 함수
func EncodeBase58Checksum(b []byte) string {
	return EncodeBase58(append(b, Hash256(b)[:4]...))
}

// ripemd160(sha256(b))를 구하는 함수
func Hash160(b []byte) []byte {
	// sha256 해시값을 구함
	h256 := sha256.New()
	_, _ = h256.Write(b)
	hash1 := h256.Sum(nil)

	// sha256 해시값을 사용하여 ripemd160 해시값을 구함
	ripemd160 := ripemd160.New()
	_, _ = ripemd160.Write(hash1)

	return ripemd160.Sum(nil)
}
// secp256k1 타원곡선의 점의 SEC 형식을 160비트 해시로 변환하는 함수
func (p s256Point) Hash160(compressed bool) []byte {
	return Hash160(p.SEC(compressed))
}

// secp256k1 타원곡선의 점을 주소로 변환하는 함수
func (p s256Point) Address(compressed bool, testnet bool) string {
	h160 := p.Hash160(compressed) // 타원곡선 점의 SEC 형식을 160비트 해시로 변환

	if testnet {
		h160 = append([]byte{0x6f}, h160...) // testnet 주소의 prefix는 0x6f
	} else {
		h160 = append([]byte{0x00}, h160...) // mainnet 주소의 prefix는 0x00
	}

	return EncodeBase58Checksum(h160) // Base58Checksum 인코딩
}

연습문제

더보기
package main

import (
	"chapter04/ecc"
	"fmt"
	"math/big"
)

func main() {
	e1 := big.NewInt(5002)

	pvk1, err := ecc.NewS256PrivateKey(e1.Bytes())
	if err != nil {
		panic(err)
	}

	addr1 := pvk1.Point().Address(false, true)

	fmt.Printf("addr1: %s\n", addr1)

	e2 := big.NewInt(0).Exp(big.NewInt(2020), big.NewInt(5), nil)

	pvk2, err := ecc.NewS256PrivateKey(e2.Bytes())
	if err != nil {
		panic(err)
	}

	addr2 := pvk2.Point().Address(true, true)

	fmt.Printf("addr2: %s\n", addr2)

	e3, _ := new(big.Int).SetString("12345deadbeef", 16)

	pvk3, err := ecc.NewS256PrivateKey(e3.Bytes())
	if err != nil {
		panic(err)
	}

	addr3 := pvk3.Point().Address(true, false)

	fmt.Printf("addr3: %s\n", addr3)
}
$ go run main.go 
addr1: mmTPbXQFxboEtNRkwfh6K51jvdtHLxGeMA
addr2: mopVkxp8UhXqRYbCYJsbeE1h1fiF64jcoH
addr3: 1F1Pn2y6pDb68E5nYJJeba4TLg2U7B6KF1

4) 비밀키의 WIF 형식

 비트코인의 비밀키는 256비트로 표현되는 숫자이며 말 그대로 비밀이기 때문에 네트워크를 통해서 전파되어서는 안됩니다. 따라서 직렬화가 필요한 경우가 거의 없습니다.

 

 한 가지 예외 상황은 비밀키를 다른 지갑으로 옮기는 경우입니다. 이 때 비밀키를 읽기 쉽도록 직렬화하는 방법이 WIF 형식입니다.

 

 WIF 형식으로 비밀키를 직렬화하는 방법은 아래와 같습니다.

  1. 비밀키를 32바이트 길이의 빅엔디언으로 표현합니다.
  2. 만약 대응하는 공개키가 압축 SEC 형식인 경우, 2의 뒤에 0x01을 추가합니다.
  3. 메인넷은 0x80를, 테스트넷은 0xef를 접두부로 하여 2의 앞에 추가합니다.
  4. 3의 결과를 hash256 함수에 넣어 나온 출력의 첫 4바이트를 체크섬으로 취합니다.
  5. 3의 결과에 4를 붙이고 이를 Base58 부호화합니다.
// secp256k1 개인키의 WIF 형식을 반환하는 함수
func (pvk s256PrivateKey) WIF(compressed bool, testnet bool) string {
	secret := pvk.secret

	// secret의 길이가 32보다 작으면 비어있는 길이만큼 0x00을 추가
	if len(secret) < 32 {
		secret = append(make([]byte, 32-len(secret)), secret...)
	}

	// 압축된 공개키를 사용하는 경우, secret에 0x01을 추가
	if compressed {
		secret = append(secret, 0x01)
	}

	// testnet을 사용하는 경우, secret에 0xef를 추가
	// mainnet을 사용하는 경우, secret에 0x80을 추가
	if testnet {
		secret = append([]byte{0xef}, secret...)
	} else {
		secret = append([]byte{0x80}, secret...)
	}

	return EncodeBase58Checksum(secret) // Base58Checksum 인코딩
}

연습문제

더보기
package main

import (
	"chapter04/ecc"
	"fmt"
	"math/big"
)

func main() {
	e1 := big.NewInt(5003)

	pvk1, err := ecc.NewS256PrivateKey(e1.Bytes())
	if err != nil {
		panic(err)
	}

	wif1 := pvk1.WIF(true, true)

	fmt.Printf("wif1: %s\n", wif1)

	e2 := big.NewInt(0).Exp(big.NewInt(2021), big.NewInt(5), nil)

	pvk2, err := ecc.NewS256PrivateKey(e2.Bytes())
	if err != nil {
		panic(err)
	}

	wif2 := pvk2.WIF(false, true)

	fmt.Printf("wif2: %s\n", wif2)

	e3, _ := new(big.Int).SetString("54321deadbeef", 16)

	pvk3, err := ecc.NewS256PrivateKey(e3.Bytes())
	if err != nil {
		panic(err)
	}

	wif3 := pvk3.WIF(true, false)

	fmt.Printf("wif3: %s\n", wif3)
}
$ go run main.go 
wif1: cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN8rFTv2sfUK
wif2: 91avARGdfge8E4tZfYLoxeJ5sBdNJQH4kvjpWAxgzczjbCwxic
wif3: KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgiuQJv1h8Ytr2S53a

📍 더 찾아볼 거리

  1. ripemd160 해시함수에 대해
  2. 유한체에서 제곱근을 구하는 방법
  3. Paymail Protocol

📖 참고자료

 

밑바닥부터 시작하는 비트코인

비트코인은 블록체인 기술의 집약체입니다. 이더리움, 이오스 같은 2, 3세대 블록체인은 비트코인을 바탕으로 확장, 발전한 개념입니다. 디앱 개발에서 머무르지 않고 블록체인 개발자로 성장하

www.hanbit.co.kr

 

GitHub - jimmysong/programmingbitcoin: Repository for the book

Repository for the book. Contribute to jimmysong/programmingbitcoin development by creating an account on GitHub.

github.com

 

Chapter 4. 직렬화

아래 글에서는 Programming Bitcoin — Jimmy Song, 한빛미디어(2019)의 Chapter4 직렬화에 대한 내용을 다룬다. 직렬화는 비트코인 네트워크에서 데이터를 주고 받기 위해 사용하는 방식에 대한 설명이다

medium.com

글에서 수정이 필요한 부분이나 설명이 부족한 부분이 있다면 댓글로 남겨주세요!
최근에 올라온 글
최근에 달린 댓글
«   2025/01   »
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
Total
Today
Yesterday
글 보관함