如何用 golang 生成比特币钱包

前一段时间看了区块链相关的内容,学习了一下bitcoin地址生成。内容来自网络。

第一步,随机选取一个32字节的数,大小介于1~0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141之间,作为私钥

PS: 助记词是将这个随机数按每 11位分组映射到 2048 个单词位得到 12 16 或 24 个单词。

18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725

第二步,使用椭圆曲线加密算法(ECDSA-SECP256k1)计算私钥所对应的非压缩公钥(共65字节,1字节0x04,32字节为x坐标,32字节为y坐标)。

0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6

第三步,计算公钥的SHA-256哈希值

600FFE422B4E00731A59557A5CCA46CC183944191006324A447BDB2D98D4B408

第四步,计算上一步哈希值的RIPEMD-160哈希值

010966776006953D5567439E5E39F86A0D273BEE

第五步,在上一步结果之间加入地址版本号(如比特币主网版本号”0x00”)

00010966776006953D5567439E5E39F86A0D273BEE

第六步,计算上一步结果的SHA-256哈希值

445C7A8007A93D8733188288BB320A8FE2DEBD2AE1B47F0F50BC10BAE845C094

第七步,再次计算上一步结果的SHA-256哈希值

D61967F63C7DD183914A4AE452C9F6AD5D462CE3D277798075B107615C1A8A30

第八步,取上一步结果的前4个字节(8位十六进制数)D61967F6,把这4个字节加在第五步结果的后面,作为校验(这就是比特币地址的16进制形态)

00010966776006953D5567439E5E39F86A0D273BEED61967F6

第九步,用base58表示法变换一下地址(这就是最常见的比特币地址形态)

16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM

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
 package main

import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"fmt"
"golang.org/x/crypto/ripemd160"
"log"
)

const VERSION = byte(0x00)
const CHECKSUM_LENGTH = 4

type BitcoinKeys struct {
PrivateKey *ecdsa.PrivateKey
PublicKey []byte
}

func GetBitcoinKeys() *BitcoinKeys {
b := &BitcoinKeys{nil, nil}
b.newKeyPair()
return b
}

func (b *BitcoinKeys) newKeyPair() {
curve := elliptic.P256()
var err error
b.PrivateKey, err = ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
log.Panic(err)
}
b.PublicKey = append(b.PrivateKey.PublicKey.X.Bytes(), b.PrivateKey.PublicKey.Y.Bytes()...)
}

//获取地址
func (b *BitcoinKeys) GetAddress() []byte {
//1.ripemd160(sha256(publickey))
ripPubKey := GeneratePublicKeyHash(b.PublicKey)
//2.最前面添加一个字节的版本信息获得 versionPublickeyHash
versionPublickeyHash := append([]byte{VERSION}, ripPubKey[:]...)
//3.sha256(sha256(versionPublickeyHash)) 取最后四个字节的值
tailHash := CheckSumHash(versionPublickeyHash)
//4.拼接最终hash versionPublickeyHash + checksumHash
finalHash := append(versionPublickeyHash, tailHash...)
//进行base58加密
address := Base58Encode(finalHash)
return address
}

func GeneratePublicKeyHash(publicKey []byte) []byte {
sha256PubKey := sha256.Sum256(publicKey)
r := ripemd160.New()
r.Write(sha256PubKey[:])
ripPubKey := r.Sum(nil)
return ripPubKey
}

//通过地址获得公钥
func GetPublicKeyHashFromAddress(address string) []byte {
addressBytes := []byte(address)
fullHash := Base58Decode(addressBytes)
publicKeyHash := fullHash[1 : len(fullHash)-CHECKSUM_LENGTH]
return publicKeyHash
}

func CheckSumHash(versionPublickeyHash []byte) []byte {
versionPublickeyHashSha1 := sha256.Sum256(versionPublickeyHash)
versionPublickeyHashSha2 := sha256.Sum256(versionPublickeyHashSha1[:])
tailHash := versionPublickeyHashSha2[:CHECKSUM_LENGTH]
return tailHash
}

//检测比特币地址是否有效
func IsVaildBitcoinAddress(address string) bool {
adddressByte := []byte(address)
fullHash := Base58Decode(adddressByte)
if len(fullHash) != 25 {
return false
}
prefixHash := fullHash[:len(fullHash)-CHECKSUM_LENGTH]
tailHash := fullHash[len(fullHash)-CHECKSUM_LENGTH:]
tailHash2 := CheckSumHash(prefixHash)
if bytes.Compare(tailHash, tailHash2[:]) == 0 {
return true
} else {
return false
}
}

func main() {
keys := GetBitcoinKeys()
bitcoinAddress := keys.GetAddress()
fmt.Println("比特币地址:", string(bitcoinAddress))
fmt.Printf("比特币地址是否有效:%v\n:", IsVaildBitcoinAddress(string(bitcoinAddress)))
}

base58

Base58 采用数字、大写字母、小写字母,去除歧义字符 0(零)、O(大写字母 O)、I(大写字母i)、l(小写字母L),总计58个字符作为编码的字母表。

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
 package main

import (
"bytes"
"math/big"
)

var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")

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{}

for x.Cmp(zero) != 0 {
x.DivMod(x, base, mod)
result = append(result, b58Alphabet[mod.Int64()])
}

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 != b58Alphabet[0] {
break
}
zeroBytes++
}
payload := input[zeroBytes:]
for _, b := range payload {
charIndex := bytes.IndexByte(b58Alphabet, b)
result.Mul(result, big.NewInt(int64(len(b58Alphabet))))
result.Add(result, big.NewInt(int64(charIndex)))
}

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

return decoded
}

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]
}
}

lua package 面向对象开发

lua 作为一门简单的语言,在变量是只有 table 和非 table 的区别。

基本

1
2
3
4
5
6
local _M = {} 
function _M:foo()
...
end

return _M

在 lua 中 _M:foo() 就是 _M.foo(self) 。在使用时和个人习惯有关。这里我希望能够严格的区分库和对象.当函数为静态方法时,比如一些utils函数,使用点号(.)。当函数需要面向对象时,使用冒号(:)。

阅读更多

go-singlefilght

singleflight

1
2
golang.org/x/sync/singleflight

singleflight 是 go 提供的一个扩展并发原语,主要是用来合并请求来降低服务压力。

code

1
https://cs.opensource.google/go/x/sync/+/036812b2:singleflight/singleflight.go

原理

实现了一个 Group 的 struct

1
2
3
4
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
}
阅读更多

docker build alpine dns error

使用 alpine 构建 Ddockerfile ,导致 docker 打包失败,错误如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fetch https://mirrors.aliyun.com/alpine/v3.13/main/x86_64/APKINDEX.tar.gz
fetch https://mirrors.aliyun.com/alpine/v3.13/community/x86_64/APKINDEX.tar.gz
v3.13.4-69-g5bcff43ec5 [https://mirrors.aliyun.com/alpine/v3.13/main]
v3.13.4-66-g32aee0eba0 [https://mirrors.aliyun.com/alpine/v3.13/community]
OK: 13892 distinct packages available
fetch https://mirrors.aliyun.com/alpine/v3.13/main/x86_64/APKINDEX.tar.gz
WARNING: Ignoring https://mirrors.aliyun.com/alpine/v3.13/main: DNS lookup error
fetch https://mirrors.aliyun.com/alpine/v3.13/community/x86_64/APKINDEX.tar.gz
WARNING: Ignoring https://mirrors.aliyun.com/alpine/v3.13/community: DNS lookup error
ERROR: unable to select packages:
bash (no such package):
required by: world[bash]
curl (no such package):
required by: world[curl]
ERROR: Service 'microservice-users-rpc' failed to build : The command '/bin/sh -c apk update && apk add --no-cache curl bash' returned a non-zero code: 2
make: *** [docker-compose-up] Error 1
阅读更多

Docker & K8S 实践

使用 marp 制作的用于内部分享的 docker && k8s 的文档。


marp: true
theme: uncover
paginate: false
header: ‘NXDev

Docker & K8S 实践

纳星科技 张巍


Docker

是新技术吗?
解决了什么问题 ?


是新技术吗?

linux 的Namespaces + Cgroup
配合一种被称为 Union file system 的文件系统
最后打成一个包。


解决了什么问题?

  1. 对开发来说,统一了开发的环境。开发环境不一致的问题不再是问题。
  2. 对运维来说,上线只需要一条命令。 把所有的代码装进盒子。

Docs : https://docs.docker.com

安装

架构

Docker Run Gin


Docker 如何安装

https://docs.docker.com/get-docker/

Linux

1
2
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh

先看看 怎么用的

gin-hello
mysql


Docker 架构

bg right:60% fit


镜像 (Image)
容器 (Container)
仓库 (Repository)


镜像(Image)

相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。

1
2
3
4
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql 5.7 9cfcce23593a 5 weeks ago 448MB
redis 3.2-alpine 6e94a98d3442 21 months ago 22.9MB

容器(Container)

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

1
2
3
4
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3548302772b1 redis:3.2-alpine "docker-entrypoint.s…" 3 weeks ago Up 3 weeks 0.0.0.0:6379->6379/tcp redis_redis_1
37259a661f27 xxiu/mysql "docker-entrypoint.s…" 3 weeks ago Up 3 weeks 0.0.0.0:3306->3306/tcp, 33060/tcp mysql_mysql_1

仓库(Repository)

仓库可看成一个代码控制中心,用来保存镜像。

  • 自己搭: Harbor
  • 阿里云:

核心技术

bg right:60% fit

Namespaces

Cgroup

Union file systems

Container format


Namespaces & Cgroup

linux 内核做资源隔离的两个概念。

所以 docker 和虚拟机的区别在于:

  • 虚拟机虚拟了整套的操作系统,
  • docker 只是在linux 内核上做了资源隔离。

Namespaces

资源隔离

bg right:68% fit


cgroup

Cgroup:控制程序对资源的占用。

  • 对进程组使用的资源总额进行限制;

  • 通过分配CPU时间片数量/磁盘IO/带宽大小,实际上就是相当于控制子进程运行的优先级。

  • 统计系统资源使用量,比如CPU使用时间,内存使用量等

  • 进程控制,恢复执行进程


Union file systems

img

  • boots:
    boot loader + kernel ,用户不可修改 (共享主机的内核)

  • root file system : 包括典型的 linux 目录结构:
    /dev /proc /bin /etc /lib /usr /tmp 等配置文件,二进制文件,库文件


AUFS (Union FS) : 支持将不同的目录挂载到虚拟目录下。

img


bg fit

bg fit


Docker Run Gin

1
2
3
4
5
6
7
8
9
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run(":8080")
}
1
go build . -o main

Dockerfile

1
2
3
4
5
FROM scratch
WORKDIR /root/
COPY main /root/main
EXPOSE 8080
CMD ["/root/main"]
1
2
3
docker build  -t ginhello .
docker ps |grep ginhello
docker run -it ginhello

1
2
3
语法
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

  • OPTIONS:
    开放的端口/挂载的文件/传入的变量…
  • IMAGE:
    正常的镜像名:
    registry.cn-beijing.aliyuncs.com/nxops/gin-hello:v0.0.2
  • COMMAND && ARG…

例如:

1
docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql

运行 docker 的命令又长 参数又多,怎么办?


docker-compose

还是来用一个文件来描述刚才我们的 run 命令

1
2
3
4
5
6
7
8
9
# docker-compose.yml
version: '3'
services:
gin-hello:
image: registry.cn-beijing.aliyuncs.com/nxops/gin-hello:v0.0.2
build: .
restart: always
ports:
- 8080:8080

mysql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3'

services:
mysql:
image: mysql
build: .
volumes:
- ./data:/var/lib/mysql
- ./conf.d/mysql.cnf:/etc/mysql/conf.d/mysql.cnf
environment:
- MYSQL_ROOT_PASSWORD=pprt123
- MYSQL_USER=pprt
- MYSQL_PASSWORD=pprt123
ports:
- 3306:3306
command: --lower-case-table-names=1 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
restart: always

接下来命令变得简单了
gin-hello

1
2
3
4
docker-compose build gin-hello
docker-compose push|pull gin-hello
docker-compose run gin-hello
docker-compose exec gin-hello bash

mysql

1
docker-compose run mysql 

K8S

K8s 是什么 ?

容器的调度和管理。


minikube

1
2
3
4
5
6
7
8
9
10
11
# 安装 kubectl
brew install kubectl

# 安装 minikube
# https://github.com/AliyunContainerService/minikube
# https://developer.aliyun.com/article/221687

curl -Lo minikube https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.12.1/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

## 选择环境
kubectl config use-context minikube

example :gin-hello


架构

bg fit right:80%


  • etcd保存了整个集群的状态;
  • apiserver提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制;
  • controller manager负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
  • scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上;
  • kubelet负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理;
  • Container runtime负责镜像管理以及Pod和容器的真正运行(CRI);
  • kube-proxy负责为Service提供cluster内部的服务发现和负载均衡;

  • kube-dns负责为整个集群提供DNS服务
  • Ingress Controller为服务提供外网入口
  • Heapster提供资源监控
  • Dashboard提供GUI
  • Federation提供跨可用区的集群
  • Fluentd-elasticsearch提供集群日志采集、存储与查询

整体

bg fit right:75%


master

bg fit right:75%


Node

bg fit right:75%


kubernetes 它都管理了什么

资源


一切都是资源,根据需要去申请。
网络,存储,cpu,内存
有状态服务,无状态服务,任务,定时任务,配置
……

所有的资源都靠一行命令:

1
kubectl apply -f  <filename.yaml>

控制器

bg right:70% fit

应用控制

  • Deployment
  • DaemonSet
  • StatefulSet
  • Job/CronJob

应用配置

  • ConfigMap
  • Secret

访问方式

  • Service
  • Ingress

service

https://kubernetes.io/zh/docs/concepts/services-networking/service/#loadbalancer
service 的四种网络

  • ClusterIP 集群内部暴露ip,(默认)
  • NodeIP 通过每个 Node 上的 ip 和NodePort 暴露服务,NodePort 会路由到 ClusterIP
  • LoadBalancer 使用云提供商的均衡负载服务。 向外暴露服务
  • ExternalName 通过返回 CNAME 和它的值。

ingress

1
2
3
4
5
  internet
|
[ Ingress ]
--|-----|--
[ Services ]

ingress 被称为server 的 server。
被用来作为网络的七层代理
其实它就是做 Nginx 的事情


Ingress Controllers

在使用七层代理时,我们需要对代理做一些配置
比如 nginx 的 upstream proxy_pass

Ingress Controllers 通过 watch ingress,将它转化为数据平面的配置。

1
2
3
4
5
  internet
|
[ Ingress ] <-- [Ingress Controllers ] kubuctl
--|-----|-- | |
[ Services ] [etcd]<----------------

Ingress 选型

  1. Nginx Ingress
  2. Kong Ingress
  3. APISIX Ingress
  4. Traefik
  5. Haproxy

Kong

Kong = Openresty + config
Openresty = Nginx + lua


END

golang sync

##Goroutine 内存占用

下面是一个创建 goroutine 什么也不干,对内存的占用打印。说明创建 goroutine 的成本非常低。

1
2
3
4
5
6
7
num:1  mem: 0.062MB average:64.000kb 
num:10 mem: 0.000MB average:0.000kb
num:100 mem: 0.000MB average:0.000kb
num:1000 mem: 0.250MB average:0.256kb
num:10000 mem: 1.373MB average:0.141kb
num:100000 mem: 265.852MB average:2.722kb
num:1000000 mem: 2462.125MB average:2.521kb

以下是创建代码:

阅读更多

tcp连接数

Tcp 连接中,Server 监听固定端口,Client 发起主动连接。 经过三次握手后建立 Tcp 连接。

Client

client 每次请求,如果不固定端口,系统会选用一个空闲端口。该端口是独占,不能和其他 Tcp 连接共享。本地端口最大数为 65536 ,0 有特殊含义(预留,系统指定动态生成的端口),其他的全作为 client 情况下 ,最大连接数为65535 。 除去系统服务使用的端口,能够使用的会比这个少一些,1024 一下的端口通常为保留端口。

阅读更多

jenkins 配置说明

这是给团队写的 jenkins 会出现问题的说明,简单的描述了 jenkins 使用中会出现的问题,以及应对方法。当然最好的应对方法是没有问题。

jenkins 是什么

jenkins 自动化的命令运行引擎,pipeline (流水线) 是按照既定的逻辑来执行 jenkinsfile 中的代码。

也就是说它只是一个流程执行引擎,所有的命令都需要依赖其它软件包。 比如 npm ,maven, ansiable 等等。当使用 docker 来启动 jenkins 时,需要在 dockerfile 里面加上打包需要的依赖。

阅读更多

生成分布式id

分布式系统中经常需要生成全局唯一 id ,要求:

  1. 全局唯一: 不能出现重复ID

  2. 高可用: 作为基础系统,被许多关键系统调用,一旦宕机,影响严重

阅读更多