基于Go语言模拟比特币公私钥及地址生成过程

比特币地址生成过程

比特币地址生成过程如下:
Alt
1.使用真随机生成数生成器,通过椭圆曲线签名算法ECDSA生成私钥。
2.通过拼接私钥的(X,Y)部分,组成公钥。
3.对公钥进行SHA-256哈希算法变换,再进行RIPEMD-160哈希算法变换,生成一个20字节的哈希。

RIPEMD160(SHA256(Pubkey)) -> 20字节Public key hash

4.在20个字节的Public key hash前加上1个字节的版本号,生成21字节的值。

version{0}+RIPEMD160(SHA256(Pubkey)) -> 21个字节值

5.再将这21个字节的值进行两次256哈希运算,生成32个字节的哈希值,取前四位,得出Checksum的值。

SHA256(SHA256(21个字节值)) -> 32字节hash -> 取前4位字节(Checksum)

6.将1个字节的版本号、20个字节Publickey hash,四个字节的Checksum相加,一共25个字节。

Version + Public key hash + Checksum -> 25个字节的数

7.将上述25个字节的数进行Base58编码,生成固定长度的比特币地址。

比特币地址的校验过程

不是所有的排列组合都会被比特币系统确认为地址,比特币地址仍需要进行地址合法性校验。
1.将地址进行Base58反编码,得到一个version + public key + checksum的字节数组。

地址 -> Base58反编码 -> version  + public key + checksum

2.取version + public key + checksum的字节数组的后4个字节为checksum值
3.取version + public key + checksum的字节数组的前21个字节进行两次SHA256哈希运算,取结果值的前4个字节,与第二步中的checksum值进行比较,如果一致则地址有效。

基于Go语言实现的比特币地址生成过程

基于Go语言实现的比特币地址生成及校验过程如下,细节部分均有详细注释。

package main

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"math/big"
	//这里需要下载并导入golang.orx/x包,具体方法为git clone https://github.com/golang/crypto.git,然后导入¥GOPATH:\src\golang下
	"golang.org/x/crypto/ripemd160"
	"log"
)

const version = byte(0x00)
const addressChecksumLen = 4
var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")

//定义钱包的结构体,其包括:
//1.私钥
//2.公钥
type Wallet struct {

	//ecdsa:椭圆曲线数字签名算法,其中的私钥
	PrivateKey ecdsa.PrivateKey

	//公钥为字节数组
	PublicKey []byte

}
//创建钱包
func NewWallet() *Wallet{

	//通过newKeyPair方法生成公私钥对
	privateKey, publicKey := newKeyPair()

	return &Wallet{privateKey, publicKey}
}

func (w *Wallet) getAddress() []byte {

	//使用RIPEMD-160哈希算法进行公钥变换
	ripemd160Hash := w.Ripemd160Hash(w.PublicKey)
	fmt.Println("PublicKey for Ripemd160Hash is : ", ripemd160Hash)

	//将version与公钥拼接,并通过CheckSum方法返回前四个字节的checksum
	version_ripemd160Hash := append([]byte{version}, ripemd160Hash...)
	checkSumBytes := CheckSum(version_ripemd160Hash)

	//拼接version+公钥+checksum生成25个字节
	bytes := append(version_ripemd160Hash, checkSumBytes...)

	//将这25个字节进行base58编码并返回
	return Base58Encode(bytes)
}

func CheckSum(payload []byte) []byte {

	//这里传入的payload其实是version+公钥,对其进行两次sha-256运算
	hash1 := sha256.Sum256(payload)
	hash2 := sha256.Sum256(hash1[:])

	//返回前四个字节,为checkSum值
	return hash2[:addressChecksumLen]
}

//RIPEMD-160算法
func (w *Wallet) Ripemd160Hash(publicKey []byte) []byte{

	//此处的逻辑为对传入的公钥进行SHA-256哈希运算,返回256位hash值
	//新建SHA-256变换对象
	hash256 := sha256.New()
	//传入公钥
	hash256.Write(publicKey)
	//进行hash转换
	hash := hash256.Sum(nil)
	fmt.Println("hash256 :", hash)

	//将上面的256位hash值进行ripemd160运算,返回160位的hash值
	ripemd160 := ripemd160.New()
	ripemd160.Write(hash)
	return ripemd160.Sum(nil)
}

//字节数组转Base58加密
func Base58Encode(input []byte) []byte {

	var result []byte
	x := big.NewInt(0).SetBytes(input)

	base := big.NewInt(int64(len(b58Alphabet)))
	zero := big.NewInt(0)
	mod := &big.Int{}

	//big.Int类自带cmp方法; 返回1:前面的值大于参数值; 返回0:前面的值等于参数值; 返回-1:前面的值等于参数值
	for x.Cmp(zero) != 0 {
		//对x进行58循环取余,并对应于b58Alphabet中取字符
		x.DivMod(x, base, mod)
		result = append(result, b58Alphabet[mod.Int64()])
	}

	//对result进行字节组反转
	ReverseBytes(result)

	for b := range input {
		if b == 0x00 {
			result = append([]byte{b58Alphabet[0]}, result...)
		} else {
			break
		}
	}

	return result
}

func Base58Decode(input []byte) []byte {
	result := big.NewInt(0)
	zeroBytes := 0

	for b := range input {
		if b == 0x00 {
			zeroBytes++
		}
	}

	payload := input[zeroBytes:]
	for _,b := range payload {
		charIndex := bytes.IndexByte(b58Alphabet, b)
		result.Mul(result, big.NewInt(58))
		result.Add(result, big.NewInt(int64(charIndex)))
	}

	decoded := result.Bytes()
	decoded = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), decoded...)

	return decoded
}

//字节组反转,注意,Go中byte数组传址,故此处可以进行赋值变换
func ReverseBytes(data []byte) {
	for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
		data[i], data[j] = data[j], data[i]
	}
}

func newKeyPair() (ecdsa.PrivateKey, []byte){

	//使用P256返回一个P-256的曲线对象
	curve := elliptic.P256()
	//rand.Reader是一个全局、共享的密码用强随机数生成器。这段表示使用强随机数,通过椭圆曲线签名算法生成私钥
	private, err := ecdsa.GenerateKey(curve, rand.Reader)
	fmt.Println("Private Key is :", private)
	if err != nil {
		//如果生成私钥报错,打印log并向上抛出异常,
		log.Panic(err)
	}
	//通过私钥生成公钥,公钥是由私钥的X域和Y域拼接而成,"..."表示可变参数
	pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
	fmt.Println("Public Key is :", pubKey)

	return *private, pubKey
}

func IsValidForAdress(adress []byte) bool {

	//将地址进行base58反编码,生成version+公钥哈希+checksum共25个字节
	version_public_checksumBytes := Base58Decode(adress)

	//[25-4:],就是21个字节往后的数(22,23,24,25一共4个字节)
	checkSumBytes := version_public_checksumBytes[len(version_public_checksumBytes) - addressChecksumLen:]

	//[:25-4],就是前21个字节(1~21,一共21个字节)
	version_ripemd160 := version_public_checksumBytes[:len(version_public_checksumBytes) - addressChecksumLen]

	//取version+public+checksum的字节数组的前21个字节进行两次256哈希运算,取结果值的前4个字节
	checkBytes := CheckSum(version_ripemd160)

	if bytes.Compare(checkSumBytes, checkBytes) == 0 {
		return true
	}
	return false
}

func main()  {

	Wallet := NewWallet()
	address := Wallet.getAddress()
	fmt.Println("address: %s\n",string(address))

	isValid := IsValidForAdress(address)
	fmt.Println("isValid is:", isValid)
}

由于私钥是随机生成的,故取某一次运行结果如下:

Private Key is : &{{{0xc0000a6040} 56839669056335834305391530473258490350350099278635486813310701327522265293780 37137243124387853709310683649044774206453753614049197921205691340043481108921} 32299294151040824604059836199836611395029679110931629547750903933432357846196}
Public Key is : [125 170 28 212 171 0 252 4 87 241 24 77 65 221 99 180 103 25 249 57 78 145 249 19 92 233 206 237 191 11 227 212 82 26 239 74 42 96 141 98 137 19 63 40 171 204 42 227 171 2 143 211 82 167 63 199 211 176 45 235 20 101 209 185]
hash256 : [205 8 211 69 70 228 104 73 253 202 44 203 2 26 68 31 220 187 250 63 36 206 216 131 240 95 205 182 61 59 192 23]
PublicKey for Ripemd160Hash is :  [216 22 52 109 186 157 226 13 230 64 179 240 202 220 91 69 8 31 214 85]
address: %s
 1LhZZFcJwq7bDFkB3cs8FvjAuA86gP9a2w
isValid is: true

你可能感兴趣的:(Go语言,比特币,区块链)