NEXT社區小課堂 | 第七課:NEO 之從私鑰到地址

NEXT社區 | 小課堂算法

因爲近期NEXT社區加入不少新的小夥伴,有在校大學生,有對區塊鏈感興趣的傳統企業從業者。爲了更方便、更系統的讓NEXT社區的夥伴們瞭解NEO的技術知識,所以咱們開設了小課堂,每週3節,向你們普及NEO相關的知識要點!swift

排版可能不清楚,詳情查看原文連接:https://mp.weixin.qq.com/s/tGeDX__bkIbfAjqoJniBZA安全

公衆號:NEONEXT,看得見的區塊鏈,Dapp孵化器app

 

NEXT社區小課堂 | 第七課dom

NEO 之從私鑰到地址工具

 


 

 

1、私鑰是怎麼來的?學習

 

私鑰是一個32字節的隨機數,這個數的範圍是介於 1 ~ 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141 之間。區塊鏈

 

見 Account.swift 類:ui

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
}

 

2、公鑰是怎麼來的?

 

公鑰是用私鑰經過橢圓曲線算法獲得的,可是沒法從公鑰算出私鑰。

 

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
}

 

3、地址腳本是怎麼來的?

 

地址腳本是由公鑰先後各加了一個字節獲得的,這兩個字節是固定的:

 

·  前面是: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)

 

4、地址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

 

5、地址是怎麼來的?

 

地址是由地址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
}

 

6、WIF 是怎麼來的?

 

WIF(Wallet Import Format)是由私鑰在前面加了一個版本號字節 0x80,在後面加了一個壓縮標誌的字節 0x01,而後對這34個字節進行哈希,取哈希值的前4個字節做爲校驗碼加在最後面,最後通過 Base58 編碼獲得:

 

 

·  前面加版本字節:0x80

·  後面加壓縮標誌字節:0x01

·  對這34個字節進行哈希:取哈希值的前4個字節加在最後面

·  編碼:Base58 編碼

 

【注】其中「對這34個字節進行哈希」,我找的在線工具作的Hash計算,結果跟 NEO學習筆記,從WIF到地址 文章中的結果不一致,不知道怎麼計算的,有了解的請留言,謝謝!

 

 

7、圖解:

 

NEO 之從私鑰到地址

 

 

 

  聯繫咱們  

微博:https://weibo.com/u/6724929880

官網:https://neonext.club/

QQ羣:612334080

電報:https://t.me/neonextop

twitter:https://twitter.com/NE0NEXT

 

掃碼關注NEO NEXT官方公衆號

獲取更多一手社區資訊

相關文章
相關標籤/搜索