filecoin lotus 公钥私钥地址生成过程

地址生成过程

fil有两种加密类型的私钥:secp256k1bls

另:代码中的切片在此皆称作数组

一、secp256k1 过程

私钥

私钥一定是32位的

代码路径
go-crypto/crypto.go

"crypto/ecdsa"//系统rcypto库导包

// PrivateKeyBytes is the size of a serialized private key.
const PrivateKeyBytes = 32

// GenerateKeyFromSeed generates a new key from the given reader.
func GenerateKeyFromSeed(seed io.Reader) ([]byte, error) {
	key, err := ecdsa.GenerateKey(secp256k1.S256(), seed)
	if err != nil {
		return nil, err
	}

	privkey := make([]byte, PrivateKeyBytes)
	blob := key.D.Bytes()

	// the length is guaranteed to be fixed, given the serialization rules for secp2561k curve points.
	copy(privkey[PrivateKeyBytes-len(blob):], blob)

	return privkey, nil
}

上面代码是由系统库”crypto/ecdsa”生成key
先创建一个32位的数组(默认值是32个0),
copy上面的注释是确保私钥长度是32位,打印过blob的长度就是32,内部算法没看懂是否会生成小于32的
如果小于32位,那么私钥前面几位就是0 (可以理解成私钥长度不够32位,前面补0)

公钥

公钥长度是65位的,也可以说是64位; 因为65位的第1位是4,固定的

代码路径
go-crypto/crypto.go

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"io"

	secp256k1 "github.com/ipsn/go-secp256k1"
)
// PublicKeyBytes is the size of a serialized public key.
const PublicKeyBytes = 65

// PublicKey returns the public key for this private key.
func PublicKey(sk []byte) []byte {
	x, y := secp256k1.S256().ScalarBaseMult(sk)
	return elliptic.Marshal(secp256k1.S256(), x, y)
}
ScalarBaseMult 获得 x,y

代码路径
github.com/ipsn/go-secp256k1
curve.go

//初始化参数
var theCurve = new(BitCurve)

func init() {
	// See SEC 2 section 2.7.1
	// curve parameters taken from:
	// http://www.secg.org/collateral/sec2_final.pdf
	theCurve.P, _ = new(big.Int).SetString("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 0)
	theCurve.N, _ = new(big.Int).SetString("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 0)
	theCurve.B, _ = new(big.Int).SetString("0x0000000000000000000000000000000000000000000000000000000000000007", 0)
	theCurve.Gx, _ = new(big.Int).SetString("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 0)
	theCurve.Gy, _ = new(big.Int).SetString("0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 0)
	theCurve.BitSize = 256
}

// S256 returns a BitCurve which implements secp256k1.
func S256() *BitCurve {
	return theCurve
}
//上面是初始参数
//下面是获取到x,y
//point是固定的64位数组,先通过Bx,By(就是初始化中的Gx,Gy)填充point,
//再通过C语言代码生成xxx
//最后将point的值写入到x,y 即将point拆分前后两部分,两个32位数组
func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
	// Ensure scalar is exactly 32 bytes. We pad always, even if
	// scalar is 32 bytes long, to avoid a timing side channel.
	if len(scalar) > 32 {
		panic("can't handle scalars > 256 bits")
	}
	// NOTE: potential timing issue
	padded := make([]byte, 32)
	copy(padded[32-len(scalar):], scalar)
	scalar = padded

	// Do the multiplication in C, updating point.
	point := make([]byte, 64)
	readBits(Bx, point[:32])
	readBits(By, point[32:])

	pointPtr := (*C.uchar)(unsafe.Pointer(&point[0]))
	scalarPtr := (*C.uchar)(unsafe.Pointer(&scalar[0]))
	res := C.secp256k1_ext_scalar_mul(context, pointPtr, scalarPtr)

	// Unpack the result and clear temporaries.
	x := new(big.Int).SetBytes(point[:32])
	y := new(big.Int).SetBytes(point[32:])
	for i := range point {
		point[i] = 0
	}
	for i := range padded {
		scalar[i] = 0
	}
	if res != 1 {
		return nil, nil
	}
	return x, y
}
elliptic.Marshal(secp256k1.S256(), x, y) 获得公钥

代码路径
系统库 “crypto/elliptic”

代码中的BitSize 查看上面代码中的BitCurve,secp256k1.S256()初始参数
theCurve.BitSize = 256
(256+7)>>3 = 32
这个代码简单,生成一个65位的数组,0位值固定是4,将上面得到的x,y写入到之后64位(其实就是上面的point,上面拆成x,y,这里再合并起来)
所以也可以说,公钥是64位的,因为前面有个固定值4;
如果是使用十六进制就是前面加04

// Marshal converts a point into the uncompressed form specified in section 4.3.6 of ANSI X9.62.
func Marshal(curve Curve, x, y *big.Int) []byte {
	byteLen := (curve.Params().BitSize + 7) >> 3

	ret := make([]byte, 1+2*byteLen)
	ret[0] = 4 // uncompressed point

	xBytes := x.Bytes()
	copy(ret[1+byteLen-len(xBytes):], xBytes)
	yBytes := y.Bytes()
	copy(ret[1+2*byteLen-len(yBytes):], yBytes)
	return ret
}

地址

地址有4种,分别是: ID,SECP256K1,Actor,BLS
这里针对SECP256K1

代码路径
go-address/address.go

// NewSecp256k1Address returns an address using the SECP256K1 protocol.
func NewSecp256k1Address(pubkey []byte) (Address, error) {
	return newAddress(SECP256K1, addressHash(pubkey))
}

// PayloadHashLength defines the hash length taken over addresses using the Actor and SECP256K1 protocols.
const PayloadHashLength = 20
var payloadHashConfig = &blake2b.Config{Size: PayloadHashLength}

func addressHash(ingest []byte) []byte {
	return hash(ingest, payloadHashConfig)
}
//注释,ingest是公钥byte数组,cfg的配置长度是20,最终会得到一个20的byte数组
//内部的blake2b.New(cfg),hasher.Sum(nil) 后面补充
func hash(ingest []byte, cfg *blake2b.Config) []byte {
	hasher, err := blake2b.New(cfg)
	if err != nil {
		// If this happens sth is very wrong.
		panic(fmt.Sprintf("invalid address hash configuration: %v", err)) // ok
	}
	if _, err := hasher.Write(ingest); err != nil {
		// blake2bs Write implementation never returns an error in its current
		// setup. So if this happens sth went very wrong.
		panic(fmt.Sprintf("blake2b is unable to process hashes: %v", err)) // ok
	}
	return hasher.Sum(nil)
}

//地址结构体
type Address struct{ str string }

//@protocol 这个就是地址的类型ID:0,SECP256K1:1,Actor:2,BLS:3, 所以此处是1
//@payload 是上面获得到的长度为20的byte数组
//返回结果,Address结构体
func newAddress(protocol Protocol, payload []byte) (Address, error) {
	switch protocol {
	case ID:
		_, n, err := varint.FromUvarint(payload)
		if err != nil {
			return Undef, xerrors.Errorf("could not decode: %v: %w", err, ErrInvalidPayload)
		}
		if n != len(payload) {
			return Undef, xerrors.Errorf("different varint length (v:%d != p:%d): %w",
				n, len(payload), ErrInvalidPayload)
		}
	case SECP256K1, Actor:
		//只是校验长度是不是20
		if len(payload) != PayloadHashLength {
			return Undef, ErrInvalidPayload
		}
	case BLS:
		if len(payload) != BlsPublicKeyBytes {
			return Undef, ErrInvalidPayload
		}
	default:
		return Undef, ErrUnknownProtocol
	}
	//创建一个21长度,0位置是地址类型(当前测试网,类型是1),后面20是刚获取到的长度为20的byte数组内容
	explen := 1 + len(payload)
	buf := make([]byte, explen)

	buf[0] = protocol
	copy(buf[1:], payload)

	return Address{string(buf)}, nil
}

看起来是不是…已经得到了私钥/公钥/地址… 还没完~

先补充上面blake2b.New(cfg),hasher.Sum(nil)

blake2b.New(cfg),hasher.Sum(nil)

代码路径
“github.com/minio/blake2b-simd”
blake2b.go

func New(c *Config) (hash.Hash, error) {
	...//部分代码省略
	d := new(digest)
	d.initialize(c)
	return d, nil
}

// initialize initializes digest with the given
// config, which must be non-nil and verified.
func (d *digest) initialize(c *Config) {
	... //略
	// Initialize.
	d.size = c.Size
	... //略
}


// Sum returns the calculated checksum.
func (d *digest) Sum(in []byte) []byte {
	// Make a copy of d so that caller can keep writing and summing.
	d0 := *d
	hash := d0.checkSum()//得到一个64位数组
	return append(in, hash[:d0.size]...)//取数组的前size位返回,这个size就是初始化时的(在上文blake2b转换时长度20)
}
//下面的write/checkSum 可不看,算法的..... 以其他语言实现时,可用于参考
//注意,下面方法中使用到了两个常量
const (
	BlockSize  = 128 // block size of algorithm
	Size       = 64  // maximum digest size
)
func (d *digest) Write(p []byte) (nn int, err error) {
	...//略
		//内部会修改d.nx
}
func (d *digest) checkSum() [Size]byte {
	// Do not create unnecessary copies of the key.
	if d.isKeyed {
		for i := 0; i < len(d.paddedKey); i++ {
			d.paddedKey[i] = 0
		}
	}

	dec := BlockSize - uint64(d.nx)
	if d.t[0] < dec {
		d.t[1]--
	}
	d.t[0] -= dec

	// Pad buffer with zeros.
	for i := d.nx; i < len(d.x); i++ {
		d.x[i] = 0
	}
	// Set last block flag.
	d.f[0] = 0xffffffffffffffff
	if d.isLastNode {
		d.f[1] = 0xffffffffffffffff
	}
	// Compress last block.
	compress(d, d.x[:])

	var out [Size]byte
	j := 0
	for _, s := range d.h[:(d.size-1)/8+1] {
		out[j+0] = byte(s >> 0)
		out[j+1] = byte(s >> 8)
		out[j+2] = byte(s >> 16)
		out[j+3] = byte(s >> 24)
		out[j+4] = byte(s >> 32)
		out[j+5] = byte(s >> 40)
		out[j+6] = byte(s >> 48)
		out[j+7] = byte(s >> 56)
		j += 8
	}
	return out
}

地址转换 字符串

上面所得到公钥/私钥都是byte数组,地址是一个结构体, 如何得到字符串…
如果直接将byte数组转字符串会发现结果乱码… 结构体中的str也是乱码…

type Key struct {
	types.KeyInfo

	PublicKey []byte
	Address   address.Address
}
type KeyInfo struct {
	Type   string
	PrivateKey []byte
}

Key是一个完整的信息,公私钥,地址; 类型分别是[]byte 和address.Address

公私钥转字符串

在转json的时候,具体查看系统包json.Marshal,内部针对切片byte特殊处理

func newSliceEncoder(t reflect.Type) encoderFunc {
	// Byte slices get special treatment; arrays don't.
	if t.Elem().Kind() == reflect.Uint8 {
		p := reflect.PtrTo(t.Elem())
		if !p.Implements(marshalerType) && !p.Implements(textMarshalerType) {
			return encodeByteSlice //内部进行base64处理 
		}
	}
	enc := sliceEncoder{newArrayEncoder(t)}
	return enc.encode
}

转换代码

priArr, err := GenPrivate()
//base64获取字符串
prikeyBase64Str := base64.StdEncoding.EncodeToString(priArr)
//16进制
priKeyHex := hex.EncodeToString(priArr)
地址转字符串

这个是最坑的…
得到type Address struct{ str string } ,以为内部的str就是地址了,结果不是!
后面才发现它重写了序列化/format的代码

// MarshalJSON implements the json marshal interface.
func (a Address) MarshalJSON() ([]byte, error) {
	return []byte(`"` + a.String() + `"`), nil
}

// String returns an address encoded as a string.
func (a Address) String() string {
	str, err := encode(Testnet, a)//这写死了是测试网
	if err != nil {
		panic(err) // I don't know if this one is okay
	}
	return str
}
	
// 取第0个,上文中有一个长度21的数组,0位置就是类型
func (a Address) Protocol() Protocol {
	if len(a.str) == 0 {
		return Unknown
	}
	return a.str[0]
}

// 获取的是那个长度为20的byte数组
func (a Address) Payload() []byte {
	return []byte(a.str[1:])//从1位置开启取,  a.str的长度是21
}

//blake2b 转换
// ChecksumHashLength defines the hash length used for calculating address checksums.
const ChecksumHashLength = 4
var checksumHashConfig = &blake2b.Config{Size: ChecksumHashLength}
// Checksum returns the checksum of `ingest`.
func Checksum(ingest []byte) []byte {
	return hash(ingest, checksumHashConfig)//长度4,   hash方法查看上文转换长度20时的代码
}	

func encode(network Network, addr Address) (string, error) {
	if addr == Undef {
		return UndefAddressString, nil
	}
	//根据网络类型 获取前缀 
	var ntwk string
	switch network {
	case Mainnet:
		ntwk = MainnetPrefix 	//f
	case Testnet:
		ntwk = TestnetPrefix	//t
	default:
		return UndefAddressString, ErrUnknownNetwork
	}
	//根据地址类型获得字符串
	var strAddr string
	switch addr.Protocol() {
	case SECP256K1, Actor, BLS:
		//checkSum 注意传值是之前那个长度为21的数组,通过这个21的数组,得到一个长度4的数组
		cksm := Checksum(append([]byte{addr.Protocol()}, addr.Payload()...))
		//将长度20的数组和长度4的数组组成一个长度24的数组,对它base32
		//加上前缀 t1 (测试网)
		strAddr = ntwk + fmt.Sprintf("%d", addr.Protocol()) + AddressEncoding.WithPadding(-1).EncodeToString(append(addr.Payload(), cksm[:]...))
	case ID:
		i, n, err := varint.FromUvarint(addr.Payload())
		if err != nil {
			return UndefAddressString, xerrors.Errorf("could not decode varint: %w", err)
		}
		if n != len(addr.Payload()) {
			return UndefAddressString, xerrors.Errorf("payload contains additional bytes")
		}
		strAddr = fmt.Sprintf("%s%d%d", ntwk, addr.Protocol(), i)
	default:
		return UndefAddressString, ErrUnknownProtocol
	}
	return strAddr, nil
}

//以下是几个常量说明
// Network represents which network an address belongs to.
type Network = byte
const (
	// Mainnet is the main network.
	Mainnet Network = iota
	// Testnet is the test network.
	Testnet
)	
// 主网前缀
const MainnetPrefix = "f"
// 测试网前缀
const TestnetPrefix = "t"

// 地址格式,从0开始 ID:0,SECP256K1:1,Actor:2,BLS:3
type Protocol = byte
const (
	// ID represents the address ID protocol.
	ID Protocol = iota
	// SECP256K1 represents the address SECP256K1 protocol.
	SECP256K1
	// Actor represents the address Actor protocol.
	Actor
	// BLS represents the address BLS protocol.
	BLS

	Unknown = Protocol(255)
)

针对base32中WithPadding(-1) 说明
-1表示不填充,默认是填充 =

package base32
const (
	StdPadding rune = '=' // Standard padding character
	NoPadding  rune = -1  // No padding
)

借鉴 https://www.jianshu.com/p/c3f0c8b48345 中的内容

Base32编码

与Base16编码区别的是,Base32使用了ASCII编码中可打印的32个字符(大写字母A~Z和数字2~7)对任
意字节数据进行编码.Base32将串起来的二进制数据按照5个二进制位分为一组,由于传输数据的单位是
字节(即8个二进制位).所以分割之前的二进制位数是40的倍数(40是5和8的最小公倍数).如果不足40位,
则在编码后数据补充"=",一个"="相当于一个组(5个二进制位),编码后的数据是原先的8/5倍

上面是针对一个24位的进行base32, 参考base32说明, 每5个一组,就还缺一个,所以会自动填充一个=, -1的作用就是不要这个= (这个结论是猜测的…)

完整生成代码

lotus代码在windows没法编译,不能运行,
这个生成代码是抽出来的,可以在windows直接跑
package main

import (
	"bytes"
	"encoding/base32"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"github.com/filecoin-project/go-address"
	goCrypto "github.com/filecoin-project/go-crypto"
	"github.com/minio/blake2b-simd"
)

const encodeStd = "abcdefghijklmnopqrstuvwxyz234567"

var AddressEncoding = base32.NewEncoding(encodeStd)

func main() {
	//k, err := GenPrivate()
	var err error
	//测试代码
	k := []byte{126, 25, 208, 242, 124, 230, 119, 157, 89, 166, 200, 224, 212, 218, 245, 211, 223, 67, 202, 62, 5, 222, 129, 216, 251, 87, 113, 250, 62, 248, 118, 12}
	fmt.Println("私钥数组:", k, ",长度:", len(k), err)
	p, err := ToPublic(k)
	fmt.Println("公钥数组:", p, ",长度:", len(p), err)

	var payloadHashConfig = &blake2b.Config{Size: 20}
	b20 := hash(p, payloadHashConfig)
	fmt.Println("通过公钥blake2b/20 得到数组 ", b20)
	explen := 1 + len(b20)
	buf := make([]byte, explen)
	buf[0] = address.SECP256K1
	copy(buf[1:], b20)
	fmt.Println("得到21数组 ", buf)
	cksm := Checksum(buf)
	fmt.Println("通过21数组 blake2b/4,得到数组 ", cksm)

	codeBuf := make([]byte, 0)
	codeBuf = append(codeBuf, b20[:]...)
	codeBuf = append(codeBuf, cksm[:]...)
	fmt.Println("20和4的数组组合 ", codeBuf)
	str := AddressEncoding.WithPadding(-1).EncodeToString(codeBuf)
	fmt.Println("通过base 32 padding -1 encode 得来字符串 ", str)
	re := "t1" + str
	fmt.Println("加上前缀得最终结果 ", re)

	prikeyBase64Str := base64.StdEncoding.EncodeToString(k)
	pubkeyBase64Str := base64.StdEncoding.EncodeToString(p)
	fmt.Println("公私钥base64 私钥:", prikeyBase64Str, ",公钥:", pubkeyBase64Str)
	priHex := hex.EncodeToString(k)
	pubHex := hex.EncodeToString(p)
	fmt.Println("公私钥hex 私钥len:", len(priHex)/2, ":", priHex, ",公钥len:", len(pubHex)/2, ":", pubHex)

	//keystore文件
	kStr := "wallet-" + re
	//kStr = "default" // 设置节点默认账号,结果是MRSWMYLVNR2A
	encName := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString([]byte(kStr))
	fmt.Println("keystore文件名: " + encName)
	kinfo:=&types.KeyInfo{
		Type:       types.KTSecp256k1,
		PrivateKey: k,
	}
	kb,_:=json.Marshal(kinfo)
	fmt.Println(string(kb))
	fmt.Println(hex.EncodeToString(kb))

	ssss:="{\"Type\":\"secp256k1\",\"PrivateKey\":\"fhnQ8nzmd51Zpsjg1Nr1099Dyj4F3oHY+1dx+j74dgw=\"}"
	fmt.Println(hex.EncodeToString([]byte(ssss)))
}

func GenPrivate() ([]byte, error) {
	priv, err := goCrypto.GenerateKey()
	if err != nil {
		return nil, err
	}
	return priv, nil
}
func ToPublic(pk []byte) ([]byte, error) {
	return goCrypto.PublicKey(pk), nil
}

func hash(ingest []byte, cfg *blake2b.Config) []byte {
	hasher, err := blake2b.New(cfg)
	if err != nil {
		// If this happens sth is very wrong.
		panic(fmt.Sprintf("invalid address hash configuration: %v", err)) // ok
	}
	if _, err := hasher.Write(ingest); err != nil {
		// blake2bs Write implementation never returns an error in its current
		// setup. So if this happens sth went very wrong.
		panic(fmt.Sprintf("blake2b is unable to process hashes: %v", err)) // ok
	}
	return hasher.Sum(nil)
}

// ValidateChecksum returns true if the checksum of `ingest` is equal to `expected`>
func ValidateChecksum(ingest, expect []byte) bool {
	digest := Checksum(ingest)
	return bytes.Equal(digest, expect)
}

// Checksum returns the checksum of `ingest`.
func Checksum(ingest []byte) []byte {
	return hash(ingest, checksumHashConfig)
}

var checksumHashConfig = &blake2b.Config{Size: 4}

输出结果

    私钥数组: [126 25 208 242 124 230 119 157 89 166 200 224 212 218 245 211 223 67 202 62 5 222 129 216 251 87 113 250 62 248 118 12] ,长度: 32 <nil>
    公钥数组: [4 171 214 7 153 167 93 7 209 192 190 228 172 59 76 180 124 141 250 100 8 122 187 73 8 154 73 24 112 5 219 216 95 74 157 145 238 230 241 53 156 56 242 220 73 124 147 39 181 22 59 247 203 70 200 51 74 207 196 249 154 182 48 214 84] ,长度: 65 <nil>
    通过公钥blake2b/20 得到数组  [36 50 207 231 244 218 10 6 114 95 192 90 220 95 40 126 114 141 85 127]
    得到21数组  [1 36 50 207 231 244 218 10 6 114 95 192 90 220 95 40 126 114 141 85 127]
    通过21数组 blake2b/4,得到数组  [33 188 123 200]
    204的数组组合  [36 50 207 231 244 218 10 6 114 95 192 90 220 95 40 126 114 141 85 127 33 188 123 200]
    通过base 32 padding -1 encode 得来字符串  eqzm7z7u3ifam4s7ybnnyxzipzzi2vl7eg6hxsa
    加上前缀得最终结果  t1eqzm7z7u3ifam4s7ybnnyxzipzzi2vl7eg6hxsa
    公私钥base64 私钥: fhnQ8nzmd51Zpsjg1Nr1099Dyj4F3oHY+1dx+j74dgw= ,公钥: BKvWB5mnXQfRwL7krDtMtHyN+mQIertJCJpJGHAF29hfSp2R7ubxNZw48txJfJMntRY798tGyDNKz8T5mrYw1lQ=
    公私钥hex 私钥len: 327e19d0f27ce6779d59a6c8e0d4daf5d3df43ca3e05de81d8fb5771fa3ef8760c ,公钥len: 6504abd60799a75d07d1c0bee4ac3b4cb47c8dfa64087abb49089a49187005dbd85f4a9d91eee6f1359c38f2dc497c9327b5163bf7cb46c8334acfc4f99ab630d654
	keystore文件名: O5QWY3DFOQWXIMLFOF5G2N32G52TG2LGMFWTI4ZXPFRG43TZPB5GS4D2PJUTE5TMG5SWONTIPBZWC
	
   {"Type":"secp256k1","PrivateKey":"fhnQ8nzmd51Zpsjg1Nr1099Dyj4F3oHY+1dx+j74dgw="}
 7b2254797065223a22736563703235366b31222c22507269766174654b6579223a2266686e51386e7a6d6435315a70736a67314e723130393944796a3446336f48592b3164782b6a37346467773d227d
 7b2254797065223a22736563703235366b31222c22507269766174654b6579223a2266686e51386e7a6d6435315a70736a67314e723130393944796a3446336f48592b3164782b6a37346467773d227d

如果运行提示了secp256k1 错误,应该是使用了vendor模式,只需要将gopath下github.com/ipsn/go-secp256k1目录下的secp256k1文件夹拷贝到vendor下对应目录即可