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官方公衆號
獲取更多一手社區資訊
▼