掘金 后端 ( ) • 2024-03-22 19:10

比特币账户是如何生成的呢?这里详细介绍了比特币从私钥,公钥,到地址的详细步骤,以及公钥到私钥的算法逻辑, 想不想亲自生成一个比特币账号?跟着下面一步步做,你也可以哦。

但需要注意的是,比特币使用的 是 椭圆曲线secp256k1 的(ECDSA算法)用来生成密钥对,以及交易签名和交易验证,但是我这里使用了椭圆曲线ed25519来生成密钥对,是另一种签名算法,但是 ed25519相对于ECDSA有多个优点,它的运算速度通常比ECDSA快,因为本人喜欢比较吊的,所以替换为了ed25519椭圆曲线用来生成密钥对。

如果想真正生成可以使用的比特币账户,还是需要使用secp256k1算法生成密钥对,对于下面的代码只需更改密钥对生成的算法库即可。

添加依赖

以下代码用到了这些库

ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
rand = "0.8.5"
bs58 = "0.5.0"
ripemd = "0.1.3" 
sha256 = { version = "1.5.0" }

生成密钥对

首先,我们需要生成一对密钥:一个公钥和一个私钥。在比特币系统中,私钥用于签署交易,证明资金的所有权,而公钥用于接收比特币。这里使用了ed25519来生成密钥对。

pub struct Keypair(ed25519_dalek::SigningKey);

impl Keypair {
    pub fn new() -> Self {
        Self(ed25519_dalek::SigningKey::generate(&mut OsRng))
    }
}

在上面的代码中,Keypair::new() 函数使用 ed25519_dalek::SigningKey 来生成一个新的密钥对。OsRng 是一个操作系统随机数生成器,用于确保生成的密钥具有高度的随机性和安全性。

公钥和私钥

一旦我们有了密钥对,就可以轻松获取公钥和私钥:

pub fn public_key(&self) -> PublicKey {
    self.0.verifying_key().to_bytes()
}

pub fn private_key(&self) -> PrivateKey {
    self.0.to_bytes()
}

这里的公钥和私钥都以字节数组的形式返回,这是加密操作中常见的数据格式。

生成地址

比特币地址是公钥的哈希版本,通过一系列的加密操作生成。这个地址可以公开分享,让其他用户向你发送比特币。

pub fn address(&self) -> String {
    let mut hash_160 = Self::pub_key_hash(self).to_vec();
    hash_160.insert(0, VERSION);
    let rt = [hash_160.clone(), checksum(hash_160.as_slice())].concat();
    base58_encode(rt.as_slice())
}

生成地址的步骤包括:

步骤1:计算公钥的SHA-256哈希,然后使用RIPEMD-160哈希

在比特币地址生成过程中,首先需要对公钥进行SHA-256哈希运算,然后对结果应用RIPEMD-160哈希运算。这样做可以缩短哈希长度,同时保持足够的安全性。

在您的代码中,这一步是通过pub_key_hash函数实现的:

pub fn pub_key_hash(&self) -> Ripemd160Hash {
    let hash = self.public_key().digest();
    ripemd160_digest(hash.as_bytes())
}

这个函数首先使用digest()方法对公钥进行SHA-256哈希运算,然后调用ripemd160_digest函数对结果进行RIPEMD-160哈希运算。

pub fn ripemd160_digest(data: &[u8]) -> Ripemd160Hash {
    let mut ripemd160 = Ripemd160::new();
    ripemd160.update(data); 
    let ret = ripemd160.finalize();
    ret[..].try_into().unwrap()
}

步骤2:添加版本字节

比特币地址中的版本字节(通常为0x00)用于标识地址类型。在您的代码中,这一步是在address函数中完成的:

let mut hash_160 = Self::pub_key_hash(self).to_vec();
hash_160.insert(0, VERSION);

这里,VERSION是一个常量,代表比特币主网络的地址类型,其值通常设为0x00。通过在哈希值前添加这个版本字节,我们能够区分不同类型的比特币地址。

步骤3:计算校验和

计算校验和是为了防止地址在传输过程中的误输入。通过对地址哈希加上版本字节的结果再进行两次SHA-256哈希运算,并取前四个字节作为校验和。

在您的代码中,校验和的计算是这样实现的:

fn checksum(payload: &[u8]) -> Vec<u8> {
    let first_sha = payload.digest();
    let second_sha = first_sha.digest();
    second_sha[0..ADDRESS_CHECK_SUM_LEN].to_vec()
}

步骤4:拼接并进行Base58编码

最后一步是将版本字节、地址哈希和校验和拼接在一起,然后使用Base58编码转换为一串字符,形成最终的比特币地址。

这个步骤发生在address函数的最后:

let rt = [hash_160.clone(), checksum(hash_160.as_slice())].concat();
base58_encode(rt.as_slice())

这里,base58_encode函数将拼接后的结果转换为Base58编码的字符串,这是比特币地址的标准格式,易于人类阅读且避免了容易混淆的字符。

完整账户信息

我们定义了一个Account结构体来存储完整的账户信息,包括私钥、公钥和地址。

pub struct Account {
    private_key: PrivateKey,
    pubkey: PublicKey,
    address: Address,
}

通过地址验证公钥哈希

通常来说我们在验证交易的时候,只知道一个地址,比特币因为其签名算法,可以从地址获得他的公钥哈希,所以竟是通过这个原理来验证签名交易,但是这里暂时不做对交易签名和验证的讲解。首先从address使用base58_decode获得原始地址,再通过去除开头的版本信息和结尾的校验和,取中间的呢公钥哈希进行对比

pub fn address_verify(addr: &Address, pubkeyhash: Ripemd160Hash) -> bool {
    let payload = base58_decode(addr);
    let pub_key_hash: Ripemd160Hash = payload[1..payload.len() - ADDRESS_CHECK_SUM_LEN]
        .try_into()
        .unwrap();
    pubkeyhash.eq(&pub_key_hash)
}

创建新账户

通过调用Account::new(),我们可以轻松创建一个新的比特币账户。这个函数内部调用Keypair::new()来生成密钥对,然后使用这些密钥来创建地址。

    let account = Account::new();
    let private_key = account.private_key();
    let public_key = account.public_key();

测试和验证

最后,我们通过实现一个简单的测试来验证账户生成的正确性。这个测试检查地址验证函数address_verify,确保生成的地址与其公钥哈希匹配。

#[test]
fn test() {
   let account = Account::new();
    let private_key = account.private_key();
    let public_key = account.public_key();
    let pub_key_hash = account.pub_key_hash();
    let address = account.address();
    let ret = address_verify(&address, pub_key_hash); 
    dbg!(ret); // 应为true
}

注意

通过以上步骤,就可以生成一个比特币账户,该账户可以用来接收比特币,并且会产生与比特币网络一致的私钥长度,和公钥哈希,但是因为算法不同,无法用来验证和交易。所以千万不要拿上面的代码直接写钱包应用哈,要不钱找不回来了,除非你自己改成secp256k1,也不复杂,直接换底层密码库就行了,其他没什么变化。 至于原因:是因为 比特币协比特币网络及其节点软件是基于secp256k1椭圆曲线加密算法设计的。这意味着网络上的交易验证、签名过程、以及公私钥的处理都是依据secp256k1算法的规则来实现的。如果你使用ed25519算法生成的密钥对,这些密钥对将无法在比特币网络中正常工作,因为:

  • 签名不兼容:比特币网络的节点无法识别或验证使用ed25519算法生成的签名。交易在传播到网络时会因为签名验证失败而被拒绝。
  • 地址格式问题:尽管可以从ed25519公钥生成符合比特币地址格式的地址(通过相应的哈希函数和编码过程),但这个地址背后的密钥和签名机制与网络预期的不同,会导致实际的交易验证失败。

上面代码是用rust实现比特币网络项目的账户部分,完整可以参考源码(虽然还没写完,还改了椭圆曲线为ed25519):https://github.com/ChainThemAll/bitcoin_example_rust

所以上面代码只作为学习用。如果想实际使用需要替换生成 secp256k1 密钥对的密钥算法。from Pomelo_刘金,转载请注明原文链接。感谢!