前端技术

数字钱包初探

2019-05-28  本文已影响25人  一俢

最近区块链风靡互联网行业,个人也专门了解了相关技术,也买过数字货币,当然也有数字钱包。一直在思考这种去中心化的钱包,到底安不安全,原理是个啥:

带着这些问题,我们要研究一下数字钱包到底是咋回事?先搞清楚一些概念:

加密 & 对称加密 & 非对称加密

加密学在区块链技术中属于核心技术之一,钱包的生成也是由加密算法来完成的,当然如果讲述加密技术对我这个不专业的人来说不能讲述的非常明白,不过我们从对它的功能上来大概的了解一下相关概念。

通俗的讲:

专业的讲:

为什么要用非对称加密,应用场景在哪儿?

其安全性更好:对称加密的通信双方使用相同的秘钥,如果一方的秘钥遭泄露,那么整个通信就会被破解。而非对称加密使用一对秘钥,一个用来加密,一个用来解密,而且公钥是公开的,秘钥是自己保存的,不需要像对称加密那样在通信之前要先同步秘钥。

但是,非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。

签名

为了能让信息的接收方得知发送方的身份,数字签名技术运用而生。

数字签名是一种以电子形式存在于数据信息之中的,或作为其附件或逻辑上有联系的数据,可用于辨别数据签署人的身份,并表名签署人对数据信息中包含的信息的认可技术。

加密算法

加密算法有很多种:RSA、RC2、RC4、IDEA、RSA、DSA、ADS、MD5、PKCS、ECC 等等,想了解算法的同学自己百度一下。

钱包需要什么功能?

钱包的核心功能:

相关 Js 库

钱包的创建(私钥、公钥、地址)

创建钱包我们可以采用 ethereumjs-wallet 库来完成,它是基于 椭圆曲线的ECDSA 算法来创建密钥对的。看源码:

// ethereumjs-wallet 模块
Wallet.generate = function (icapDirect) {
    if (icapDirect) {
        while (true) {
            var privKey = crypto.randomBytes(32)
            if (ethUtil.privateToAddress(privKey)[0] === 0) {
                return new Wallet(privKey)
            }
        }
    } 
    else {
        return new Wallet(crypto.randomBytes(32))
    }
}

var Wallet = function (priv, pub) {
    //...
}

Object.defineProperty(Wallet.prototype, 'pubKey', {
  get: function () {
    if (!this._pubKey) {
        this._pubKey = ethUtil.privateToPublic(this.privKey)
    }
        return this._pubKey
    }
})

// ethereumjs-util 模块
exports.privateToAddress = function (privateKey) {
    return exports.publicToAddress(privateToPublic(privateKey))
}

var privateToPublic = exports.privateToPublic = function (privateKey) {
    privateKey = exports.toBuffer(privateKey)
    // skip the type flag and use the X, Y points
    return secp256k1.publicKeyCreate(privateKey, false).slice(1)
}

exports.pubToAddress = exports.publicToAddress = function (pubKey, sanitize) {
    pubKey = exports.toBuffer(pubKey)
    if (sanitize && (pubKey.length !== 64)) {
        pubKey = secp256k1.publicKeyConvert(pubKey, false).slice(1)
    }
    assert(pubKey.length === 64)
    // Only take the lower 160bits of the hash
    return exports.sha3(pubKey).slice(-20)
}

exports.sha3 = function (a, bytes) {
    a = exports.toBuffer(a)
    if (!bytes) bytes = 256

    var h = new SHA3(bytes)
    if (a) {
        h.update(a)
    }
    return new Buffer(h.digest('hex'), 'hex')
}

对于使用者非常简单:

相关文档:https://github.com/ethereumjs/ethereumjs-wallet

const ethUtil = require('ethereumjs-util')
const Wallet = require('ethereumjs-wallet');

// 生成钱包
var wallet = Wallet.generate();
var privateKey = wallet.getPrivateKey(); // 返回 Buffer,可以通过 wallet.getPrivateKeyString() 直接得到字符串
var publicKey = wallet.getPublicKey(); // 返回 Buffer,可以通过 wallet.getPublicKeyString() 直接得到字符串
var address = wallet.getAddress(); // 返回 Buffer,可以通过 wallet.getAddressString() 直接得到字符串

// 导入钱包
var privateKey2 = ethUtil.toBuffer('0xe601e598111629240e4dc6ec7a95534e025838bd0f638dabad9ad4152d80443b');
var wallet2 = Wallet.fromPrivateKey(privateKey2);
var publicKey2 = wallet2.getPublicKey();

查询钱包的资产

查询钱包资产通过 web3.js 很容易实现:

var balance = web3.eth.getBalance("0x407d73d8a49eeb85d32cf465507dd71d507100c1");
console.log(balance); // instanceof BigNumber
console.log(balance.toString(10)); // '1000000000000'
console.log(balance.toNumber()); // 1000000000000

钱包交易

钱包交易的过程:

交易对象

{
    nonce: '0x00',
    gasPrice: '0x01',
    gasLimit: '0x01',
    to: '0x633296baebc20f33ac2e1c1b105d7cd1f6a0718b',
    value: '0x00',
    data: '0xc7ed014952616d6100000000000000000000000000000000000000000000000000000000',
    // EIP 155 chainId - mainnet: 1, ropsten: 3
    chainId: 3
}

参考以太坊文档:

构建 data

如果是合约交易,需要通过合约信息来构建 data 字段。这一过程相对复杂,可以参考Ethereum Contract ABI 分两大过程:

源代码:https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js

对于使用者非常简单

相关文档:https://github.com/ethereumjs/ethereumjs-abi

var abi = require('ethereumjs-abi');

var methodID = abi.methodID('sam', ['bytes', 'bool', 'uint256[]']);
// returns the encoded binary (as a Buffer) data to be sent
var encoded = abi.rawEncode(['bytes', 'bool', 'uint256[]'], ['dave', true, [1, 2, 3]]);

var data = methodID.toString('hex') + rawEncode.toString('hex');
console.log(data);

签名

交易数据构造好后,接下来我们将数据进行签名,并序列化,最后的数据就可以进行交易了,继续看源代码:

// ethereumjs-tx 模块
Transaction.prototype.sign = function sign(privateKey) {
    var msgHash = this.hash(false);
    var sig = ethUtil.ecsign(msgHash, privateKey);
    if (this._chainId > 0) {
        sig.v += this._chainId * 2 + 8;
    }
    Object.assign(this, sig);
};

Transaction.prototype.hash = function hash(includeSignature) {
    if (includeSignature === undefined) includeSignature = true;

    // EIP155 spec:
    // when computing the hash of a transaction for purposes of signing or recovering,
    // instead of hashing only the first six elements (ie. nonce, gasprice, startgas, to, value, data),
    // hash nine elements, with v replaced by CHAIN_ID, r = 0 and s = 0

    var items = void 0;
    if (includeSignature) {
        items = this.raw;
    } else {
        if (this._chainId > 0) {
            var raw = this.raw.slice();
            this.v = this._chainId;
            this.r = 0;
            this.s = 0;
            items = this.raw;
            this.raw = raw;
        } else {
            items = this.raw.slice(0, 6);
        }
    }

    // create hash
    return ethUtil.rlphash(items);
};

// ethereumjs-util 模块
exports.ecsign = function (msgHash, privateKey) {
    const sig = secp256k1.sign(msgHash, privateKey)

    const ret = {}
    ret.r = sig.signature.slice(0, 32)
    ret.s = sig.signature.slice(32, 64)
    ret.v = sig.recovery + 27
    return ret
}

同样对于使用者非常简单

相关文档:https://github.com/ethereumjs/ethereumjs-tx

const EthereumTx = require('ethereumjs-tx');

var privateKey = ...;
var txParams = {
    nonce: '0x00',
    gasPrice: '0x09184e72a000', 
    gasLimit: '0x2710',
    to: '0x0000000000000000000000000000000000000000', 
    value: '0x00', 
    data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057',
    // EIP 155 chainId - mainnet: 1, ropsten: 3
    chainId: 3
};

var tx = new EthereumTx(txParams);
tx.sign(privateKey);

var serializedTx = tx.serialize(); // 这是最终交易需要发送的数据

估算 Gas

需要签名的交易,需要估算 Gas 费用,如果给一个不合理的 Gas 交易不会发送成功。我们可以通过 Web3 来估算一个相对合理的 Gas,让交易能够顺利的进行。

var transactionObject = {
    nonce: '',
    gasPrice: '',
    from: '',
    to: '',
    value: '',
    data: '',
}

web3.eth.estimateGas(transactionObject, function(err, res) {
    if (!err)
        console.log(res);
});

发送交易

发送交易我们使用 web3 的协议很容易就能搞定了:

var transactionObject = {
    nonce: '',
    gasPrice: '',
    gasLimit: '',
    from: '',
    to: '',
    value: '',
    data: '',
}

web3.eth.sendTransaction(transactionObject, function(err, address) {
    if (!err)
        console.log(address);
});

or

// ...
// 32字节的16进制格式的交易哈希串
web3.eth.sendRawTransaction(serializedTx.toString('hex'), function(err, hash) {
    if (!err)
        console.log(hash);
});

钱包交易过程

Flowchart

回顾一下钱包的核心功能:

有了以上几个核心方法,你就可以完成数字钱包应用了。

钱包核心 SDK 的封装

为了简化以上的操作,并且让钱包具有更好的扩展性(支持以太币、比特币等),我们将上面的整过过程进行一次封装,让开发人员更好的使用,我们做成了 trip-wallet

Install

yarn add trip-wallet
Or
npm install trip-wallet

Usage

import Wallet from 'trip-wallet';

let wallet = Wallet('eth');
wallet.generate();
wallet.setProvider('http://host:port');

// async/await
let balance = await wallet.getBalance(wallet.address);

// Promise
wallet.getBalance(wallet.address).then(res => {
    balance = res;
}, err => {

});

Object & Attributes

Methods

eth-util

钱包 App 的整体架构

Wallet

问题:

我们了解了一下钱包的大致原理后,来看看最早提出来的问题:

所以,中本聪通过这一独特的思维,将用户钱包信息(账户体系)全部由用户自己本地来管理;账本或者交易(公开信息)中除了钱包地址没有存储任何其它帐户信息。这样一来区块链看起来是公开透明又是安全可靠的。

〖坚持的一俢〗

上一篇 下一篇

猜你喜欢

热点阅读