私钥:51位,以数字“5”开头,是不支持压缩格式的私钥;
52位,以“K”或“L”开头,是支持压缩格式的私钥;
5开头的私钥都是不支持压缩格式的私钥,而L和K开头的都是支持压缩格式的私钥。
公钥:33位,支持未压缩公钥
34位,支持压缩公钥
地址:“1”开头的地址长26~34位
“3”开头的地址长34位
以太坊私钥和地址位数:
privateKey 64
publicKey. 128
Address. 40
Credentials 64
一个私钥可以有一个压缩的公钥,和一个未压缩的公钥,每个公钥都会生成一个地址,上面私钥就会有两个地址分别为:1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN(未压缩公钥),1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV(压缩公钥),这两个地址都是这一个私钥的地址,每个地址上面的钱都可以用这个私钥花费。但是目前大多数的应用都是支持压缩格式的私钥。
比特币地址是由用户的公开密钥经过SHA-256散列运算后,再通过RIPEMD-160散列运算而得,其长度固定为160个比特(bits),通常会利用Base-58将之编码成一串由英文字母和数字所组成的字符串,以方便显示或传播,其特征是皆以“1”或者“3”开头,英文区分大小写,但不包括“IlO0”等字符,“1”开头的地址长26~34位,“3”开头的地址长34位,例如”1DwunA9otZZQyhkVvkLJ8DV1tuSwMF7r3v“,地址也可编码成快速反应矩阵码(QR-Code,二维码)的形式让移动设备能够便捷地读取复制。
一个人可以生成并拥有许多比特币地址,并用在不同的交易上,而且除非自己揭露,否则外人无法看出其中的关系。可用的比特币地址数量接近2161个。
比特币私密密钥通常由51比特或52比特字符表示,其编码方式与比特币地址相似。51比特标记法由数字“5”开头,52位标记法由“K”或“L”开头。比特币地址是由比特币公开密钥进行散列运算得出的,公开密钥是可以通过私密密钥推算出的。所以掌握私密密钥就可以推算出私密密钥对应的地址;但不能使用比特币公钥反推出私钥。
先说说私钥,例如这样的一段字符串:
5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,就是一个私钥。只要是正确支持比特币协议的应用都可以把这段字符串识别为私钥,转换成公钥,再转换为地址,如果对应的地址上面有比特币,就可以使用这个私钥花费上面的比特币。公钥和地址的生成都依赖私钥,所以私钥才是比特币世界里面最重要的,有了私钥就能生成公钥和地址,就能够花费对应地址上面的比特币。私钥花费比特币的方式就是对这个私钥拥有的未花费的交易进行签名(加密)。
私钥本质上是32个byte组成的数组,1个byte等于8位二进制,一个二进制只有两种可能性0或者1,所以私钥的总数就是2^(8*32)=2^256个,这个数量已经超过了宇宙中原子的总数,想要遍历所有的私钥,耗尽整个太阳的能量也是不可能成功的。我们所说的比特币私钥的是密码学上面安全的,并不是不可能出现重复的私钥,而是说不可能通过遍历所有的私钥方式,或者其它的方式,找到上面有比特币的私钥,所以私钥是密码学上安全的。
私钥的总数量很大,但是私钥的生成却是要依赖随机,不依赖随机就会大大的降低生成私钥的概率空间,密码学上面安全的随机是指,是指随机是不可预测的,随机的结果是不可遍历的。密码学上安全的随机生成的私钥才能用来存比特币,如果随机不安全,你得到的私钥有可能会和别人的相同,那么你的比特币就有丢失的可能。
32个byte组成的数组是由256个0或者1组成的,如果显示出来,不仅仅是识别率太差,而且太长。因此私钥就会被转换成上面的样子,上面私钥的样子是对32个byte的数组做了Base58的转换,Base58是用于Bitcoin中使用的一种独特的编码方式,主要用于产生Bitcoin的钱包地址和私钥。相比Base64,Base58不使用数字"0",字母大写"O",字母大写"I",和字母小写"l",以及"+"和"/"符号。主要就是为了肉眼容易识别,在输入的时候不容易打错,不过我更倾向于使用二维码的方式扫描私钥,毕竟上面那段没有规律的字符串输入起来还是挺费劲的。
我们看到的私钥除了以5开头的以外,还有以”L”和”K”开头的私钥,为什么会出现这样的情况呢?5,L,K又带代表什么呢?关于这部分的内容就是说到公钥了。
公钥是由私钥生成的,通过椭圆曲线(ECPoint)生成,一个私钥经过椭圆曲线变换之后会生成一个65个byte的数组,一般我们会看到这样的一个公钥:04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,公钥一般是把byte数组是经过hex(16进制)的处理之后显示出来的,不同于私钥的Base58,公钥是用来解开私钥签名的数据,使用私钥签名交易之后,会把自己的公钥一起发送,私钥签名的数据可以使用公钥解密,发送公钥之后旷工才能验证私钥的签名的正确性(能不能解开),私钥和公钥是成对出现的,一个私钥签名的数据,只有对应的公钥才能解开,而地址也是从公钥生成的,这样就可以验证花费的交易是不是属于这个地址的。
至于为什么会出现5开头或者L,K开头的私钥,问题就是在公钥上面,例子中的私钥不仅仅包含了32个byte数组的信息,还是私钥version的信息和公钥是否压缩的信息,早期的比特币开发者并不知道生成的公钥是可以压缩的,压缩的公钥只有33个byte,而未压缩的公钥有65个byte。压缩的公钥对比特币的意义更大,因为比特币是去中心化的p2p加密货币,每个节点都会拥有完整的交易记录,除了coinbase(挖矿得到的比特币)以外,每个交易都会发送公钥,支持压缩格式的公钥,交易数据就会减少32个字节,这对整个比特币网络是非常有意义的,整个比特币网络的数据就会因此而减少不少。
而对私钥进行Base58编码的时候,支持未压缩公钥的私钥其实是33位byte数组,第一位存放版本信息,当前值为128。支持压缩公钥的私钥是34位,同样是第一位是version信息,它的值也是128,而多出来的一位是最后一个byte是用来存放是否压缩信息,1就表示是支持压缩格式的公钥,5开头的私钥都是不支持压缩格式的私钥,而L和K开头的都是支持压缩格式的私钥。
公钥是否压缩除了对私钥的显示有影响以外,还是地址有影响。地址是由公钥产生的,看到的地址都是Base58编码处理的,地址的生成比较复杂,说地址之前不得不先说下hash160,地址是由公钥生成的的,但是公钥生成地址的时候会先生成一个hash160。
生成的过程是,先对公钥做一次SHA256,再对得到的结果做一次RIPEMD160,再从结果中取20个byte的数组,这个得到的数组就是得到的hash160,例如:9a1c78a507689f6f54b847ad1cef1e614ee23f1e,在这个20位的byte数组前面再加上一个byte,就是地址的version信息(当前为0)得到一个21位的byte数组,对它进行Base58转换就是我们看到的地址的样子,也就是说hash160和是可以和地址相互转换的。
一个私钥可以有一个压缩的公钥,和一个未压缩的公钥,每个公钥都会生成一个地址,上面私钥就会有两个地址分别为:1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN(未压缩公钥),1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV(压缩公钥),这两个地址都是这一个私钥的地址,每个地址上面的钱都可以用这个私钥花费。但是目前大多数的应用都是支持压缩格式的私钥。 |
----walletAddress====: UTC--2018-07-27T13-46-50.663--18b981fcc3ce9ffb55b4b6b799269c3e5052f045.json length=75
filePath=======: /data/user/0/org.cloudworks.wallet/files
privateKey=======: c2faaaf7ab423418c37e5c9f9476d9876d9c940b1adb4894fb1407e8d4d77489 length=64
publicKey=======741f3be65007eb2a6ca9a64d10817ffdf413cdc2e955d73a382031c331c66d749aa7acf61b8ee4825f0602faddd2c3beb2063026d2a4c7406abc502f88635c3e length=128
07-30 10:07:45.985 3134-3134/org.cloudworks.wallet E/address=======: a3896d9358eec47e1c23307b92128c9b8d7aa676 length=40
07-30 10:07:49.114 3134-3134/org.cloudworks.wallet E/----walletAddress====: a3896d9358eec47e1c23307b92128c9b8d7aa676 length=40
07-30 10:07:49.114 3134-3134/org.cloudworks.wallet E/WalletStorage: -------load------wallets.dat address = /data/user/0/org.cloudworks.wallet/files/wallets.dat
07-30 10:07:52.225 3134-3134/org.cloudworks.wallet E/----credentials====: c2faaaf7ab423418c37e5c9f9476d9876d9c940b1adb4894fb1407e8d4d77489 length=64
{"version":3,"id":"0E6C154D-21AD-4005-B198-6713C60FB19F","x-ethers":{"gethFilename":"UTC--2018-07-31T15-47-03.0Z--c8b48dc25b2009540265498584146f4f0aaf1707","mnemonicCiphertext":"685912c3aca60853236200118c446d59","mnemonicCounter":"ea5ecbdf1a353d47ce254390a1ddd446","client":"ethers\/iOS","version":"0.1"},"Crypto":{"ciphertext":"e4f2a36dff6f4fd9f9dece3ac737430a1ea18739f6fb07b832f0240060565336","cipherparams":{"iv":"cfbd07ae661273cd4d99d880c11f8f50"},"kdf":"scrypt","kdfparams":{"r":8,"p":1,"n":262144,"dklen":32,"salt":"e763ec5a8b9dc71adf7d1d7198e31ad7c27a09c4f64445d6981eb9c8f0e95f74"},"mac":"abf64e2d2e754e04d0b6b4ac4bff893ade55e41878a91db63c757cd6ca6673ca","cipher":"aes-128-ctr"},"address":"c8b48dc25b2009540265498584146f4f0aaf1707"}
【钱包地址address】0x374209f6169624744a87b2dd9c568899c881cfcd
【官方keyStore】{"version":3,"id":"6BA52440-0C4B-43E4-AAE0-1AC0E2455396","crypto":{"ciphertext":"12d0171ffee055566deb57171cca197ae0546ff149ed54408f7645fd7b0c4fbc","cipherparams":{"iv":"cfe1296760ed55e1dd50c9d775c0244e"},"kdf":"scrypt","kdfparams":{"r":8,"p":1,"n":262144,"dklen":32,"salt":"fc63aed7d66a0714902f3edb79dd953940df6caca9ced2ab6b4c794d79d97c7a"},"mac":"f2d4fcfb6888ea1d03009cf863f15e5bf9b16c84eef9e9c8770001fc86c07127","cipher":"aes-128-ctr"},"address":"374209f6169624744a87b2dd9c568899c881cfcd"}
【助记词mnemonicPhrase】visa answer drive current clean crush dry raven sudden deputy west glass
【私钥privateKey】0xcdbec762ea9e18c0f163fe3f95ab2c5aa6fbd630fa48f6a51f2b309ff10d8ba6
2018-08-31 09:38:42.922175+0800 Wallet-Client[17097:725373] Success:数据插入表 KeyStoreTB 成功: {
"chain_id" = ETH;
keystore = "{\"ETH\":\"{\\\"version\\\":3,\\\"id\\\":\\\"6BA52440-0C4B-43E4-AAE0-1AC0E2455396\\\",\\\"crypto\\\":{\\\"ciphertext\\\":\\\"12d0171ffee055566deb57171cca197ae0546ff149ed54408f7645fd7b0c4fbc\\\",\\\"cipherparams\\\":{\\\"iv\\\":\\\"cfe1296760ed55e1dd50c9d775c0244e\\\"},\\\"kdf\\\":\\\"scrypt\\\",\\\"kdfparams\\\":{\\\"r\\\":8,\\\"p\\\":1,\\\"n\\\":262144,\\\"dklen\\\":32,\\\"salt\\\":\\\"fc63aed7d66a0714902f3edb79dd953940df6caca9ced2ab6b4c794d79d97c7a\\\"},\\\"mac\\\":\\\"f2d4fcfb6888ea1d03009cf863f15e5bf9b16c84eef9e9c8770001fc86c07127\\\",\\\"cipher\\\":\\\"aes-128-ctr\\\"},\\\"address\\\":\\\"374209f6169624744a87b2dd9c568899c881cfcd\\\"}\",\"name\":\"Hello\"}";
"wallet_id" = 261;
}
insufficient funds for gas * price + value
成功 哈希值 = 0x323d0417e344e4efa502844f9e6e87606259fd5ef10d25e571cd5efa26993435
ropsten哈希值 = 0x11621d0f42ba058f5fb6271ba9afe516725c713b73fda929dd47f15b1827dfc3
比特币对消息进行签名:实际上是对消息的哈希进行签名。
对消息进行签名,实际上是对消息的哈希进行签名,这样可以使任意长度的消息在签名前先转换为固定长度的哈希数据。对哈希进行签名相当于保证了原始消息的不可伪造性。
我们来看看使用ECDSA如何通过私钥对消息进行签名。关键代码是通过sign()方法签名,并获取一个ECSignature对象表示签名:
const bitcoin = require('bitcoinjs-lib');
let
message = 'a secret message!',
hash = bitcoin.crypto.sha256(message),
wif = 'L16zYACWmkQXMUhYAzLA1dWEg95yv8VokSVFcadgpMFeTQoDzMPz',
keyPair = bitcoin.ECPair.fromWIF(wif);
// 用私钥签名:
let signature = keyPair.sign(hash).toDER(); // ECSignature对象
// 打印签名:
console.log(signature.toString('hex'));
// 打印公钥以便验证签名:
console.log(keyPair.getPublicKeyBuffer().toString('hex'));
Run
3045022100d3fcd05aed64d7f7684dd921b11f3b4a10fb07c1418e458a43aaef64bf19d48802205a3bbe9a69b83a00c855b4a882b71c7af9c9ef03e1f62ff7f683378d42b2a994
0319e2c444740d73d54387d711306356bbc89c7a31b87749bedc6f8f07d5810004
第一步:对消息字符串进行哈希,得到固定长度的哈希值,
第二步:对WIF进行处理,生成KeyPair,
第三步:使用KeyPair对哈希数据进行签名,生成ECSignature,
第四步:使用KeyPair生成公钥,以便验证签名。
ECSignature对象可序列化为十六进制表示的字符串。
在获得签名、原始消息和公钥的基础上,可以对签名进行验证。验证签名需要先构造一个不含私钥的ECPair,然后调用verify()方法验证签名:
const bitcoin = require('bitcoinjs-lib');
let signAsStr = '3045022100d3fcd05aed64d7f7684dd9'
+ '21b11f3b4a10fb07c1418e458a43aaef'
+ '64bf19d48802205a3bbe9a69b83a00c8'
+ '55b4a882b71c7af9c9ef03e1f62ff7f6'
+ '83378d42b2a994';
let
signAsBuffer = Buffer.from(signAsStr, 'hex'),
signature = bitcoin.ECSignature.fromDER(signAsBuffer), // ECSignature对象
message = 'a secret message!',
hash = bitcoin.crypto.sha256(message),
pubKeyAsStr = '0319e2c444740d73d54387d711306356bbc89c7a31b87749bedc6f8f07d5810004',
pubKeyAsBuffer = Buffer.from(pubKeyAsStr, 'hex'),
pubKeyOnly = bitcoin.ECPair.fromPublicKeyBuffer(pubKeyAsBuffer); // 从public key构造ECPair
// 验证签名:
let result = pubKeyOnly.verify(hash, signature);
console.log('Verify result: ' + result);
Run
Verify result: true
注意上述代码只引入了公钥,并没有引入私钥。
修改signAsStr、message和pubKeyAsStr的任意一个变量的任意一个字节,再尝试验证签名,看看是否通过。
比特币对交易数据进行签名和对消息进行签名的原理是一样的,只是格式更加复杂。对交易签名确保了只有持有私钥的人才能够花费对应地址的资金。
小结
通过私钥可以对消息进行签名,签名可以保证消息防伪造,防篡改,防抵赖。