第六十篇 墨客区块链(MOAC BlockChain) 钱包账号管理

Nadia ·
更新时间:2024-09-20
· 956 次阅读

本文简单描述钱包账号管理的一些方法。

一、术语

1.1 gas,Gas Limit和Gas Price
在墨客区块链上,发送代币或调用智能合约、执行写入操作,需要支付矿工计算费用,计费是按照Gas计算的,Gas使用moac来支付。无论调用的方法是成功还是失败,都需要支付计算费用。即使失败,矿工也验证并执行交易(计算),因此必须和成功交易一样支付矿工费。

Gas Limit称为限额,它是用户愿意在一笔交易中花费Gas的最大数量。交易所需的Gas是通过调用智能合约执行多少代码来定义。 如果您不想花太多的Gas,通过降低Gas Limit将不会有太大的帮助。 因为您必须包括足够的Gas来支付的计算资源,否则由于Gas不够导致交易失败,您所设置的所有Gas limit也将消耗光。建议您在有充足moac情况下,将Gas Limit尽量设高,所有未使用的Gas将在转账结束时退还给您。

通过降低Gas Price可以节省矿工费用,但是也会减慢矿工打包的速度。矿工会优先打包 Gas Price设置高的交易,如果您想加快转账,您可以把Gas Price设置得更高,这样您就可以排队靠前。如果您不急,您只需要设置一个安全的 Gas Price,矿工也会打包您的交易。

1.2 moac单位
moac最小的单位是sha,最常用的单位是mc。

    'sha':    '1',
    'ksha':   '1000',
    'msha':   '1000000',
    'nano':   '1000000000',
    'micro':  '1000000000000',
    'milli':  '1000000000000000',
    'mc':     '1000000000000000000',


1.3 keystore
keystore是账户的一种表现形式,里面包含了账户的地址,账户密文的私钥和MAC地址等等一系列的信息,下面是一个keystore的完整信息。

{
  "address":"2ffe6e3816b6a84f509ea3be1b8e8bb024c894d8",
  "crypto":
  {
    "cipher":"aes-128-ctr",
    "ciphertext":"c302e468ad640ad6c43d51754caa60964ece820ad98ad0d5aa72a785a93d7a59",
    "cipherparams": {"iv":"f4609a583b28e48a6bda7b6bf1229a26"},
    "mac":"ef8872a3ad0a92a410b15b0e2e662d5cbfc98360d72b190a7b3189bd4151ebcf",
    "kdf":"pbkdf2",
    "kdfparams":
    {
      "c":262144,
      "dklen":32,
      "prf":"hmac-sha256",
      "salt":"5a757ae33c08a46c8a50f34a2a514503fd66481ea56aeab31e25da45ae3f1c39"
    }
 },
 "id":"49a53e88-4f8a-4858-80a4-02ad230da1d3",
 "version":3
}

1.5 元交易

元交易是这样一种发送交易的模式:发送方先对一个合法的交易签名,然后把该交易和签名通过链下传递的方式转交给一个中继方,该中继方愿意承担该笔交易的 gas 费并最终发送交易到网络中。

这种元交易模式很有用,因为发送方不再需要在发送账户中存有moac,从用户体验角度这很有益处。

元交易最终的目标地址一般都是某个合约,且在某种程度上,该合约知道,交易的签名方并不是交易实际的发送者。此时,交易 API 的 msg.sender 字段会返回中继方的地址,但其很可能并没有代表签名方操作的权利,所以在这个场景下(仅仅查看 msg.sender 字段)并没有太大意义。因此,许多元交易依赖链上的签名校验(通过 API 的 ecRecover 函数)来保证签名方账户的确是在一份合适的白名单里面(有权限操作该交易想要执行的指令)。

1.6 其他

Nonce交易计数器,即该账户主动发起的交易数量,从0开始计数。 助记词:通过算法从词库里面产生12-24个单词,目前支持多种语言。 随机种子:使用助记词通过算法产生的一个随机字符串。 私钥:钱包中最重要的东西,钱包实际上就对私钥进行管理,现在私钥有主私钥和派生私钥的说法。 主私钥:钱包私钥,由助记词产生的随机种子生成,主私钥被盗的话,你的整个钱包的钱将全部被盗。 派生私钥:由主私钥通过bip分层协议生成的私钥,派生私钥丢失,只会丢失单一账户上的数字资产。 备份钱包:其实也叫备份助记词,现在的钱包基本上都是用助记词恢复的。 备份私钥:对钱包的主私钥或者派生私钥进行备份,通过主私钥可以找回钱包中所有的账户,通过派生私钥只能找回其中的一个账户。 导入钱包:通过导入助记词或者私钥的方式生成以前的钱包。 导入私钥:通过导入私钥生成多链多账户钱包,或者单链单账户钱包。 单链钱包:只支持一条链的钱包,比方说比特币钱包。 多链钱包:支持所有币种的钱包。 单账户钱包:每种币只支持一个账户。 多账户钱包:每种币支持多个账户。 地址:类似于银行卡账户,通过地址可以查询到目前账户上的余额。 公钥:对所有人公开的秘钥。 签名:在发起数字资产转账时,所有的币种都要对转账的数据进行签名,签名的数据量对于不同的币种不一样。 空投:就是批量打币。 bip协议簇:HD分层钱包中的一个重要的规定,是数字货币发展的必然产物。 非确定性钱包:每个账户对应一个私钥,私钥不好管理。 确定性钱包:有主私钥,通过主私钥可以生产其他所有币种的私钥,但不能支持多账户。 确定性分层钱包:有主私钥,通过主私钥可以生产其他所有币种的私钥,支持多账户。 二、开源库keythereum

Keythereum是一个用于生成,导入和导出密钥的JavaScript工具。 这提供了一种在本地和Web钱包中使用同一帐户的简单方法。 它可用于可验证和存储钱包。

Keythereum使用相同的密钥派生函数(PBKDF2-SHA256或scrypt),对称密码(AES-128-CTR或AES-128-CBC)和消息验证代码。 您可以将生成的密钥导出到文件,将其复制到数据目录的密钥库,然后立即开始在您的本地客户端中使用它。

从版本0.5.0开始,keythereum的加密和解密函数都返回Buffers而不是字符串。 对于直接使用这些功能的人来说,这是一个重大改变。
2.1 使用keythereum生产keystore:

在生成keystore之前,你必须有一个nodeJs的环境,并且安装好keythereum。

npm install keythereum

或者使用压缩的浏览器文件dist/keythereum.min.js,以便在浏览器中使用。 使用代码引入。


生成一个新的随机私钥(256位),以及密钥派生函数使用的salt(256位),用于AES-128-CTR的初始化向量(128位)对密钥进行加密。 如果传递回调函数,则create是异步的,否则是同步的。

下面是生成keystore的代码:

var keythereum = require("keythereum");
var params = { keyBytes: 32, ivBytes: 16 };
var dk = keythereum.create(params);
keythereum.create(params, function (dk) {
    var password = "123456";
    var kdf = "pbkdf2";
    var options = {
        kdf: "pbkdf2",
        cipher: "aes-128-ctr",
        kdfparams: {
            c: 262144,
            dklen: 32,
            prf: "hmac-sha256"
        }
    };
    keythereum.dump(password, dk.privateKey, dk.salt, dk.iv, options, function (keyObject) {
        console.log(keyObject);
    });
});

运行结果:

2.2 将keystore保存到文件中:

dump创建一个对象而不是JSON字符串。 在Node中,exportToFile方法提供了一种将此格式化的密钥对象导出到文件的简便方法。 它在keystore子目录中创建一个JSON文件,并使用geth的当前文件命名约定(ISO时间戳与密钥派生的以太坊地址连接)。

代码如下:

var keythereum = require("keythereum");
var params = { keyBytes: 32, ivBytes: 16 };
var dk = keythereum.create(params);
keythereum.create(params, function (dk) {
    var password = "123456";
    var kdf = "pbkdf2";
    var options = {
        kdf: "pbkdf2",
        cipher: "aes-128-ctr",
        kdfparams: {
            c: 262144,
            dklen: 32,
            prf: "hmac-sha256"
        }
    };
    keythereum.dump(password, dk.privateKey, dk.salt, dk.iv, options, function (keyObject) {
        keythereum.exportToFile(keyObject);
    });
});

成功之后在你的keystore目录下将看到一个keystore格式的账号文件,如果出现错误,最可能的原因就是你的目录下没有keystore这个目录,当然以上代码中你也可以指定keystore的存储目录。

2.3 keystore的导入:

从keystore密钥库导入密钥只能在Node上完成。 将JSON文件解析为与上面的keyObject具有相同结构的对象。

以下代码需要在keystore目录下已经存在keystore格式的账号文件。

var keythereum = require("keythereum");
var datadir = "./";
var address = "0x1474bc7cddd0b26ecfa8e340a233eecea3f39e83";
var keyObject = keythereum.importFromFile(address, datadir);
 //console.log(keyObject)
keythereum.importFromFile(address, datadir, function (keyObject) {
   console.log(keyObject)
});

运行结果:

2.4 从keystore中恢复私钥:

这里恢复出来的私钥是buffer格式的,password是你设置的密码,keyObject就是keystore。

var keythereum = require("keythereum");
var datadir = "./";
var address= "0x1474bc7cddd0b26ecfa8e340a233eecea3f39e83";
const password = "123456";
var keyObject = keythereum.importFromFile(address, datadir);
var privateKey = keythereum.recover(password, keyObject);
console.log(privateKey);
console.log(privateKey.toString('hex'));

运行结果:

三、开源库eth-crypto

3.1 安装

npm install g -eth-crypto
// es6
import EthCrypto from 'eth-crypto';
// node
const EthCrypto = require('eth-crypto');

3.2 创建账号

node环境下运行以下代码,可以离线生成账号,得到私钥、公钥和地址。

const EthCrypto = require('eth-crypto');
const identity = EthCrypto.createIdentity();
//可以直接显示私钥、公钥和地址
console.log('privateKey:'+identity.privateKey);
console.log('publicKey: '+identity.publicKey);
console.log('address:   '+identity.address);
/* >  
privateKey:0xafd2fa1a57103993c16e2318f45206417c5cf840bb204603912b8dc54b7d8800
publicKey: 37b7d6b1b7bc05947d42ecfd3b63dee6cd54ff3ebfd5c6637f3224f45d05c2f4ad0b8ffea1abd2c2313965662cfd7a09e2c9dd683eec4eeab4b19838e01b9f1c
address:   0x16F119734F845a5348dd37175ca81059E66C90B3
*/

3.3 从私钥得到公钥

const EthCrypto = require('eth-crypto');
var privateKey  = "0xafd2fa1a57103993c16e2318f45206417c5cf840bb204603912b8dc54b7d8800";
//从私钥得到公钥
const publicKey = EthCrypto.publicKeyByPrivateKey(privateKey);
console.log('publicKeyByPrivateKey: '+publicKey); 
/* >       
publicKeyByPrivateKey: 37b7d6b1b7bc05947d42ecfd3b63dee6cd54ff3ebfd5c6637f3224f45d05c2f4ad0b8ffea1abd2c2313965662cfd7a09e2c9dd683eec4eeab4b19838e01b9f1c
*/

3.4 从公钥得到地址

const EthCrypto = require('eth-crypto');
const publicKey = "37b7d6b1b7bc05947d42ecfd3b63dee6cd54ff3ebfd5c6637f3224f45d05c2f4ad0b8ffea1abd2c2313965662cfd7a09e2c9dd683eec4eeab4b19838e01b9f1c";
//从公钥得到地址
const address = EthCrypto.publicKey.toAddress(publicKey);
console.log('addressByPublicKey: '+address); 
/* >       
addressByPublicKey: 0x16F119734F845a5348dd37175ca81059E66C90B3
*/

3.5 签名

const EthCrypto = require('eth-crypto');
const privateKey  = "0xafd2fa1a57103993c16e2318f45206417c5cf840bb204603912b8dc54b7d8800";
const message = 'hello,world!';
const messageHash = EthCrypto.hash.keccak256(message);
const signature = EthCrypto.sign(privateKey,messageHash);
console.log('signature: ' + signature); 
/*
signature: 0x6f9188a50f13d75fd64df378f11b025a48f089f1d51d60264e84f3690d9fd6c6117abff827b06ab9de98e9c2837d6d9a6451ed113cf4492c8e28a4e2bd377bf51c
*/

3.6 从签名信息获得地址

此处需要知道明文信息。

const EthCrypto = require('eth-crypto');
const signature = "0x6f9188a50f13d75fd64df378f11b025a48f089f1d51d60264e84f3690d9fd6c6117abff827b06ab9de98e9c2837d6d9a6451ed113cf4492c8e28a4e2bd377bf51c";
const signer = EthCrypto.recover(signature,EthCrypto.hash.keccak256('hello,world!'));
console.log('signer: ' + signer);
/*
signer: 0x16F119734F845a5348dd37175ca81059E66C90B3
*/

3.7 从签名信息获得公钥

此处需要知道明文信息。

const EthCrypto = require('eth-crypto');
const signature = "0x6f9188a50f13d75fd64df378f11b025a48f089f1d51d60264e84f3690d9fd6c6117abff827b06ab9de98e9c2837d6d9a6451ed113cf4492c8e28a4e2bd377bf51c";
const signer = EthCrypto.recoverPublicKey(signature,EthCrypto.hash.keccak256('hello,world!'));
console.log('signer: ' + signer);
/*
signer: 37b7d6b1b7bc05947d42ecfd3b63dee6cd54ff3ebfd5c6637f3224f45d05c2f4ad0b8ffea1abd2c2313965662cfd7a09e2c9dd683eec4eeab4b19838e01b9f1c
*/
四、交易

4.1交易确认

并不是我们查询区块链中的交易就说明这笔交易已经成功了,墨客是默认确认12次之后,此交易几乎不会被篡改,那么怎么计算确认次数呢?

确认次数 = 当前区块高度 - 交易所在区块高度 + 1

此处注意事项:交易有可能会被孤立,在执行此公式时需要验证一下区块中此交易是否还是在那个区块上,是否已经被回滚。

交易的gas费算是常识性的内容,可给用户一些参考值,让用户选择愿意支付的手续费。最笨拙而又有效的方法是定期观察一下区块链上交易成功的交易的 gasPrice 的大概范围,动态的调整一下价格,而 gasLimit 在不影响交易的情况下,尽可能稍微多一些,因为此部分如果未使用还会退换到交易发起账户中。

4.2交易中nonce维护

转账地址的合法性检查,此合法性检查可以避免后续很多问题的出现,比如 nonce 值的维护。 交易的金额检查,nonce 值检查(nonce 值会遇到的问题前面已经提到过),特别是私钥与节点分离之后自己来维护私钥时 nonce 值会是一个很大的问题,比如前一笔交易失败,nonce 值需要回退,此时后一笔交易已经发出,因为前一笔 nonce 没有被补齐,后一笔迟迟不会被交易。这些都需要业务进行特殊判断和处理。 查询一个地址 nonce,mc.getTransactionCount

4.3交易记录

墨客区块链只能通过遍历区块交易的方法来判断是否有对应账户的交易。相关操作步骤主要包括:

查询区块高度,比对是否是新生成的区块,mc.blockNumber; 查看区块内容及详细交易,mc.gethBlock; 比对交易的 toAddress 是否为本钱包的地址,如果是则记录此笔交易到数据库,并记录交易状态(确认次数等); 保证入库和记账的幂等性,因为会多次查询到同一笔交易; 作为钱包开发来说,实际需要解析所有交易,并保存到数据库备查。

主要参考文档:

1.https://blog.csdn.net/jiang_xinxing/article/details/84104304;

2.https://github.com/guoshijiang/blockchain-wallet/blob/master/chapter/readme.md;

3.https://github.com/pubkey/eth-crypto;


作者:Li_Yu_Qing



钱包 区块链

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章