私鑰、公鑰、地址在目前看來就是一串(幾乎)不可能碰撞的字串。
可以用四個等式來說明
-
- = random() (0 < k < n; n = 1.158 * 10^77)
-
- = k * G (G 為橢圓曲線密碼學的一個常數)
-
- = Hash.ripemd160(Hash.sha256(K))
-
- = base58(0 + a + checksum)
從 k(私鑰) 到 K(公鑰) 到 A(地址) 的過程,由密碼學保證其不可逆。
程式碼如下:
const crypto = require(`crypto`);
var EC = require(`elliptic`).ec;
var ec = new EC(`secp256k1`);
var BN = require(`bn.js`);
var bs58 = require(`bs58`);
class PrivateKey {
constructor() {
this.bn = this.generateKey();
this.compressed = true;
this.network = Networks.defaultNetwork;
}
generateKey() {
let condition;
let bn;
do {
// 隨機生成 1 ~ 2^256 之間的數字,並以 hex 這種編碼格式顯示。
// hex :一種編碼格式,將每個位元組編碼為兩個十六進位制字元
// privateHex: "ceea0ada327fc521e9c5ba704a002f56c95de6bffc83901aa2290fc882c4c218"
const privateHex = crypto.randomBytes(32).toString(`hex`);
// privateHex 是字串型別,字串格式是沒法直接比較大小的,所以要轉化為數字型別。
// 但是 js 中最大的安全數是 Number.MAX_SAFE_INTEGER = 9007199254740991,根本不夠表示一個 private 值。
// 所以用到了 BN 這個庫,對比 private。BN 即大數 Big Number。
bn = new BN(privateHex, 16)
// max = <BN: fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141>
const max = new BN(ec.curve.n.toArray())
// 實際上 private 要比 max 小
// max 是一個常數 n=1.158 * 10^77,略小於2^256
// 由比特幣所使用的橢圓曲線的階
// 當 bn < max 成功生成私鑰
condition = bn.lt(max)
} while (!condition);
return bn;
}
}
class PublicKey {
constructor(privateKey){
// 橢圓曲線乘法可以從私鑰計算得到公鑰
// 是不可逆轉的過程:K = k * G
// 其中k是私鑰,G是被稱為生成點的常數點,而K是所得公鑰。
this.point = ec.curve.g.mul(privateKey.bn)
this.compressed = privateKey.compressed
this.network = privateKey.network
}
// 這一塊沒有找到對應文件
toBuffer () {
var xbuf = this.point.getX().toBuffer({ size: 32 });
var ybuf = this.point.getY().toBuffer({ size: 32 });
var prefix;
if (!this.compressed) {
prefix = new Buffer([0x04]);
return Buffer.concat([prefix, xbuf, ybuf]);
} else {
var odd = ybuf[ybuf.length - 1] % 2;
if (odd) {
prefix = new Buffer([0x03]);
} else {
prefix = new Buffer([0x02]);
}
return Buffer.concat([prefix, xbuf]);
}
};
}
class Address {
constructor(publicKey){
// publish key to bitcoin address(內部地址)
this.hashBuffer = Hash.ripemd160(Hash.sha256(publicKey.toBuffer()))
this.network = publicKey.network
this.type = Address.PayToPublicKeyHash
}
// 生成使用者見到的比特幣地址
// Base58Check Encoding
toString () {
// 比特幣地址的字首是0(十六進位制是0x00)
const version = new Buffer([0])
const payload = this.hashBuffer
// 1. add version prefix
const addVersionPrefix = Buffer.concat([version, payload])
// 2. hash(version prefix + payload)
const checksum = Hash.sha256(Hash.sha256(addVersionPrefix)).slice(0, 4)
// 3. add first 4 bytes as checksum
const addChecksum = Buffer.concat([addVersionPrefix, checksum])
// 4. encode in base-58
return bs58.encode(addChecksum);
}
}
Address.PayToPublicKeyHash = `pubkeyhash`;
Address.PayToScriptHash = `scripthash`;
class Networks {}
Networks.defaultNetwork = `livenet`;
class Hash {}
Hash.sha256 = function(buf) {
return crypto.createHash(`sha256`).update(buf).digest();
};
Hash.ripemd160 = function(buf) {
return crypto.createHash(`ripemd160`).update(buf).digest();
};
const privateKey = new PrivateKey()
console.log(privateKey)
const publicKey = new PublicKey(privateKey)
console.log(publicKey)
const address = new Address(publicKey)
console.log(address)