如何用 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]
}
}

使用 notion2md 从 notion 导出 markdown

为了用 notion 写公众号,我给 notion2md 提了个 merge 。

前段时间使用 notion 作为笔记,当需要将一篇笔记导出来贴到其他地方,比如公众号的时候,会有一些问题。主要碰到的是自带的一些图片导出的问题。

阅读更多

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

osxfuse:mac ntfs 支持

install

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
brew cask install osxfuse
Updating Homebrew...
Fast-forwarded master to origin/master.
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/cask).
No changes to formulae.

==> Caveats
To install and/or use osxfuse you may need to enable their kernel extension in

System Preferences → Security & Privacy → General

For more information refer to vendor documentation or the Apple Technical Note:

https://developer.apple.com/library/content/technotes/tn2459/_index.html

You must reboot for the installation of osxfuse to take effect.

==> Satisfying dependencies
==> Downloading https://github.com/osxfuse/osxfuse/releases/download/osxfuse-3.10.4/osxfuse-3.10.4.dmg
==> Downloading from https://github-production-release-asset-2e65be.s3.amazonaws.com/1867347/58615480-1769-11ea-8f1f-f6cc029e4f08?X-Amz-Algorithm=AWS4-HMAC-SH
######################################################################## 100.0%
==> Verifying SHA-256 checksum for Cask 'osxfuse'.
==> Installing Cask osxfuse
==> Running installer for osxfuse; your password may be necessary.
==> Package installers may write to any location; options such as --appdir are ignored.
Password:
installer: Package name is FUSE for macOS
installer: Installing at base path /
installer: The install was successful.
==> Changing ownership of paths required by osxfuse; your password may be necessary
🍺 osxfuse was successfully installed!

系统偏好设置-> FUSE

阅读更多

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 一下的端口通常为保留端口。

阅读更多