原本一直都是靠比特币钱包生成公私钥的,但感觉一直不是很放心,尤其是 npm 包隐藏盗币代码后,一直感觉危险重重,加上货币交易所,也是存在倒闭或跑路的风险,毕竟是第三方。加之又看到 node12 支持了原生的 BigInt,想着是时候自己做个无第三方依赖的公私钥生成工具了。
私钥是如何产生的呢?简单来说就是在一个大数中选值,最后进行按照一些规则加密成我们所使用的私钥。我这边使用了两种方法实现,一个是随机法,一个是加密法生成。
// 简易 sha256 通过字符串生成摘要
function getPrivteOriginKeyByStr(strSeed) {
return crypto.createHash('sha256').update(strSeed).digest('hex');
}
这里就是直接使用 sha256 生成摘要,然后生成一个十六进制的私钥原值
const n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n;
function getPrivteOriginKeyByRand() {
let nHex = n.toString(16);
let privteKeyList = [];
let isZero = true;
for(let i=0;i<nHex.length;i++) {
let rand16Num = Math.round(Math.random()*parseInt(nHex[i],16));
privteKeyList.push(rand16Num.toString(16));
if(rand16Num>0) {isZero = false;}
}
if(isZero){getPrivteOriginKeyByRand();}
return privteKeyList.join('');
}
这里就是通过通过每位进行随机,再组合生成一个长串,目前 node 的随机数种子在 linux 中会取一个文件的指纹(这个文件是会不断变化的,以前看过代码,现在有点忘记叫什么文件了),所以你不用当心第一次生成都会是一样的值。
转换规则是原值加上版本做前缀,进行两次 sha256 加密,同时取前 4 个字节,进行 58 进制转换。如下。
function hex2sha256(hexStr) {
return crypto.createHash('sha256').update(Buffer.alloc(hexStr.length/2,hexStr, 'hex')).digest('hex');
}
function getPrivteKeyByOrigin(privteKeyOrigin) {
if(privteKeyOrigin.length!==64){
throw new Error('privte Key Origin length must be 64!')
}
let version = '80';
let sha1Str = `${version}${privteKeyOrigin}`;
let sha1 = hex2sha256(sha1Str);
let sha2Str = `${sha1}`;
let sha2 = hex2sha256(sha2Str);
let key = `${version}${privteKeyOrigin}${sha2.slice(0,8)}`;
return util.hex2Base58(key);
}
这里你可能会有几个疑问:
公钥原值生成其实是采用了椭圆加密算法,简单来说就是使用了 E : y^2 ≡ x^3 + ax + b (mod p)算法实现椭圆曲线,然后使用 K=kG,计算公钥,小 k 是私钥,大 K 我们要求的公钥,G 是椭圆曲线上的一个点,这是一个常量。
注意的点是 kG 并不是代表 k 和 G 点的乘基,而是又前一个点推导到后一个点。
可以用如下公司求解(其中 a,p 都是常量):
相同的点相加第一式: λ≡(3x1^2+ a)/2y1(mod p)
相同的点相加第二式: x3 ≡ λ^2 − 2x1 (mod p), y3≡ λ(x1 − x3) − y1 (mod p)
不同的点相加第一式: λ≡ ( y2 − y1 )/( x2 − x1 )(mod p)
不同的点相加第二式: x3 ≡ λ^2 − x1 − x2 (mod p), y3 ≡ λ(x1 − x3) − y1 (mod p)
第一步:比如 G 点的 x,y 坐标是 x1,y1,那么这时我需要求解 2G,那么先用 G 导入“相同的点相加第一式”,求出λ,然后“相同的点相加第二式”求解 x3,y3,这个点就是 2G 了。
第二步:现在有 G 和 2G 两个点,那么 3G 的求解则是将 2G 带入“不同的点相加第一式”去减 G,求出λ,然后再用“不同的点相加第二式”求解 x3,y3,这个点就是 3G 了。
然后 4G,5G,6G...kG 可以不断使用第二步循环执行来得出。
目前本人正在写这部分的原生纯算法实现,但是目前生成公钥因为不需要签名所以,我直接用了 node 的 ECDH 库,因为 ECDH 和 ECDSA 仅仅是椭圆加密算法的不同实现,所以生成公钥可以直接使用。如下。
function getPublicOriginKey(privteKeyOrigin) {
if(privteKeyOrigin.length!==64){
throw new Error('privte Key Origin length must be 64!')
}
const ecdh = crypto.createECDH('secp256k1');
ecdh.setPrivateKey(privteKeyOrigin,'hex');
return ecdh.getPublicKey('hex');
}
这里其实就是将公钥原值,先进行一次 hex2sha256 运算,然后使用再 ripemd160 加密上一步结果,将结果增加主网号 00 后再进行两次加密,取 ripemd160 加密结果和上一次结果的前四个字节,组成 key 走一个转 58 进制,加地址标示 1,生成公钥地址。如下。
function getPublicKeyByOrigin(publicKeyOrigin) {
let mainVersionHex = '00';
let addreeSign = '1';
let sha1 = hex2sha256(publicKeyOrigin);
let ripemd160Hex = crypto.createHash('ripemd160').update(Buffer.alloc(sha1.length/2,sha1, 'hex')).digest('hex');
let ripemd160HexUsed =`${mainVersionHex}${ripemd160Hex}`;
let sha2 = hex2sha256(ripemd160HexUsed);
let sha3 = hex2sha256(sha2);
let key = `${ripemd160HexUsed}${sha3.slice(0,8)}`;
return `${addreeSign}${util.hex2Base58(key)}`;
}
生成公钥地址可以说是不可逆的,首先用椭圆加密算法将私钥进行了数学难题加密,再通过摘要算法,只获取摘要信息,所以简单来说这个公钥地址也仅仅是原值的摘要而已,连要复原公钥原值都不太可能。
欢迎大家使用 bitcoin-key-generator(代码点此,感谢点星)来生成私钥,代码不多,无第三方库,可以看后再生成保证自己私钥安全。最后希望大家能推荐我一个杭州仓前不加班 955 的坑,工资可谈。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.