返回介绍

3.秘钥的生成

发布于 2024-12-29 22:37:17 字数 12041 浏览 0 评论 0 收藏 0

a. 像过去的美好日子

首先,为什么要生成好几个秘钥呢?

主要原因是为了私密性。既然你可以看到所有地址的余额,最好还是在每次交易时使用新地址。

然而,实际上,你可以为每个合约生成秘钥。因为这是识别你的付款人的一种简单的方法,而且不会泄露太多的隐私。

你可以生成秘钥,就好像刚开始的时候一样:

var key = new Key();

然而,那样有两个问题:

  • 当你生成一个新的秘钥后,你钱包的所有备份就都过期了。
  • 你不能把地址产生的过程委托给一个不可信的人。

如果你正在开发网络钱包,为你的用户生成秘钥,那么当一个用户被黑时,他立马就会怀疑你。

a.BIP38(part 2)

我们已经看到 BIP38 在秘钥加密方面的内容了,然而这个 BIP 实际上是一篇文章里的两个思想。

这个 BIP 的第二部分告诉你为何可以将秘钥和地址的生成委托给不可信第三方。这就解决了前面的担忧。

这个方法是为秘钥生成器生成一个 PassphraseCode。通过 PassphraseCode,他就可以为你生成加密后的秘钥,并且不需要知道你的密码和私钥。

PassphraseCode 可以按 WIF 格式提供给你的秘钥生成器。

Tip:在 NBitcoin,所有以 Bitcoin 前缀开头的就是 Base58(WIF)格式数据

所以,作为一个想要委托生成秘钥的用户,首先你需要生成 PassphraseCode 。

BitcoinPassphraseCode passphraseCode = new BitcoinPassphraseCode("my secret",
Network.Main, null);

然后你可以将 PassphraseCode 提供给第三方秘钥生成器。

秘钥生成器将为你生成新的加密密钥。

EncryptedKeyResult encryptedKey1 = passphraseCode.GenerateEncryptedSecret();

EncryptedKeyResult 信息量丰富:

首先:生成的比特币地址,然后是 EncryptedKey ,就像我们在秘钥加密部分看到的那样,最后仍然很重要的是 ConfirmationCode, 这样第三方机构就可以证明生成的秘钥和地址与你的密码是相对应的。

EncryptedKeyResult encryptedKey1 = passphraseCode.GenerateEncryptedSecret();
Console.WriteLine(encryptedKey1.GeneratedAddress);
Console.WriteLine(encryptedKey1.EncryptedKey);
Console.WriteLine(encryptedKey1.ConfirmationCode);

作为所有者,你一旦收到这些信息,就需要检查一下,确保秘钥生成器没有使用 ConfirmationCode.Check 来骗人,然后你就根据密码取得了私钥。

Console.WriteLine(confirmationCode.Check("my secret", generatedAddress));
BitcoinSecret privateKey = encryptedKey.GetSecret("my secret");
Console.WriteLine(privateKey.GetAddress() == generatedAddress);
Console.WriteLine(privateKey);

所以,我们看到了,第三方机构可以为你生成加密秘钥,无需知道你的秘钥和私钥。

然而,仍然存在一个问题:

  • 当你生成一个秘钥后,你钱包的所有备份都过期失效了。BIP32,或者叫分层确定钱包(HD 钱包)推荐了另一种解决方案,得到了更广泛的支持。

b. HD 钱包(BIP 32)

谨记我们需要解决的问题:

  • 避免备份过期失效
  • 委托不可信第三方生成秘钥/地址

    一个 确定的 钱包可以解决我们备份的问题。 通过这样的钱包,你只需要保存种子。从这个种子,就可以多次生成同样系列的私钥。

这就是 确定的 含义。 你可以看到,从主秘钥出发,我可以生成新的秘钥:

ExtKey masterKey = new ExtKey();
Console.WriteLine("Master key : " + masterKey.ToString(Network.Main));
for (int i = 0 ; i < 5 ; i++)
{
    ExtKey key = masterKey.Derive((uint)i);
    Console.WriteLine("Key " + i + " : " + key.ToString(Network.Main));
}
Master key :
xprv9s21ZrQH143K3JneCAiVkz46BsJ4jUdH8C16DccAgMVfy2yY5L8A4XqTvZqCiKXhNWFZXdLH6VbsCs
qBFsSXahfnLajiB6ir46RxgdkNsFk
Key 0 :
xprv9tvBA4Kt8UTuEW9Fiuy1PXPWWGch1cyzd1HSAz6oQ1gcirnBrDxLt8qsis6vpNwmSVtLZXWgHbqff9
rVeAErb2swwzky82462r6bWZAW6Ty
Key 1 :
xprv9tvBA4Kt8UTuHyzrhkRWh9xTavFtYoWhZTopNHGJSe3KomssRrQ9MTAhVWKFp4d7D8CgmT7TRza
uoAZXp3xwHQfxr7FpXfJKpPDUtiLdmcF
Key 2 :
xprv9tvBA4Kt8UTuLoEZPpW9fBEzC3gfTdj6QzMp8DzMbAeXgDHhSMmdnxSFHCQXycFu8FcqTJRm2ka
mjeE8CCKzbiXyoKWZ9ihiF7J5JicgaLU
Key 3 :
xprv9tvBA4Kt8UTuPwJQyxuZoFj9hcEMCoz7DAWLkz9tRMwnBDiZghWePdD7etfi9RpWEWQjKCM8wH
vKQwQ4uiGk8XhdKybzB8n2RVuruQ97Vna
Key 4 :
xprv9tvBA4Kt8UTuQoh1dQeJTXsmmTFwCqi4RXWdjBp114rJjNtPBHjxAckQp3yeEFw7Gf4gpnbwQTgDp
GtQgcN59E71D2V97RRDtxeJ4rVkw4E
Key 5 :
xprv9tvBA4Kt8UTuTdiEhN8iVDr5rfAPSVsCKpDia4GtEsb87eHr8yRVveRhkeLEMvo3XWL3GjzZvncfWVK
nKLWUMNqSgdxoNm7zDzzD63dxGsm

你仅仅需要保存好 masterKey,因为由此可以多次生成同样的私钥套装。

你会发现,这些秘钥都是 ExtKey,不是你熟悉的秘钥。然而,先别着急,因为在里面有真实的私钥:

在 base58 格式上与 ExtKey 等价的就叫 BitcoinExtKey。

但是怎样才能解决我们的第二个难题呢:委托地址生成工作给第三方,并可能被黑(就像是支付服务器)?

技巧就是你可以让主秘钥偏中性一点,这样你就有主秘钥的公开版本(不含私钥)。从中性版本的角度,第三方可以生成公钥而不需要知道私钥。

ExtPubKey masterPubKey = masterKey.Neuter();
for (int i = 0 ; i < 5 ; i++)
{
    ExtPubKey pubkey = masterPubKey.Derive((uint)i);
    Console.WriteLine("PubKey " + i + " : " + pubkey.ToString(Network.Main));
}

想象一下,你的支付服务器生成了 pubkey1,你可以通过主私钥得到对应的私钥。

masterKey = new ExtKey();
masterPubKey = masterKey.Neuter();

//The payment server generate pubkey1
ExtPubKey pubkey1 = masterPubKey.Derive((uint)1);

//You get the private key of pubkey1
ExtKey key1 = masterKey.Derive((uint)1);

//Check it is legit
Console.WriteLine("Generated address : " +
pubkey1.PubKey.GetAddress(Network.Main));
Console.WriteLine("Expected address : " +
key1.PrivateKey.PubKey.GetAddress(Network.Main));

ExtPubKey 与 ExtKey 相似,除了它包含的是 PubKey 而非 Key 之外。

现在我们就知道了,确定秘钥解决问题的过程。让我们看看分层是什么意思。

在前面的练习中,我们已经知道,通过联合 master key + index 我们可以生成另一个秘钥。我们称这个过程为派生,主秘钥是 parent key,生成的秘钥叫 child key。

然后,你也可以从子秘钥中派生子秘钥,这就是分层的含义。

这就是为什么从概念上来讲,你可以更一般地说:Parent Key + KeyPath => Child Key

在上图中,你可以通过两种途径从父秘钥那里派生 Child(1,1):

ExtKey parent = new ExtKey();
ExtKey child11 = parent.Derive(1).Derive(1);

或者

ExtKey parent = new ExtKey();
ExtKey child11 = parent.Derive(new KeyPath("1/1"));

因此,总的来说:

ExtPubKey 也是一样的。

为什么你需要分层秘钥?因为在多账户时可以更好地对秘钥进行分类。更多内容参考 BIP44。

它也允许在组织内部分配账户权力。

假设你是一个公司的 CEO,想要掌控所有的钱包,但是你不想会计部门花市场部门的钱。

所以你的首选方案就是为每个部门生成一个层次结构。

然而,在这个案例中,Accounting 和 Marketing 可以恢复 CEO 的私钥。

我们把这些子秘钥定义为非固定的(non-hardened)。

ExtKey ceoKey = new ExtKey();
Console.WriteLine("CEO: " + ceoKey.ToString(Network.Main));
ExtKey accountingKey = ceoKey.Derive(0, hardened: false);

ExtPubKey ceoPubkey = ceoKey.Neuter();

//Recover ceo key with accounting private key and ceo public key
ExtKey ceoKeyRecovered = accountingKey.GetParentExtKey(ceoPubkey);
Console.WriteLine("CEO recovered: " + ceoKeyRecovered.ToString(Network.Main));

换句话来说,非固定的秘钥(non-hardened key)可以在分层结构中爬跃层次。

非固定的秘钥(non-hardened key)应仅仅被用于对 single control 所拥有的账户进行分类。

在我们的案例中,CEO 应该创建一个固定的秘钥(hardened key),这样会计部门就不能超越层次等级了。

ExtKey ceoKey = new ExtKey();
Console.WriteLine("CEO: " + ceoKey.ToString(Network.Main));
ExtKey accountingKey = ceoKey.Derive(0, hardened: true);

ExtPubKey ceoPubkey = ceoKey.Neuter();

ExtKey ceoKeyRecovered = accountingKey.GetParentExtKey(ceoPubkey); //Crash

你也可以通过 ExtKey.Derivate(KeyPath) 创建 hardened key,在子秘钥索引后使用撇号即可:

var nonHardened = new KeyPath("1/2/3");
var hardened = new KeyPath("1/2/3'");

我们可以设想一下,会计部门为每个客户生成一个父秘钥,为每笔客户支付服务生成一个子秘钥。

作为 CEO,你想要向其中一个地址上支付款项,下面就是处理过程:

ceoKey = new ExtKey();
string accounting = "1'";
int customerId = 5;
int paymentId = 50;
KeyPath path = new KeyPath(accounting + "/" + customerId + "/" + paymentId);
//Path : "1'/5/50"
ExtKey paymentKey = ceoKey.Derive(path);

c.HD 秘钥助记代码(BIP39)

生成 HD 秘钥是非常容易的。但是,如果我们想通过电话或者手写传输这些秘钥,怎么样才能一样方便呢?

类似 Trezor 这样的冷钱包,可以从一个容易写下来的句子生成 HD 秘钥。他们把这样的句子称为 种子 或者 助记符 。它也能用密码或者 PIN 保护起来。

写句子的语言就叫做词汇表(Wordlist)。

Mnemonic mnemo = new Mnemonic(Wordlist.English, WordCount.Twelve);
ExtKey hdRoot = mnemo.DeriveExtKey("my password");
Console.WriteLine(mnemo);

现在,如果你有助记符和密码,就可以还原 hdRoot 秘钥。

mnemo = new Mnemonic("minute put grant neglect anxiety case globe win famous
correct turn link",
Wordlist.English);
hdRoot = mnemo.DeriveExtKey("my password");

目前支持的词汇表包括:英语、日语、西班牙语、中文(简体和繁体)。

d.黑暗钱包

名字有点不吉利,但是它实际上没有什么黑暗的东西,它引来了没有必要的关注和麻烦。

黑暗钱包实际上解决了我们最初的两个问题:

  • 避免备份过期失效
  • 委托秘钥/地址生成给不可信第三方

但它有一个可以获大奖的特点。

你仅需要分享一个地址(叫 StealthAddress)就可以,不需要泄露其它任何私密信息。

提醒一下,如果你跟其他人分享了 BitcoinAdress,那么他们都可以通过区块链查询你的余额。这是跟 StealthAddress 不一样的。

被叫做黑暗的真是很遗憾,因为它部分解决了由于比特币伪匿名而引起的隐私泄露问题。更好的名字应该是:One Address。

在黑暗钱包的术语里面,下面是不同的角色:

  • Scanner 知道 Scan Key,有了它就可以觉察接收者的交易。
  • 接收者知道 Spend Key,有了它就可以把他从交易中接收到的币花出去。
  • 付款人知道接收者的 StealthAddress。

剩下的就是操作层面的细节了。

在底层,StealthAddress 包含一个或者几个 Spend PubKey,以及一个 Scan PubKey。

var scanKey = new Key();
var spendKey = new Key();
BitcoinStealthAddress stealthAddress
= new BitcoinStealthAddress
(
    scanKey: scanKey.PubKey,
    pubKeys: new[] { spendKey.PubKey },
    signatureCount: 1,
    bitfield: null,
    network: Network.Main);

付款方将用你的 StealthAddress 生成一个临时秘钥,叫 Ephem Key,然后生成一个 Stealth 公钥,

它是比特币地址的源头,也是支付目的地。

然后,它将 Ephem PubKey 打包进一个 Stealth Metadata 对象,嵌入到交易的 OP_RETURN 中(就好像我们在第一个任务里面做的一样),它也会将输出添加到生成的比特币地址中。(Stealth pub key 的地址)

var ephemKey = new Key();
Transaction transaction = new Transaction();
stealthAddress.SendTo(transaction, Money.Coins(1.0m), ephemKey);
Console.WriteLine(transaction);

EphemKey 生成过程的实现细节你可以忽略,NBitcoin 将自动生成。

Transaction transaction = new Transaction();
stealthAddress.SendTo(transaction, Money.Coins(1.0m));
Console.WriteLine(transaction);
{
    ….
    "in": [],
    "out": [
    {
        "value": "0.00000000",
        "scriptPubKey": "OP_RETURN
        06000000000211e76c3de929f7d8b3473f6e41fc31016d7a3e56a47e9f541b0d1cc7fa3f8819"
    },
    {
        "value": "1.00000000",
        "scriptPubKey": "OP_DUP OP_HASH160 b4638a6b452bbfc6fdbb4e1e8c9f1952bbd18c39
        OP_EQUALVERIFY OP_CHECKSIG"
    }
    ]
}

然后付款人添加输入并签名,最后把交易发送到网络上。

Scanner 知道 StealthAddress,Scan Key 可以恢复 Stealth PubKey,所以它也期待着向 BitcoinAddress 的付款。

然后 scanner 检查交易的输出是否与这些地址向对应,如果是,Scanner 向 Receiver 通知这个交易。

Receiver 可以通过它的 Spend Key 得到地址的私钥。

代码说明了 Scanner 对交易的巡查工作,至于接收者解密私钥的工作,我们将在 TransactionBuilder 部分作解释。

应当注意的是,一个 StealthAddress 可以有多个 spend pubkeys,那种情况下,地址表示一个多重签名。

黑暗钱包的一个限制就是 OP_RETURN 的使用,我们无法轻易将任意数据嵌入到交易中,这个与传送比特币时不一样。(目前比特币规则仅允许每个交易 40 字节的 OP_RETURN,很快将允许 80 字节)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文