学习 NEO 钱包的 O3 项目 ,其中有用到 NeoSwift 库,记录一下。
私钥是怎么来的?
私钥是一个32字节的随机数,这个数的范围是介于 1 ~ 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141
之间。
见 Account.swift 类:
public init?() {
var pkeyData = Data(count: 32)
let result = pkeyData.withUnsafeMutableBytes {
SecRandomCopyBytes(kSecRandomDefault, pkeyData.count, $0)
}
if result != errSecSuccess {
fatalError()
}
var error: NSError?
guard let wallet = NeoutilsGeneratePublicKeyFromPrivateKey(pkeyData.fullHexString, &error) else { return nil }
self.wif = wallet.wif()
self.publicKey = wallet.publicKey()
self.privateKey = pkeyData
self.address = wallet.address()
self.hashedSignature = wallet.hashedSignature()
//default to mainnet
self.neoClient = NeoClient.sharedMain
}
它是通过 Security.framework 库里的 SecRandomCopyBytes
方法,生成一组密码安全的随机字节:
/*!
@function SecRandomCopyBytes
@abstract Return count random bytes in *bytes, allocated by the caller.
It is critical to check the return value for error
@result Return 0 on success, any other value on failure.
*/
@available(iOS 2.0, *)
public func SecRandomCopyBytes(_ rnd: SecRandomRef?, _ count: Int, _ bytes: UnsafeMutableRawPointer) -> Int32
随机生成一个32字节的 Data
数据,即 privatekeyData
:
var pkeyData = Data(count: 32)
let result = pkeyData.withUnsafeMutableBytes {
SecRandomCopyBytes(kSecRandomDefault, pkeyData.count, $0)
}
然后根据私钥(用 privatekeyData
的 HexString
作为参数)生成一个钱包,见 neo-utils:
var error: NSError?
guard let wallet = NeoutilsGeneratePublicKeyFromPrivateKey(pkeyData.fullHexString, &error) else { return nil }
// Generate a wallet from a private key
func GenerateFromPrivateKey(privateKey string) (*Wallet, error) {
pb := hex2bytes(privateKey)
var priv btckey.PrivateKey
err := priv.FromBytes(pb)
if err != nil {
return &Wallet{}, err
}
wallet := &Wallet{
PublicKey: priv.PublicKey.ToBytes(),
PrivateKey: priv.ToBytes(),
Address: priv.ToNeoAddress(),
WIF: priv.ToWIFC(),
HashedSignature: priv.ToNeoSignature(),
}
return wallet, nil
}
公钥是怎么来的?
公钥是用私钥通过椭圆曲线算法得到的,但是无法从公钥算出私钥。
见neowallet.go 和 btckey.go:
// Generate a wallet from a private key
func GenerateFromPrivateKey(privateKey string) (*Wallet, error) {
pb := hex2bytes(privateKey)
var priv btckey.PrivateKey
err := priv.FromBytes(pb)
if err != nil {
return &Wallet{}, err
}
wallet := &Wallet{
PublicKey: priv.PublicKey.ToBytes(),
PrivateKey: priv.ToBytes(),
Address: priv.ToNeoAddress(),
WIF: priv.ToWIFC(),
HashedSignature: priv.ToNeoSignature(),
}
return wallet, nil
}
// derive derives a Bitcoin public key from a Bitcoin private key.
func (priv *PrivateKey) derive() (pub *PublicKey) {
/* See Certicom's SEC1 3.2.1, pg.23 */
/* Derive public key from Q = d*G */
Q := secp256r1.ScalarBaseMult(priv.D)
/* Check that Q is on the curve */
if !secp256r1.IsOnCurve(Q) {
panic("Catastrophic math logic failure in public key derivation.")
}
priv.X = Q.X
priv.Y = Q.Y
return &priv.PublicKey
}
地址脚本是怎么来的?
地址脚本是由公钥前后各加了一个字节得到的,这两个字节是固定的:
- 前面是:
0x21
- 后面是:
0xAC
见 btckey.go:
/* Convert the public key to bytes */
pub_bytes := pub.ToBytes()
pub_bytes = append([]byte{0x21}, pub_bytes...)
pub_bytes = append(pub_bytes, 0xAC)
地址ScriptHash是怎么来的?
地址ScriptHash就是地址脚本取了个Hash,一次 sha256
,一次ripemd160
:
见 btckey.go:
/* SHA256 Hash */
sha256_h := sha256.New()
sha256_h.Reset()
sha256_h.Write(pub_bytes)
pub_hash_1 := sha256_h.Sum(nil)
/* RIPEMD-160 Hash */
ripemd160_h := ripemd160.New()
ripemd160_h.Reset()
ripemd160_h.Write(pub_hash_1)
pub_hash_2 := ripemd160_h.Sum(nil)
program_hash := pub_hash_2
地址是怎么来的?
地址是由地址ScriptHash加了盐,加了验证功能,然后 Base58
编码得到的:
- 加盐:前面加了一个字节
0x17
- 加验证功能:把加盐后的字节做了一个
hash
,两次sha256
,取前四个字节 - 编码:
Base58
编码
见 btckey.go 完整的由公钥生成地址的代码:
// ToAddress converts a Bitcoin public key to a compressed Bitcoin address string.
func (pub *PublicKey) ToNeoAddress() (address string) {
/* See https://en.bitcoin.it/wiki/Technical_background_of_Bitcoin_addresses */
/* Convert the public key to bytes */
pub_bytes := pub.ToBytes()
pub_bytes = append([]byte{0x21}, pub_bytes...)
pub_bytes = append(pub_bytes, 0xAC)
/* SHA256 Hash */
sha256_h := sha256.New()
sha256_h.Reset()
sha256_h.Write(pub_bytes)
pub_hash_1 := sha256_h.Sum(nil)
/* RIPEMD-160 Hash */
ripemd160_h := ripemd160.New()
ripemd160_h.Reset()
ripemd160_h.Write(pub_hash_1)
pub_hash_2 := ripemd160_h.Sum(nil)
program_hash := pub_hash_2
//wallet version
//program_hash = append([]byte{0x17}, program_hash...)
// doublesha := sha256Bytes(sha256Bytes(program_hash))
// checksum := doublesha[0:4]
// result := append(program_hash, checksum...)
/* Convert hash bytes to base58 check encoded sequence */
address = b58checkencodeNEO(0x17, program_hash)
return address
}
// b58checkencode encodes version ver and byte slice b into a base-58 check encoded string.
func b58checkencodeNEO(ver uint8, b []byte) (s string) {
/* Prepend version */
bcpy := append([]byte{ver}, b...)
/* Create a new SHA256 context */
sha256_h := sha256.New()
/* SHA256 Hash #1 */
sha256_h.Reset()
sha256_h.Write(bcpy)
hash1 := sha256_h.Sum(nil)
/* SHA256 Hash #2 */
sha256_h.Reset()
sha256_h.Write(hash1)
hash2 := sha256_h.Sum(nil)
/* Append first four bytes of hash */
bcpy = append(bcpy, hash2[0:4]...)
/* Encode base58 string */
s = b58encode(bcpy)
// /* For number of leading 0's in bytes, prepend 1 */
// for _, v := range bcpy {
// if v != 0 {
// break
// }
// s = "1" + s
// }
return s
}
WIF 是怎么来的?
WIF(Wallet Import Format)是由私钥在前面加了一个版本号字节 0x80
,在后面加了一个压缩标志的字节 0x01
,然后对这34个字节进行哈希,取哈希值的前4个字节作为校验码加在最后面,最后经过 Base58
编码得到:
- 前面加版本字节:
0x80
- 后面加压缩标志字节:
0x01
- 对这34个字节进行哈希:取哈希值的前4个字节加在最后面
- 编码:
Base58
编码
【注】其中“对这34个字节进行哈希”,我找的在线工具做的Hash计算,结果跟 NEO学习笔记,从WIF到地址 文章中的结果不一致,不知道怎么计算的,有了解的请留言,谢谢!
图解:
总结
欢迎留言讨论,有错误请指出,谢谢!
【联系我(QQ:3500229193)或者加入社群,请戳这里!】
参考链接
- http://www.cnblogs.com/crazylights/p/8166690.html
- https://github.com/CityOfZion/neo-swift/blob/master/NeoSwift/Account.swift
- https://developer.apple.com/documentation/security/1399291-secrandomcopybytes
- https://github.com/apisit/neo-wallet-address-go/blob/master/neowallet.go
- https://github.com/O3Labs/neo-utils/blob/master/neoutils/neowallet.go
- https://github.com/apisit/btckeygenie/blob/master/btckey/btckey.go
- https://www.zhihu.com/question/22399196
- https://mp.weixin.qq.com/s/jOcVk7olBDgBgoy56m5cxQ
- https://www.jianshu.com/p/af6328cc693e
- https://en.bitcoin.it/wiki/Wallet_import_format
- http://gobittest.appspot.com/PrivateKey
更新日志
- 2018.03.13 第一次更新
- 2018.03.14 第二次更新
- 2018.03.15 第三次更新
- 2018.03.21 第四次更新
- 2018.08.07 第五次更新