继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

NEO 之从私钥到地址

慕容森
关注TA
已关注
手记 414
粉丝 183
获赞 655

学习 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)
        }

然后根据私钥(用 privatekeyDataHexString 作为参数)生成一个钱包,见 neo-utils

var error: NSError?guard let wallet = NeoutilsGeneratePublicKeyFromPrivateKey(pkeyData.fullHexString, &error) else { return nil }
// Generate a wallet from a private keyfunc 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.gobtckey.go

// Generate a wallet from a private keyfunc 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到地址 文章中的结果不一致,不知道怎么计算的,有了解的请留言,谢谢!

图解:

NEO 之从私钥到地址

总结

欢迎留言讨论,有错误请指出,谢谢!

参考链接


作者:宅小馒
原文链接
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP