美文网首页
创建数字钱包(一)账号生成

创建数字钱包(一)账号生成

作者: lambeta | 来源:发表于2019-01-23 18:45 被阅读21次

    椭圆曲线数字签名算法生成私钥

    Secp256k1
    通过椭圆曲线数字签名算法生成私钥和公钥,其中SEC(Standards for Efficient Cryptography)是专门利用ECDSA或者其可选项Schnorr算法来产生高效的加密方法。
    特点是生成密钥很快。

    Scep256k1 基本特性

    • secp256k1 ECDSA signing/verification and key generation.
    • Adding/multiplying private/public keys.
    • Serialization/parsing of private keys, public keys, signatures.
    • Constant time, constant memory access signing and pubkey generation.
    • Derandomized DSA (via RFC6979 or with a caller provided function.)
    • Very efficient implementation.

    讲解代码

    步骤

    1. 生成私钥
    2. 加密私钥
    3. 生成 keyObject 对象
    4. 从keyObject对象中恢复私钥

    生成私钥

    下面利用 keythereum[1] 产生符合以太坊的密钥,并产生keyObject文件

    const params = { keyBytes: 32, ivBytes: 16 };
    let {privateKey, salt, iv} = keythereum.create(params);
    

    keythereum可以产生私钥,以及后面加密私钥所用的PBKDF2算法需要的salt,和加密aes-128-ctr私钥的iv值。

    得到私钥之后,我们可以通过私钥生成公钥。

    let privateKeyBuffer = Buffer.from(privateKey, "hex") // or "base64"
    let publicKey = secp256k1.publicKeyCreate(privateKeyBuffer, false).slice(1);
    let address = "0x" + keccak256(publicKey).slice(-20).toString("hex");
    

    加密私钥

    利用KDF算法基于password派生出密钥,然后利用这个密钥加密我们的私钥。

    const password = "Hello,Ethereum"
    const options = {
        kdf: "pbkdf2",
        cipher: "aes-128-ctr",
        kdfparams: {
            c: 262144,
            dklen: 32,
            prf: "hmac-sha256"
        }
    };
    const keyObject = keythereum.dump(password, privateKey, salt, iv, options);
    

    这就是产生keyObject基本思路。我们在看看dump函数到底做了什么

    this.marshal(this.deriveKey(password, salt, options), privateKey, salt, iv, options);
    

    deriveKey(...) 的源码如下:

    this.crypto.pbkdf2Sync(
            password,
            salt,
            options.kdfparams.c || this.constants.pbkdf2.c,
            options.kdfparams.dklen || this.constants.pbkdf2.dklen,
            prf //hmac-sha256
          );
    

    这里基于password生成的derivedKey,这个密钥并不是我们要用的私钥,而是用来加密先前生成的privateKey的,加密的过程在marshal函数中调用的encrypt函数里。

    let ciphertext = this.encrypt(privateKey, derivedKey.slice(0, 16), iv, algo).toString("hex");
    

    encrypt函数,如下:

    var cipher, ciphertext;
    algo = algo || this.constants.cipher;
    if (!this.isCipherAvailable(algo)) throw new Error(algo + " is not available");
    
    //加密过程
    cipher = this.crypto.createCipheriv(algo, this.str2buf(key), this.str2buf(iv));
    ciphertext = cipher.update(this.str2buf(plaintext));
    
    return Buffer.concat([ciphertext, cipher.final()]);
    

    此处的ciphertext代表的是privateKey,而key则是derivedKey

    生成 keyObject 对象

    得到了加密后的ciphertext之后,开始组装keyObject对象并返回。

    keyObject = {
          address: this.privateKeyToAddress(privateKey).slice(2),
          crypto: {
            cipher: options.cipher || this.constants.cipher,
            ciphertext: ciphertext,
            cipherparams: { iv: iv.toString("hex") },
            mac: this.getMAC(derivedKey, ciphertext)
          },
          id: uuid.v4(), // random 128-bit UUID
          version: 3
        };
    keyObject.crypto.kdf = "pbkdf2";
          keyObject.crypto.kdfparams = {
            c: options.kdfparams.c || this.constants.pbkdf2.c,
            dklen: options.kdfparams.dklen || this.constants.pbkdf2.dklen,
            prf: options.kdfparams.prf || this.constants.pbkdf2.prf,
            salt: salt.toString("hex")
          };    
    

    privateKeyToAddress(...)方法里首先通过privateKey产生publicKey,然后使用keccak256哈希publicKey得到地址。

    具体实现如下:

    let privateKeyBuffer = Buffer.from(privateKey);
    let publicKey = secp256k1.publicKeyCreate(privateKeyBuffer, false).slice(1);
    let address = "0x" + keccak256(publicKey).slice(-20).toString("hex");
    

    keccak256(publicKey) 产生了32bytes,截取尾部20bytes转换成十六进制之后就是40字符,加上前导0x之后,就是42个字符的以太坊地址,比如:0x0f645438395206b408e52be4fcf4bc21c330bfa2

    从keyObject对象中恢复私钥

    有了keyObject和密码就可以恢复原来的私钥

    let privateKey = keythereum.recover(password, keyObject)
    

    可以想到,recover方法中,首先会利用password和keyObject中的salt派生出当初的密钥derivedKey,然后把加密过的私钥ciphertext和derivedKey, iv作为原来加密算法aes-128-ctr的输入参数,成功解密后返回明文的私钥。

    具体代码如下:

    verifyAndDecrypt(this.deriveKey(password, salt, keyObjectCrypto), salt, iv, ciphertext, algo)
    

    这里首先得到了derivedKey,然后验证并解密kyeObject中的ciphertext,如下:

    function verifyAndDecrypt(derivedKey, salt, iv, ciphertext, algo) {
      var key;
      if (self.getMAC(derivedKey, ciphertext) !== keyObjectCrypto.mac) {
        throw new Error("message authentication code mismatch");
      }
      if (keyObject.version === "1") {
        key = keccak256(derivedKey.slice(0, 16)).slice(0, 16);
      } else {
        key = derivedKey.slice(0, 16);
      }
      return self.decrypt(ciphertext, key, iv, algo);
    }
    

    注意这里的mac值比较,确保了ciphertext没有被人篡改才有解密的必要。

    参考实现

    1. NodeJS
    2. Bitcoin-core

    1. Keythereum

    相关文章

      网友评论

          本文标题:创建数字钱包(一)账号生成

          本文链接:https://www.haomeiwen.com/subject/bzysjqtx.html