NEO 之从私钥到地址

学习 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 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.gobtckey.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到地址 文章中的结果不一致,不知道怎么计算的,有了解的请留言,谢谢!

图解:

NEO 之从私钥到地址

总结

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

【联系我(QQ:3500229193)或者加入社群,请戳这里!】

参考链接

更新日志

  • 2018.03.13 第一次更新
  • 2018.03.14 第二次更新
  • 2018.03.15 第三次更新
  • 2018.03.21 第四次更新
  • 2018.08.07 第五次更新
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 在比特币中,经常出现三个词:私钥,公钥和地址。他们是什么意思呢?他们之间又有什么样的关系呢?搞清楚他们之间的关系和...
    姜家志阅读 44,136评论 3 48
  • 比特币改进协议BIP32(翻译) 英文原文地址: https://github.com/bitcoin/bips/...
    Pony小马阅读 5,094评论 1 48
  • 我有一壶酒,足以慰风尘。 相邀东门外,兄弟两三人。 苦乐杯中醉,冷暖腹内焚。 此时情正切,出口成诗文。
    二离阅读 1,865评论 0 0
  • 对于被钢筋水泥包围的孩子来说,大自然是最好的课堂。在这个课堂上,生命各式各样,多姿多彩,每一个物种都演绎着精彩的故...
    黄老邪123阅读 672评论 0 1
  • 想起之前拥有相机的时候 。 每个周末都去拍拍拍 。 现在,大黑莓里的照片满屏都是猫咪 。 那时候,还撩了好多模特 ...
    miaomiaowu阅读 170评论 0 0