NODEJS中椭圆曲线签名和验证
上文 介绍了基于任意加密货币的用户身份认证设计思路,本文承接这个思路,介绍在Nodejs中如何做基于椭圆曲线的签名和验证方法,为后续生成完整的身份认证库和Demo做准备。
我调查了两个可用的库,一个是 steem-js , 另一个是 bitsharesjs ,目前倾向于使用后者,原因本文逐步展开。为叙述方便,首先把签名和验证的概念源代码贴上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | const steem = require('steem');
const bitshares = require('bitsharesjs');
const bitsharesjsws = require('bitsharesjs-ws');
const env = process.env;
const role = 'active';
// Steem 有4种角色: owner, posting, active, memo
// Bitshares 有3种角色: owner, active, memo
bitsharesjsws.ChainConfig.setChainId(
'4018d7844c78f6a6c41c6a552b898022310fc5dec06da467ee7905a8dad512c8');
// bitshares主链, 自动设定前缀为BTS(默认为GPH)
// bitsharesjsws.ChainConfig.setPrefix('STM');
// 假如取消上面这行注释,还可以手工设定前缀,这样的话可以兼容Steem区块链。
const userName = env['username'] || 'test';
const passphrase = env['password' ] || 'the very long and stupid password';
var wifSteem = steem.auth.toWif(userName, passphrase, role);
var btsKeys = bitshares.Login.generateKeys(userName, passphrase, [role]);
// Steem的API提供wif格式的私钥,而Bitshares的库提供可以计算(签名和验证)的私钥和公钥
// Wif格式来自BitCoinWiki
console.log('private key( Wallet import format ):');
console.log('steem:', wifSteem);
console.log('bitshares:', btsKeys.privKeys[role].toWif());
// 从上面可以看出 bitshares和Steem采用相同的算法生成私钥(私钥相同)
console.log('public key:');
console.log('steem', steem.auth.wifToPublic(wifSteem));
console.log('bitshares:', btsKeys.pubKeys[role]);
// 公钥也相同,但是前缀与哪个链有关
// Signature function is not exported from steem.
const bsSignature = bitshares.Signature;
const testBuffer = new Buffer('a random buffer for sign/verify test', 'utf-8');
const testBuffer2 = new Buffer('another random buffer', 'utf-8');
var sign = bsSignature.signBuffer(testBuffer, btsKeys.privKeys[role]);
// console.log(bsSignature.verifyBuffer(signBuffer, btsKeys.pubKeys[role]));
console.log(sign.verifyBuffer(testBuffer, btsKeys.privKeys[role].public_key));
console.log(sign.verifyBuffer(testBuffer2, btsKeys.privKeys[role].public_key));
console.log(sign.verifyBuffer(testBuffer, bitshares.PublicKey
.fromStringOrThrow(btsKeys.pubKeys[role])));
var sign2 = bsSignature.fromBuffer(sign.toBuffer());
console.log(sign2.verifyBuffer(testBuffer, btsKeys.privKeys[role].public_key));
console.log(sign2.verifyBuffer(testBuffer2, btsKeys.privKeys[role].public_key));
console.log(sign.verifyBuffer(testBuffer, bitshares.PublicKey
.fromStringOrThrow(btsKeys.pubKeys[role])));
|
然后逐渐讲解。 整个代码分为两个部分,1-34行是公私钥生成;36-53行是签名和验证。
首先看公私钥生成。 前两行引用了2个库,第1行是steem, 第2行是bitsharesjs,无需多说。第3行引用了一个bitsharesjs依赖的底层库,与第10-15行有关,下文详解。第4行引用env,与第17、18行有关:可以使用环境变量设置自己的用户名和密码。第5行设定一个常数角色,第6、7行有注释。 关于角色,这里还要解释下,在bitshares和steem体系下,区块链上的广播都不涉及密码,不同的行为需要不同角色的公私钥对:私钥签名,公钥验证;或者公钥加密,私钥解密。例如active角色与转账行为有关,涉及转账的需要这对公私钥;而owner角色与账户设定有关,修改账户设定需要这对公私钥;而memo角色与备注相关, 可以在转账给他人的时候利用对方的memo公钥加密,而对方需要利用memo私钥解密。由于Steem面向内容市场,因此多了一个posting角色,用于发表文章、支持反对等等。Steem的上层(steemit网站)设定,通过用户名、密码、角色的组合,使用确定性算法生成公私钥对;而bitshares刚刚支持这种方法。
先跳过第10-15行。
第17-18行声明了两个变量,用户名和密码。第21-22行分别使用Steemjs和Bitsharesjs库生成了两对私钥,注意23-24行的注释很重要,“Steem的API提供wif格式的私钥,而Bitshares的库提供可以计算(签名和验证)的密钥,包含私钥和公钥”:这也就是本文一开始提到的我倾向于bitshares库的一个原因。“Wif格式来自BitCoinWiki”: Wif,全称Wallet Import Format,钱包导入格式,来自 这里 。Wif的发明,完全是解决人机接口的问题:对于底层算法,私钥是一个大整数,而这个大整数不方便人们导入导出,因此用这种文本格式来记录、拷贝、导入导出;在bitshares客户端看到的私钥,以及 steemit账号设定里面看到的私钥,都是这种格式。
第26-29行分别以Wif格式输出了两个库生成的私钥,如果读者运行程序,就会发现二者相同。
第31-34行输出公钥,这里面公钥的格式是石墨烯区块链自定义的,与Wif类似,但是前缀与哪个链相关。读者运行程序可以看到这两行的输出大部分是一样的,但前缀不同。Steem公钥前缀是STM,Bitshares默认公钥前缀是GPH。程序一行不改,33行运行结果,前缀是BTS;如果将10行注释掉,可以看到33行运行结果,前缀是GPH;如果将14行注释去掉,33行运行结果,前缀是STM。 这里回过头来说明一下第10行和14行所涉及的两个API。第10行涉及一个API: bitsharesjsws.ChainConfig.setChainId,设定哪个公有链,一共包含4个,具体可查看 这里的代码 ,(不过这个列表并不包含Steem链)。设定公有链直接就会有一个副作用,同时设定了 公钥的前缀,当然也可直接设定前缀,利用的就是bitsharesjsws.ChainConfig.setPrefix 这个API,正如第14行那样。
公私钥生成的代码解析到此结束,下面阐述一下签名和验证。第36-37行,定义了一个新的常数,bsSignature,这是bitsharesjs库提供的签名接口,而Steem库并没有对外暴露类似的接口。39-40行定义了两个不相同的缓冲区,而41行利用上面生成的私钥,对第一个缓冲区做了一个签名。45-47行分别是这个签名对两个缓冲区的验证方法,运行时可以看出分别输出 true 、 false、true。其中, 45行和47行都是对签名的缓冲区进行验证,只是公钥的来源稍稍有些区别;而46行对不同的缓冲区进行验证,因此输出false。
第49行重新生成了一个签名,目的是模拟签名的序列化和反序列化过程,可以使用这种方法,序列化后将签名在网络上传输,对端反序列化得到一个签名对象,并且50-52行重新利用新得到的签名对象验证两个缓冲区,结果与45-47行完全相同。