<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>0xfanjf</title>
        <link>https://paragraph.com/@0xfanjf</link>
        <description>undefined</description>
        <lastBuildDate>Sat, 22 Aug 2026 05:06:32 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[算法稳定币]]></title>
            <link>https://paragraph.com/@0xfanjf/AzOKJNscitZ3pYTnjFBP</link>
            <guid>AzOKJNscitZ3pYTnjFBP</guid>
            <pubDate>Fri, 05 Aug 2022 08:50:07 GMT</pubDate>
            <description><![CDATA[在币圈，由于无监管，各种币满天飞，一天暴跌几倍不是新鲜事。 因此，在链上计价的交易时间，也得在链上，而且价格要稳定，而且要以计价方式来稳定。 稳定币就是稳定币，比如稳定币的稳定币，固定美元。 第一种机制是法币制1:1刚兑付发行的类似私人钱庄。例如存入美元到方的银行账簿，发行方就发出等额入汇给汇。 做这个生意是这个家的私人公司，他们稳定地发行了美元T（美元T），因此，1T 1111 美元的价格该公司兑在某个小地方。财务极端不透明，无审计，能否100%兑付已发行的USDT，大家心里是没谱的。 除了USDT外，USDC和GUSD等稳定币是由受监管的美国公司发行，审计相对透明。 用非常和法币挂钩的稳定机制，它很容易上架就是1:1刚兑的借据。只要本币发行稳定币的一种，因为100%稳定币，因为100%的资产。 有几种稳定的发行机制是经常发行，没有由MakerDAO制造。MakerDAO自己创造任何资产，它的合约在链上创建了几个正常，来实现稳定币发行。 首先，MakerDAO 也选用了自己的几个高价值币种，比如这些流动性非常大的币种，然后这些 ETH 等的资产可以作为普通用户，用户的资产正常到...]]></description>
            <content:encoded><![CDATA[<p>在币圈，由于无监管，各种币满天飞，一天暴跌几倍不是新鲜事。</p><p>因此，在链上计价的交易时间，也得在链上，而且价格要稳定，而且要以计价方式来稳定。</p><p>稳定币就是稳定币，比如稳定币的稳定币，固定美元。</p><p>第一种机制是法币制1:1刚兑付发行的类似私人钱庄。例如存入美元到方的银行账簿，发行方就发出等额入汇给汇。</p><p>做这个生意是这个家的私人公司，他们稳定地发行了美元T（美元T），因此，1T 1111 美元的价格该公司兑在某个小地方。财务极端不透明，无审计，能否100%兑付已发行的USDT，大家心里是没谱的。</p><p>除了USDT外，USDC和GUSD等稳定币是由受监管的美国公司发行，审计相对透明。</p><p>用非常和法币挂钩的稳定机制，它很容易上架就是1:1刚兑的借据。只要本币发行稳定币的一种，因为100%稳定币，因为100%的资产。</p><p>有几种稳定的发行机制是经常发行，没有由<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://makerdao.com/">MakerDAO</a>制造。MakerDAO自己创造任何资产，它的合约在链上创建了几个正常，来实现稳定币发行。</p><p>首先，MakerDAO 也选用了自己的几个高价值币种，比如这些流动性非常大的币种，然后这些 ETH 等的资产可以作为普通用户，用户的资产正常到那里，并获得一个普通的合约。，根据市场当前，50%的价格，用户用美元发行的价格稳定下来，比如DAI。当DAI发行后，美国无法被用户按，可以按DAI的价格上涨。众筹资金，快速筹集资金时，抵触触发机制，MakerDA 的约定不会直接物，阻止 DAI 债债的发行。</p><p>因此，DAI是一种以链上资产为稳定币的稳定币，只要以超币的价格为稳定币的资产价格不会致命坠落平仓线，就能以1:1的价格稳定锚定美元。</p><p>一种稳定币，即Algor Stablecoin，它不依赖于任何资产的使用范围都可以用1:1的方式来算币，但是每个人都可以用货币来计算稳定币。懂得的，完全没有监管，任何一种算法稳定的币种发行上不就是部署显着契约，都可以部署自己的稳定币，因此，各种算法稳定层，各有各的神通。</p><p>本文重点讲述几类有代表性的算法稳定。</p><h3 id="h-ampl" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AMPL</h3><p>Amplefor 发行发行是稳定的算法，它的价格是 AMPL 的美元，即 1 美元（如果用户增加余额，根据高出的，所有账户的 AMP，“每个”5 美元。例如，1 AMPL 的用户，将AMPL的价格降到1美元就是5美元的价格。AMP表价格比如余额为1美元。），每个人的AMPL压力跌到0.5美元，每个人的AMPL都了，“通缩”的，也把AMPL的价格拉回1美元。</p><p>AMPL的算法基本稳定，是AMPL最大的一个不稳定的问题，用户今天10个不稳定的问题，今天11个，明天120天，不稳定，复上不始终。</p><h3 id="h-bac" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">BAC</h3><p>基差首创了3种代币稳定的稳定币，发行了基差希望现金（BAC3、Basis Share（BAS和Basis Bond）3种代币发行，因为）只有BAC。它的机制：</p><p>当 BAC 的价格上涨 1 美元时，就发 BAC，将 BAC 分给获得的人，让他们增加 BA 的价格，凭空的 BAC，以低 BAC。</p><p>当BAC的比价为1美元时，用户持有BAC的价格为0.5美元，持有BAC的等价获得BAC的价格，BAC的时候，将BAC的价格1:1换回BAC。</p><p>基本的设计其实把BAC当成钞票，BAS可以用印钞票实现高价，BAB可以模拟印钞票，通过世界各地很容易兑现的印制和债券来。就厉害了。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">菲</h3><p>它是另一种稳定币，让ETH/FEI交易的价格锚定ETH/USD，就让FEI锚定了USD。</p><p>1 美元，当我在交易利时套，FE 和我在市场上落下，我在 1 月 1 日开始控制 ETH 的价格，并在使用 FEI 的市场价格，并让 FEI 日涨价。</p><p>债权人的投资无法 100% 的 FEI 能够实现稳定，但没有人愿意坚持铸造新的价格，早期 FEI 的并刚兑现，是完全由人认赔退出的实现。</p><p>算法研究并没有确定在以前的地盘上运行的方法，但没有能够在先前的成功与浅析浅析的基础上运行的方法，但并没有能够成功实现与真实世界的真实世界的实际方法的源源各种地，钱币价格利用资源大的情况，空印钞票交易会在印成巴时，需要利用资源的资源，人的套利元稳定的资源。必须将拉上小规模的结果集利有通，整个把钱从可能带来的利益，表示让所有人一致，世界到的。</p>]]></content:encoded>
            <author>0xfanjf@newsletter.paragraph.com (0xfanjf)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/aebec08479d0be8e85cd06488f0f996bdd3d66513f646491c081c7498600dc5a.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Segwit地址]]></title>
            <link>https://paragraph.com/@0xfanjf/segwit</link>
            <guid>IUUaUpYaQ3uATIfYqY5O</guid>
            <pubDate>Tue, 18 Jan 2022 09:01:44 GMT</pubDate>
            <description><![CDATA[Segwit地址又称隔离见证地址。在比特币区块链上，经常可以看到类似bc1qmy63mjadtw8nhzl69ukdepwzsyvv4yex5qlmkd这样的以bc开头的地址，这种地址就是隔离见证地址。 Segwit地址有好几种，一种是以3开头的隔离见证兼容地址（Nested Segwit Address），从该地址上无法区分到底是多签地址还是隔离见证兼容地址，好处是钱包程序不用修改，可直接付款到该地址。 另一种是原生隔离见证地址（Native Segwit Address），即以bc开头的地址，它本质上就是一种新的编码方式。 我们回顾一下1开头的比特币地址是如何创建的：根据公钥计算hash160；添加固定头并计算带校验的Base58编码。简单地概括就是使用Base58编码的公钥哈希。 而bc地址使用的不是Base58编码，而是Bech32编码，它的算法是：根据公钥计算hash160；使用Base32编码得到更长的编码；以bc作为识别码进行编码并带校验。Bech32编码实际上由两部分组成：一部分是bc这样的前缀，被称为HRP（Human Readable Part，用户可读部分）...]]></description>
            <content:encoded><![CDATA[<p>Segwit地址又称隔离见证地址。在比特币区块链上，经常可以看到类似<code>bc1qmy63mjadtw8nhzl69ukdepwzsyvv4yex5qlmkd</code>这样的以<code>bc</code>开头的地址，这种地址就是隔离见证地址。</p><p>Segwit地址有好几种，一种是以<code>3</code>开头的隔离见证兼容地址（Nested Segwit Address），从该地址上无法区分到底是多签地址还是隔离见证兼容地址，好处是钱包程序不用修改，可直接付款到该地址。</p><p>另一种是原生隔离见证地址（Native Segwit Address），即以<code>bc</code>开头的地址，它本质上就是一种新的编码方式。</p><p>我们回顾一下<code>1</code>开头的比特币地址是如何创建的：</p><ol><li><p>根据公钥计算hash160；</p></li><li><p>添加固定头并计算带校验的Base58编码。</p></li></ol><p>简单地概括就是使用Base58编码的公钥哈希。</p><p>而<code>bc</code>地址使用的不是Base58编码，而是Bech32编码，它的算法是：</p><ol><li><p>根据公钥计算hash160；</p></li><li><p>使用Base32编码得到更长的编码；</p></li><li><p>以<code>bc</code>作为识别码进行编码并带校验。</p></li></ol><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki">Bech32编码</a>实际上由两部分组成：一部分是<code>bc</code>这样的前缀，被称为HRP（Human Readable Part，用户可读部分），另一部分是特殊的Base32编码，使用字母表<code>qpzry9x8gf2tvdw0s3jn54khce6mua7l</code>，中间用<code>1</code>连接。对一个公钥进行Bech32编码的代码如下：</p><pre data-type="codeBlock" text="const
    bitcoin = require(&apos;bitcoinjs-lib&apos;),
    bech32 = require(&apos;bech32&apos;),
    createHash = require(&apos;create-hash&apos;);
"><code>const
    <span class="hljs-attr">bitcoin</span> = require(<span class="hljs-string">'bitcoinjs-lib'</span>),
    <span class="hljs-attr">bech32</span> = require(<span class="hljs-string">'bech32'</span>),
    <span class="hljs-attr">createHash</span> = require(<span class="hljs-string">'create-hash'</span>)<span class="hljs-comment">;</span>
</code></pre><p>和Base58地址相比，Bech32地址的优点有：</p><ol><li><p>不用区分大小写，因为编码用的字符表没有大写字母；</p></li><li><p>有个固定前缀，可任意设置，便于识别；</p></li><li><p>生成的二维码更小。</p></li></ol><p>它的缺点是：</p><ol><li><p>和现有地址不兼容，钱包程序必须升级；</p></li><li><p>使用<code>1</code>作为分隔符，却使用了字母<code>l</code>，容易混淆；</p></li><li><p>地址更长，有42个字符。</p></li></ol><p>那为什么要引入Segwit地址呢？按照官方说法，它的目的是为了解决比特币交易的延展性（Transaction Malleability）攻击。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">延展性攻击</h3><p>什么是延展性攻击呢？我们先回顾一下比特币的区块链如何保证一个交易有效并且不被修改：</p><ol><li><p>每个交易都必须签名才能花费输入（UTXO）；</p></li><li><p>所有交易的哈希以Merkle Tree计算并存储到区块头。</p></li></ol><p>我们再看每个交易的细节，假设有一个输入和一个输出，它类似：</p><pre data-type="codeBlock" text="tx = ... input#index ... signature ... output-script ...
"><code><span class="hljs-built_in">tx</span> <span class="hljs-operator">=</span> ... input#index ... signature ... output<span class="hljs-operator">-</span>script ...
</code></pre><p>而整个交易的哈希可直接根据交易本身计算：</p><pre data-type="codeBlock" text="tx-hash = dhash(tx)
"><code><span class="hljs-built_in">tx</span><span class="hljs-operator">-</span>hash <span class="hljs-operator">=</span> dhash(<span class="hljs-built_in">tx</span>)
</code></pre><p>因为只有私钥持有人才能正确地签名，所以，只要签名是有效的，tx本身就应该固定下来。</p><p>但问题出在ECDSA签名算法上。ECDSA签名算法基于私钥计算的签名实际上是两个整数，记作<code>(r, s)</code>，但由于椭圆曲线的对称性，<code>(r, -s mod N)</code>实际上也是一个有效的签名（N是椭圆曲线的固定参数之一）。换句话说，对某个交易进行签名，总是可以计算出两个有效的签名，并且这两个有效的签名还可以互相计算出来。</p><p>黑客可以在某一笔交易发出但并未落块的时间内，对签名进行修改，使之仍是一个有效的交易。注意黑客并无法修改任何输入输出的地址和金额，仅能修改签名。但由于签名的修改，使得整个交易的哈希被改变了。如果修改后的交易先被打包，虽然原始交易会被丢弃，且并不影响交易安全，但这个延展性攻击可用于攻击交易所。</p><p>要解决延展性攻击的问题，有两个办法，一是对交易签名进行归一化（Normalize）。因为ECDSA签名后总有两个有效的签名<code>(r, s)</code>和<code>(r, -s mod N)</code>，那只接受数值较小的那个签名，为此比特币引入了一个<code>SCRIPT_VERIFY_LOW_S</code>标志仅接受较小值的签名。</p><p>另一个办法是把签名数据移到交易之外，这样交易本身的哈希就不会变化。不含签名的交易计算出的哈希称为wtxid，为此引入了一种新的隔离见证地址。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">小结</h3><p>以<code>bc</code>开头的隔离见证地址使用了Bech32编码；</p><p>比特币延展性攻击的原因是ECDSA签名总是有两个有效签名，且可以相互计算；</p><p>规范ECDSA签名格式可强制使用固定签名（例如总是使用较小值的签名）。</p>]]></content:encoded>
            <author>0xfanjf@newsletter.paragraph.com (0xfanjf)</author>
        </item>
        <item>
            <title><![CDATA[UTXO模型]]></title>
            <link>https://paragraph.com/@0xfanjf/utxo</link>
            <guid>4RLwmJoHIqJ5IMauWRzU</guid>
            <pubDate>Tue, 18 Jan 2022 01:22:31 GMT</pubDate>
            <description><![CDATA[比特币的区块链由一个个区块串联构成，而每个区块又包含一个或多个交易。 如果我们观察任何一个交易，它总是由若干个输入（Input）和若干个输出（Output）构成，一个Input指向的是前面区块的某个Output，只有Coinbase交易（矿工奖励的铸币交易）没有输入，只有凭空输出。所以，任何交易，总是可以由Input溯源到Coinbase交易。 这些交易的Input和Output总是可以串联起来：┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │Block #1 │ │Block #2 │ │Block #3 │ │Block #4 │ │┌──┬────┬───┐│ │┌──┬────┬───┐│ │┌──┬────┬───┐│ │┌──┬────┬───┐│ ││CB│50.0│OUT├┼──┐ ││CB│50.0│OUT├┼──┐ ││CB│50.0│OUT├┼──┐ ││CB│50.0│OUT││ │└──┴────┴───┘│ │ │└──┴────┴───┘│ │ │└──┴────┴──...]]></description>
            <content:encoded><![CDATA[<p>比特币的区块链由一个个区块串联构成，而每个区块又包含一个或多个交易。</p><p>如果我们观察任何一个交易，它总是由若干个输入（Input）和若干个输出（Output）构成，一个Input指向的是前面区块的某个Output，只有Coinbase交易（矿工奖励的铸币交易）没有输入，只有凭空输出。所以，任何交易，总是可以由Input溯源到Coinbase交易。</p><p>这些交易的Input和Output总是可以串联起来：</p><pre data-type="codeBlock" text="┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│Block #1     │     │Block #2     │     │Block #3     │     │Block #4     │
│┌──┬────┬───┐│     │┌──┬────┬───┐│     │┌──┬────┬───┐│     │┌──┬────┬───┐│
││CB│50.0│OUT├┼──┐  ││CB│50.0│OUT├┼──┐  ││CB│50.0│OUT├┼──┐  ││CB│50.0│OUT││
│└──┴────┴───┘│  │  │└──┴────┴───┘│  │  │└──┴────┴───┘│  │  │└──┴────┴───┘│
│             │  │  │┌──┬────┬───┐│  │  │┌──┬────┬───┐│  │  │┌──┬────┬───┐│
│             │  │  ││  │8.70│OUT├┼──┼──&gt;│IN│    │   ││  └──&gt;│IN│25.0│OUT││
│             │  └──&gt;│IN├────┼───┤│  │  │├──┤58.7│OUT││     │├──┼────┼───┤│
│             │     ││  │41.3│OUT├┼─┐└──&gt;│IN│    │   ││  ┌──&gt;│IN│66.3│OUT││
│             │     │└──┴────┴───┘│ │   │└──┴────┴───┘│  │  │└──┴────┴───┘│
└─────────────┘     └─────────────┘ │   └─────────────┘  │  └─────────────┘
                                    └────────────────────┘
"><code>┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│Block #<span class="hljs-number">1</span>     │     │Block #<span class="hljs-number">2</span>     │     │Block #<span class="hljs-number">3</span>     │     │Block #<span class="hljs-number">4</span>     │
│┌──┬────┬───┐│     │┌──┬────┬───┐│     │┌──┬────┬───┐│     │┌──┬────┬───┐│
││CB│<span class="hljs-number">50.0</span>│<span class="hljs-keyword">OUT</span>├┼──┐  ││CB│<span class="hljs-number">50.0</span>│<span class="hljs-keyword">OUT</span>├┼──┐  ││CB│<span class="hljs-number">50.0</span>│<span class="hljs-keyword">OUT</span>├┼──┐  ││CB│<span class="hljs-number">50.0</span>│<span class="hljs-keyword">OUT</span>││
│└──┴────┴───┘│  │  │└──┴────┴───┘│  │  │└──┴────┴───┘│  │  │└──┴────┴───┘│
│             │  │  │┌──┬────┬───┐│  │  │┌──┬────┬───┐│  │  │┌──┬────┬───┐│
│             │  │  ││  │<span class="hljs-number">8.70</span>│<span class="hljs-keyword">OUT</span>├┼──┼──<span class="hljs-operator">></span>│<span class="hljs-keyword">IN</span>│    │   ││  └──<span class="hljs-operator">></span>│<span class="hljs-keyword">IN</span>│<span class="hljs-number">25.0</span>│<span class="hljs-keyword">OUT</span>││
│             │  └──<span class="hljs-operator">></span>│<span class="hljs-keyword">IN</span>├────┼───┤│  │  │├──┤<span class="hljs-number">58.7</span>│<span class="hljs-keyword">OUT</span>││     │├──┼────┼───┤│
│             │     ││  │<span class="hljs-number">41.3</span>│<span class="hljs-keyword">OUT</span>├┼─┐└──<span class="hljs-operator">></span>│<span class="hljs-keyword">IN</span>│    │   ││  ┌──<span class="hljs-operator">></span>│<span class="hljs-keyword">IN</span>│<span class="hljs-number">66.3</span>│<span class="hljs-keyword">OUT</span>││
│             │     │└──┴────┴───┘│ │   │└──┴────┴───┘│  │  │└──┴────┴───┘│
└─────────────┘     └─────────────┘ │   └─────────────┘  │  └─────────────┘
                                    └────────────────────┘
</code></pre><p>还没有被下一个交易花费的Output被称为UTXO：Unspent TX Output，即未花费交易输出。给定任何一个区块，计算当前所有的UXTO金额之和，等同于自创世区块到给定区块的挖矿奖励之和。</p><p>因此，比特币的交易模型和我们平时使用的银行账号有所不同，它并没有账户这个说法，只有UTXO。想要确定某个人拥有的比特币，并无法通过某个账户查到，必须知道此人控制的所有UTXO金额之和。</p><p>在钱包程序中，钱包管理的是一组私钥，对应的是一组公钥和地址。钱包程序必须从创世区块开始扫描每一笔交易，如果：</p><ol><li><p>遇到某笔交易的某个Output是钱包管理的地址之一，则钱包余额增加；</p></li><li><p>遇到某笔交易的某个Input是钱包管理的地址之一，则钱包余额减少。</p></li></ol><p>钱包的当前余额总是钱包地址关联的所有UTXO金额之和。</p><p>如果刚装了一个新钱包，导入了一组私钥，在钱包扫描完整个比特币区块之前，是无法得知当前管理的地址余额的。</p><p>那么，给定一个地址，要查询该地址的余额，难道要从头扫描几百GB的区块链数据？</p><p>当然不是。</p><p>要做到瞬时查询，我们知道，使用关系数据库的主键进行查询，由于用了索引，速度极快。</p><p>因此，对区块链进行查询之前，首先要扫描整个区块链，重建一个类似关系数据库的地址-余额映射表。这个表的结构如下：</p><p>addressbalancelastUpdatedAtBlockaddress-150.00</p><p>一开始，这是一个空表。每当扫描一个区块的所有交易后，某些地址的余额增加，另一些地址的余额减少，两者之差恰好为区块奖励：</p><p>addressbalancelastUpdatedAtBlockaddress-150.00address-240.03address-350.03address-410.03</p><p>这样，扫描完所有区块后，我们就得到了整个区块链所有地址的完整余额记录，查询的时候，并不是从区块链查询，而是从本地数据库查询。大多数钱包程序使用<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/google/leveldb">LevelDB</a>来存储这些信息，手机钱包程序则是请求服务器，由服务器查询数据库后返回结果。</p><p>如果我们把MySQL这样的数据库看作可修改的，那么区块链就是不可修改，只能追加的只读数据库。但是，MySQL这样的数据库虽然其状态是可修改的，但它的状态改变却是由修改语句（INSERT/UPDATE/DELETE）引起的。把MySQL的binlog日志完整地记录下来，再进行重放，即可在另一台机器上完整地重建整个数据库。把区块链看作不可修改的binlog日志，我们只要把每个区块的所有交易重放一遍，即可重建一个地址-余额的数据库。</p><p>可见，比特币的区块链记录的是修改日志，而不是当前状态。</p>]]></content:encoded>
            <author>0xfanjf@newsletter.paragraph.com (0xfanjf)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/cb2c4f0639857b953453a6ebe723eceed077605f40d168cf91c439fc6008f98b.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[多重签名]]></title>
            <link>https://paragraph.com/@0xfanjf/IhlhGIVn7P1JU8mp7YBW</link>
            <guid>IhlhGIVn7P1JU8mp7YBW</guid>
            <pubDate>Tue, 18 Jan 2022 01:10:12 GMT</pubDate>
            <description><![CDATA[由比特币的签名机制可知，如果丢失了私钥，没有任何办法可以花费对应地址的资金。 这样就使得因为丢失私钥导致资金丢失的风险会很高。为了避免一个私钥的丢失导致地址的资金丢失，比特币引入了多重签名机制，可以实现分散风险的功能。 具体来说，就是假设N个人分别持有N个私钥，只要其中M个人同意签名就可以动用某个“联合地址”的资金。 多重签名地址实际上是一个Script Hash，以2-3类型的多重签名为例，它的创建过程如下： 常量比特币 = 要求（&apos;bitcoinjs-lib&apos;）；让pubKey1 = &apos;026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01&apos;，pubkey2到本地= &apos;02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9&apos;，pubKey3 = &apos;03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a...]]></description>
            <content:encoded><![CDATA[<p>由比特币的签名机制可知，如果丢失了私钥，没有任何办法可以花费对应地址的资金。</p><p>这样就使得因为丢失私钥导致资金丢失的风险会很高。为了避免一个私钥的丢失导致地址的资金丢失，比特币引入了多重签名机制，可以实现分散风险的功能。</p><p>具体来说，就是假设N个人分别持有N个私钥，只要其中M个人同意签名就可以动用某个“联合地址”的资金。</p><p>多重签名地址实际上是一个Script Hash，以2-3类型的多重签名为例，它的创建过程如下：</p><p>常量比特币 = 要求（&apos;bitcoinjs-lib&apos;）；让pubKey1 = &apos;026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01&apos;，pubkey2到本地= &apos;02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9&apos;，pubKey3 = &apos;03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9&apos;，pubKeys = [pubKey1，pubkey2到本地，pubKey3] .MAP（S =&gt; Buffer.from（S， &apos;六角&apos;））; // 注意把字符串转换为缓冲区</p><p>// 创建2-3 RedeemScript: letredeemScript = bitcoin.script.multisig.output.encode(2, pubKeys); console.log(&apos;兑换脚本：&apos;+redeemScript.toString(&apos;hex&apos;));</p><p>// 编码: let scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)); let address = bitcoin.address.fromOutputScript(scriptPubKey);</p><p>console.log(&apos;Multisig address: &apos; + address); // 36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7</p><p>Run 首先，我们需要所有公钥列表，这里是3个公钥。然后，通过bitcoin.script.multisig.output.encode()方法编码为2-3类型的脚本，对这个脚本计算hash160后，使用Base58编码即得到总是以3开头的多重签名地址，这个地址实际上是一个脚本哈希后的编码。</p><p>以3开头的地址就是比特币的多重签名地址，但从地址本身无法得知签名所需的M/N。 如果我们观察Redeem Script的输出，它的十六进制实际上是：</p><p>52 21 026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01 21 02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9 21 03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9 53 ae 翻译成比特币的脚本指令就是：</p><p>OP_2 PUSHDATA(33) 026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01 PUSHDATA(33) 02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9 PUSHDATA(33) 03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9 OP_3 OP_CHECKMULTISIG OP_2和OP_3构成2-3多重签名，这两个指令中间的3个PUSHDATA(33)就是我们指定的3个公钥，最后一个OP_CHECKMULTISIG表示需要验证多重签名。</p><p>发送给多重签名地址的交易创建的是P2SH脚本，而花费多重签名地址的资金需要的脚本就是M个签名+Redeem Script。</p><p>注意：从多重签名的地址本身并无法得知该多重签名使用的公钥，以及M-N的具体数值。必须将Redeem Script公示给每个私钥持有人，才能够验证多重签名地址是否正确（即包含了所有人的公钥，以及正确的M-N数值）。要花费多重签名地址的资金，除了M个私钥签名外，必须要有Redeem Script（可由所有人的公钥构造）。只有签名，没有Redeem Script是不能构造出解锁脚本来花费资金的。因此，保存多重签名地址的钱包必须同时保存Redeem Script。</p><p>利用多重签名，可以实现：</p><p>1-2，两人只要有一人同意即可使用资金； 2-2，两人必须都同意才可使用资金； 2-3，3人必须至少两人同意才可使用资金； 4-7，7人中多数人同意才可使用资金。 最常见的多重签名是2-3类型。例如，一个提供在线钱包的服务，为了防止服务商盗取用户的资金，可以使用2-3类型的多重签名地址，服务商持有1个私钥，用户持有两个私钥，一个作为常规使用，一个作为应急使用。这样，正常情况下，用户只需使用常规私钥即可配合服务商完成正常交易，服务商因为只持有1个私钥，因此无法盗取用户资金。如果服务商倒闭或者被黑客攻击，用户可使用自己掌握的两个私钥转移资金。</p><p>大型机构的比特币通常都使用多重签名地址以保证安全。例如，某个交易所的3-6多重签名地址3D2oetdNuZUqQHPJmcMDDHYoqkyNVsFk9r。</p><p>利用多重签名，可以使得私钥丢失的风险被分散到N个人手中，并且，避免了少数人窃取资金的问题。</p><p>比特币的多重签名最多允许15个私钥参与签名，即可实现1-2至15-15的任意组合（1⩽M⩽N⩽15）。</p><p>小结 多重签名可以实现N个人持有私钥，其中M个人同意即可花费资金的功能。</p><p>多重签名降低了单个私钥丢失的风险。</p><p>支付比特币到一个多重签名地址实际上是创建一个P2SH输出。</p>]]></content:encoded>
            <author>0xfanjf@newsletter.paragraph.com (0xfanjf)</author>
        </item>
        <item>
            <title><![CDATA[挖矿原理]]></title>
            <link>https://paragraph.com/@0xfanjf/F76StG44Jygz9M8OBQ0R</link>
            <guid>F76StG44Jygz9M8OBQ0R</guid>
            <pubDate>Tue, 18 Jan 2022 01:08:54 GMT</pubDate>
            <description><![CDATA[在比特币的P2P网络中，有一类节点，它们时刻不停地进行计算，试图把新的交易打包成新的区块并附加到区块链上，这类节点就是矿工。因为每打包一个新的区块，打包该区块的矿工就可以获得一笔比特币作为奖励。所以，打包新区块就被称为挖矿。 比特币的挖矿原理就是一种工作量证明机制。工作量证明POW是英文Proof of Work的缩写。 在讨论POW之前，我们先思考一个问题：在一个新区块中，凭什么是小明得到50个币的奖励，而不是小红或者小军？ 当小明成功地打包了一个区块后，除了用户的交易，小明会在第一笔交易记录里写上一笔“挖矿”奖励的交易，从而给自己的地址添加50个比特币。为什么比特币的P2P网络会承认小明打包的区块，并且认可小明得到的区块奖励呢？ 因为比特币的挖矿使用了工作量证明机制，小明的区块被认可，是因为他在打包区块的时候，做了一定的工作，而P2P网络的其他节点可以验证小明的工作量。工作量证明什么是工作量证明？工作量证明是指，证明自己做了一定的工作量。例如，在驾校学习了50个小时。而其他人可以简单地验证该工作量。例如，出示驾照，表示自己确实在驾校学习了一段时间： 比特币的工作量证明需要归结...]]></description>
            <content:encoded><![CDATA[<p>在比特币的P2P网络中，有一类节点，它们时刻不停地进行计算，试图把新的交易打包成新的区块并附加到区块链上，这类节点就是矿工。因为每打包一个新的区块，打包该区块的矿工就可以获得一笔比特币作为奖励。所以，打包新区块就被称为挖矿。</p><p>比特币的挖矿原理就是一种工作量证明机制。工作量证明POW是英文Proof of Work的缩写。</p><p>在讨论POW之前，我们先思考一个问题：在一个新区块中，凭什么是小明得到50个币的奖励，而不是小红或者小军？</p><p>当小明成功地打包了一个区块后，除了用户的交易，小明会在第一笔交易记录里写上一笔“挖矿”奖励的交易，从而给自己的地址添加50个比特币。为什么比特币的P2P网络会承认小明打包的区块，并且认可小明得到的区块奖励呢？</p><p>因为比特币的挖矿使用了工作量证明机制，小明的区块被认可，是因为他在打包区块的时候，做了一定的工作，而P2P网络的其他节点可以验证小明的工作量。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">工作量证明</h3><p>什么是工作量证明？工作量证明是指，证明自己做了一定的工作量。例如，在驾校学习了50个小时。而其他人可以简单地验证该工作量。例如，出示驾照，表示自己确实在驾校学习了一段时间：</p><p>比特币的工作量证明需要归结为计算机计算，也就是数学问题。如何构造一个数学问题来实现工作量证明？我们来看一个简单的例子。</p><p>假设某个学校的一个班里，只有一个女生叫小红，其他都是男生。每个男生都想约小红看电影，但是，能实现愿望的只能有一个男生。</p><p>到底选哪个男生呢？本着公平原则，小红需要考察每个男生的诚意，考察的方法是，出一道数学题，比如说解方程，谁第一个解出这个方程，谁就有资格陪小红看电影：</p><p>因为解高次方程没有固定的公式，需要进行大量的计算，才能算出正确的结果，这个计算过程就需要一定的工作量。假设小明率先计算出了结果x＝2.5，小红可以简单地验证这个结果是否正确：</p><p>可以看出，解方程很困难，但是，验证结果却比较简单。所以，一个有效的工作量证明在于：计算过程非常复杂，需要消耗一定的时间，但是，验证过程相对简单，几乎可以瞬间完成。</p><p>现在出现了另一个问题：如果其他人偷看了小明的答案并且抢答了怎么办？</p><p>要解决这个问题也很容易，小红可以按照男生的编号，给不同的男生发送不同的方程，方程的第一项的系数就是编号。这样，每个人要解的方程都是不一样的。小明解出的x＝2.5对于小军来说是无效的，因为小军的编号是3，用小明的结果验证小军的方程是无法通过验证的。</p><p>事实上如果某个方程被验证通过了，小红可以直接从方程的第一项系数得知是谁解出的方程。所以，窃取别人的工作量证明的结果是没有用的。</p><p>通过工作量证明，可以有效地验证每个人确实都必须花费一定时间做了计算。</p><p>在比特币网络中，矿工的挖矿也是一种工作量证明，但是，不能用解多项式方程来实现，因为解多项式方程对人来说很难计算，对计算机来说非常容易，可以在1秒钟以内完成。</p><p>要让计算机实现工作量证明，必须找到一种工作量算法，让计算机无法在短时间内算出来。这种算法就是哈希算法。</p><p>通过改变区块头部的一个<code>nonce</code>字段的值，计算机可以计算出不同的区块哈希值：</p><p>直到计算出某个特定的哈希值的时候，计算结束。这个哈希和其他的哈希相比，它的特点是前面有好几个0：</p><pre data-type="codeBlock" text="hash256(block data, nonce=0) = 291656f37cdcf493c4bb7b926e46fee5c14f9b76aff28f9d00f5cca0e54f376f
hash256(block data, nonce=1) = f7b2c15c4de7f482edee9e8db7287a6c5def1c99354108ef33947f34d891ea8d
hash256(block data, nonce=2) = b6eebc5faa4c44d9f5232631f39ddf4211443d819208da110229b644d2a99e12
hash256(block data, nonce=3) = 00aeaaf01166a93a2217fe01021395b066dd3a81daffcd16626c308c644c5246
hash256(block data, nonce=4) = 26d33671119c9180594a91a2f1f0eb08bdd0b595e3724050acb68703dc99f9b5
hash256(block data, nonce=5) = 4e8a3dcab619a7ce5c68e8f4abdc49f98de1a71e58f0ce9a0d95e024cce7c81a
hash256(block data, nonce=6) = 185f634d50b17eba93b260a911ba6dbe9427b72f74f8248774930c0d8588c193
hash256(block data, nonce=7) = 09b19f3d32e3e5771bddc5f0e1ee3c1bac1ba4a85e7b2cc30833a120e41272ed
...
hash256(block data, nonce=124709132) = 00000000fba7277ef31c8ecd1f3fef071cf993485fe5eab08e4f7647f47be95c
"><code>hash256(<span class="hljs-built_in">block</span> data, nonce<span class="hljs-operator">=</span><span class="hljs-number">0</span>) <span class="hljs-operator">=</span> 291656f37cdcf493c4bb7b926e46fee5c14f9b76aff28f9d00f5cca0e54f376f
hash256(<span class="hljs-built_in">block</span> data, nonce<span class="hljs-operator">=</span><span class="hljs-number">1</span>) <span class="hljs-operator">=</span> f7b2c15c4de7f482edee9e8db7287a6c5def1c99354108ef33947f34d891ea8d
hash256(<span class="hljs-built_in">block</span> data, nonce<span class="hljs-operator">=</span><span class="hljs-number">2</span>) <span class="hljs-operator">=</span> b6eebc5faa4c44d9f5232631f39ddf4211443d819208da110229b644d2a99e12
hash256(<span class="hljs-built_in">block</span> data, nonce<span class="hljs-operator">=</span><span class="hljs-number">3</span>) <span class="hljs-operator">=</span> 00aeaaf01166a93a2217fe01021395b066dd3a81daffcd16626c308c644c5246
hash256(<span class="hljs-built_in">block</span> data, nonce<span class="hljs-operator">=</span><span class="hljs-number">4</span>) <span class="hljs-operator">=</span> 26d33671119c9180594a91a2f1f0eb08bdd0b595e3724050acb68703dc99f9b5
hash256(<span class="hljs-built_in">block</span> data, nonce<span class="hljs-operator">=</span><span class="hljs-number">5</span>) <span class="hljs-operator">=</span> 4e8a3dcab619a7ce5c68e8f4abdc49f98de1a71e58f0ce9a0d95e024cce7c81a
hash256(<span class="hljs-built_in">block</span> data, nonce<span class="hljs-operator">=</span><span class="hljs-number">6</span>) <span class="hljs-operator">=</span> 185f634d50b17eba93b260a911ba6dbe9427b72f74f8248774930c0d8588c193
hash256(<span class="hljs-built_in">block</span> data, nonce<span class="hljs-operator">=</span><span class="hljs-number">7</span>) <span class="hljs-operator">=</span> 09b19f3d32e3e5771bddc5f0e1ee3c1bac1ba4a85e7b2cc30833a120e41272ed
...
hash256(<span class="hljs-built_in">block</span> data, nonce<span class="hljs-operator">=</span><span class="hljs-number">124709132</span>) <span class="hljs-operator">=</span> 00000000fba7277ef31c8ecd1f3fef071cf993485fe5eab08e4f7647f47be95c
</code></pre><p>比特币挖矿的工作量证明原理就是，不断尝试计算区块的哈希，直到计算出一个特定的哈希值，它比难度值要小。</p><p>比特币使用的SHA-256算法可以看作对随机输入产生随机输出，例如，我们对字符串<code>Hello</code>再加上一个数字计算两次SHA-256，根据数字的不同，得到的哈希是完全无规律的256位随机数：</p><pre data-type="codeBlock" text="hash256(&quot;Hello?&quot;) = ????????????????????????????????????????????????????????????????
"><code>hash256(<span class="hljs-string">"Hello?"</span>) = <span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span><span class="hljs-string">??</span>
</code></pre><p>大约计算16次，我们可以在得到的哈希中找到首位是<code>0</code>的哈希值，因为首位是0出现的概率是1/16：</p><pre data-type="codeBlock" text="hash256(&quot;Hello1&quot;) = ffb7a43d629d363026b3309586233ab7ffc1054c4f56f43a92f0054870e7ddc9
hash256(&quot;Hello2&quot;) = e085bf19353eb3bd1021661a17cee97181b0b369d8e16c10ffb7b01287a77173
hash256(&quot;Hello3&quot;) = c5061965d37b8ed989529bf42eaf8a90c28fa00c3853c7eec586aa8b3922d404
hash256(&quot;Hello4&quot;) = 42c3104987afc18677179a4a1a984dbfc77e183b414bc6efb00c43b41b213537
hash256(&quot;Hello5&quot;) = 652dcd7b75d499bcdc61d0c4eda96012e3830557de01426da5b01e214b95cd7a
hash256(&quot;Hello6&quot;) = 4cc0fbe28abb820085f390d66880ece06297d74d13a6ddbbab3b664582a7a582
hash256(&quot;Hello7&quot;) = c3eef05b531b56e79ca38e5f46e6c04f21b0078212a1d8c3500aa38366d9786d
hash256(&quot;Hello8&quot;) = cf17d3f38036206cfce464cdcb44d9ccea3f005b7059cff1322c0dd8bf398830
hash256(&quot;Hello9&quot;) = 1f22981824c821d4e83246e71f207d0e49ad57755889874d43def42af693a077
hash256(&quot;Hello10&quot;) = 8a1e475d67cfbcea4bcf72d1eee65f15680515f65294c68b203725a9113fa6bf
hash256(&quot;Hello11&quot;) = 769987b3833f082e31476db0f645f60635fa774d2b92bf0bab00e0a539a2dede
hash256(&quot;Hello12&quot;) = c2acd1bb160b1d1e66d769a403e596b174ffab9a39aa7c44d1e670feaa67ab2d
hash256(&quot;Hello13&quot;) = dab8b9746f1c0bcf5750e0d878fc17940db446638a477070cf8dca8c3643618a
hash256(&quot;Hello14&quot;) = 51a575773fccbb5278929c08e788c1ce87e5f44ab356b8760776fd816357f6ff
hash256(&quot;Hello15&quot;) = 0442e1c38b810f5d3c022fc2820b1d7999149460b83dc680abdebc9c7bd65cae
"><code><span class="hljs-built_in">hash256</span>("Hello1") = ffb7a43d629d363026b3309586233ab7ffc1054c4f56f43a92f0054870e7ddc9
<span class="hljs-built_in">hash256</span>("Hello2") = e085bf19353eb3bd1021661a17cee97181b0b369d8e16c10ffb7b01287a77173
<span class="hljs-built_in">hash256</span>("Hello3") = c5061965d37b8ed989529bf42eaf8a90c28fa00c3853c7eec586aa8b3922d404
<span class="hljs-built_in">hash256</span>("Hello4") = <span class="hljs-number">42</span>c3104987afc18677179a4a1a984dbfc77e183b414bc6efb00c43b41b213537
<span class="hljs-built_in">hash256</span>("Hello5") = <span class="hljs-number">652</span>dcd7b75d499bcdc61d0c4eda96012e3830557de01426da5b01e214b95cd7a
<span class="hljs-built_in">hash256</span>("Hello6") = <span class="hljs-number">4</span>cc0fbe28abb820085f390d66880ece06297d74d13a6ddbbab3b664582a7a582
<span class="hljs-built_in">hash256</span>("Hello7") = c3eef05b531b56e79ca38e5f46e6c04f21b0078212a1d8c3500aa38366d9786d
<span class="hljs-built_in">hash256</span>("Hello8") = cf17d3f38036206cfce464cdcb44d9ccea3f005b7059cff1322c0dd8bf398830
<span class="hljs-built_in">hash256</span>("Hello9") = <span class="hljs-number">1</span>f22981824c821d4e83246e71f207d0e49ad57755889874d43def42af693a077
<span class="hljs-built_in">hash256</span>("Hello10") = <span class="hljs-number">8</span>a1e475d67cfbcea4bcf72d1eee65f15680515f65294c68b203725a9113fa6bf
<span class="hljs-built_in">hash256</span>("Hello11") = <span class="hljs-number">769987</span>b3833f082e31476db0f645f60635fa774d2b92bf0bab00e0a539a2dede
<span class="hljs-built_in">hash256</span>("Hello12") = c2acd1bb160b1d1e66d769a403e596b174ffab9a39aa7c44d1e670feaa67ab2d
<span class="hljs-built_in">hash256</span>("Hello13") = dab8b9746f1c0bcf5750e0d878fc17940db446638a477070cf8dca8c3643618a
<span class="hljs-built_in">hash256</span>("Hello14") = <span class="hljs-number">51</span>a575773fccbb5278929c08e788c1ce87e5f44ab356b8760776fd816357f6ff
<span class="hljs-built_in">hash256</span>("Hello15") = <span class="hljs-number">0442</span>e1c38b810f5d3c022fc2820b1d7999149460b83dc680abdebc9c7bd65cae
</code></pre><p>如果我们要找出前两位是<code>0</code>的哈希值，理论上需要计算256次，因为<code>00</code>出现的概率是162=256，实际计算44次：</p><pre data-type="codeBlock" text="hash256(&quot;Hello44&quot;) = 00e477f95283a544ffac7a8efc7decb887f5c073e0f3b43b3797b5dafabb49b5
"><code><span class="hljs-built_in">hash256</span>("Hello44") = <span class="hljs-number">00</span>e477f95283a544ffac7a8efc7decb887f5c073e0f3b43b3797b5dafabb49b5
</code></pre><p>如果我们要找出前3位是<code>0</code>的哈希值，理论上需要计算163=4096次，实际计算6591次：</p><pre data-type="codeBlock" text="hash256(&quot;Hello6591&quot;) = 0008a883dacb7094d6da1a6cefc6e7cbc13635d024ac15152c4eadba7af8d11c
"><code><span class="hljs-built_in">hash256</span>("Hello6591") = <span class="hljs-number">0008</span>a883dacb7094d6da1a6cefc6e7cbc13635d024ac15152c4eadba7af8d11c
</code></pre><p>如果我们要找出前4位是<code>0</code>的哈希值，理论上需要计算164=6万5千多次，实际计算6万7千多次：</p><pre data-type="codeBlock" text="hash256(&quot;Hello67859&quot;) = 00002e4af0b80d706ae749d22247d91d9b1c2e91547d888e5e7a91bcc0982b87
"><code><span class="hljs-built_in">hash256</span>("Hello67859") = <span class="hljs-number">00002</span>e4af0b80d706ae749d22247d91d9b1c2e91547d888e5e7a91bcc0982b87
</code></pre><p>如果我们要找出前5位是<code>0</code>的哈希值，理论上需要计算165=104万次，实际计算158万次：</p><pre data-type="codeBlock" text="hash256(&quot;Hello1580969&quot;) = 00000ca640d95329f965bde016b866e75a3e29e1971cf55ffd1344cdb457930e
"><code><span class="hljs-built_in">hash256</span>("Hello1580969") = <span class="hljs-number">00000</span>ca640d95329f965bde016b866e75a3e29e1971cf55ffd1344cdb457930e
</code></pre><p>如果我们要找出前6位是<code>0</code>的哈希值，理论上需要计算166=1677万次，实际计算1558万次：</p><pre data-type="codeBlock" text="hash256(&quot;Hello15583041&quot;) = 0000009becc5cf8c9e6ba81b1968575a1d15a93112d3bd67f4546f6172ef7e76
"><code><span class="hljs-built_in">hash256</span>("Hello15583041") = <span class="hljs-number">0000009</span>becc5cf8c9e6ba81b1968575a1d15a93112d3bd67f4546f6172ef7e76
</code></pre><p>对于给定难度的SHA-256：假设我们用难度1表示必须算出首位1个<code>0</code>，难度2表示必须算出首位两个<code>0</code>，难度N表示必须算出首位N个<code>0</code>，那么，每增加一个难度，计算量将增加16倍。</p><p>对于比特币挖矿来说，就是先给定一个难度值，然后不断变换<code>nonce</code>，计算Block Hash，直到找到一个比给定难度值低的Block Hash，就算成功挖矿。</p><p>我们用简化的方法来说明难度，例如，必须计算出连续17个<code>0</code>开头的哈希值，矿工先确定Prev Hash，Merkle Hash，Timestamp，bits，然后，不断变化<code>nonce</code>来计算哈希，直到找出连续17个<code>0</code>开头的哈希值。我们可以大致推算一下，17个十六进制的<code>0</code>相当于计算了1617次，大约需要计算2.9万亿亿次。</p><pre data-type="codeBlock" text="17个0 = 1617 = 295147905179352825856 = 2.9万亿亿次
"><code><span class="hljs-number">17</span>个<span class="hljs-number">0</span> <span class="hljs-operator">=</span> <span class="hljs-number">1617</span> <span class="hljs-operator">=</span> <span class="hljs-number">295147905179352825856</span> <span class="hljs-operator">=</span> <span class="hljs-number">2.9</span>万亿亿次
</code></pre><p>实际的难度是根据<code>bits</code>由一个公式计算出来，比特币协议要求计算出的区块的哈希值比难度值要小，这个区块才算有效：</p><pre data-type="codeBlock" text="Difficulty = 402937298
           = 0x18 0455d2
           = 0x0455d2 * 28 * (0x18 - 3)
           = 106299667504289830835845558415962632664710558339861315584
           = 0x00000000000000000455d2000000000000000000000000000000000000000000
"><code>Difficulty <span class="hljs-operator">=</span> <span class="hljs-number">402937298</span>
           <span class="hljs-operator">=</span> <span class="hljs-number">0x18</span> 0455d2
           <span class="hljs-operator">=</span> <span class="hljs-number">0x0455d2</span> <span class="hljs-operator">*</span> <span class="hljs-number">28</span> <span class="hljs-operator">*</span> (<span class="hljs-number">0x18</span> <span class="hljs-operator">-</span> <span class="hljs-number">3</span>)
           <span class="hljs-operator">=</span> <span class="hljs-number">106299667504289830835845558415962632664710558339861315584</span>
           <span class="hljs-operator">=</span> <span class="hljs-number">0x00000000000000000455d2000000000000000000000000000000000000000000</span>
</code></pre><p>注意，难度值越小，说明哈希值前面的<code>0</code>越多，计算难度越大。</p><p>比特币网络的难度是不断变化的，它的难度保证大约每10分钟产生一个区块，而难度值在每2015个区块调整一次：如果区块平均生成时间小于10分钟，说明全网算力增加，难度也会增加，如果区块平均生成时间大于10分钟，说明全网算力减少，难度也会减少。因此，难度随着全网算力的增减会动态调整。</p><p>比特币设计时本来打算每2016个区块调整一次难度，也就是两周一次，但是由于第一版代码的一个bug，实际调整周期是2015个区块。</p><p>根据比特币每个区块的难度值和产出时间，就可以推算出整个比特币网络的全网算力。</p><p>比特币网络的全网算力一直在迅速增加。目前，全网算力已经超过了100EH/每秒，也就是大约每秒钟计算1万亿亿次哈希：</p><p>所以比特币的工作量证明被通俗地称之为挖矿。在同一时间，所有矿工都在努力计算下一个区块的哈希。而挖矿难度取决于全网总算力的百分比。举个例子，假设小明拥有全网总算力的百分之一，那么他挖到下一个区块的可能性就是1%，或者说，每挖出100个区块，大约有1个就是小明挖的。</p><p>由于目前全网算力超过了100EH/s，而单机CPU算力不过几M，GPU算力也不过1G，所以，单机挖矿的成功率几乎等于0。比特币挖矿已经从早期的CPU、GPU发展到专用的ASIC芯片构建的矿池挖矿。</p><p>当某个矿工成功找到特定哈希的新区块后，他会立刻向全网广播该区块。其他矿工在收到新区块后，会对新区块进行验证，如果有效，就把它添加到区块链的尾部。同时说明，在本轮工作量证明的竞争中，这个矿工胜出，而其他矿工都失败了。失败的矿工会抛弃自己当前正在计算还没有算完的区块，转而开始计算下一个区块，进行下一轮工作量证明的竞争。</p><p>为什么区块可以安全广播？因为Merkle Hash锁定了该区块的所有交易，而该区块的第一个coinbase交易输出地址是该矿工地址。每个矿工在挖矿时产生的区块数据都是不同的，所以无法窃取别人的工作量。</p><p>比特币总量被限制为约2100万个比特币，初始挖矿奖励为每个区块50个比特币，以后每4年减半。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">共识算法</h3><p>如果两个矿工在同一时间各自找到了有效区块，注意，这两个区块是不同的，因为coinbase交易不同，所以Merkle Hash不同，区块哈希也不同。但它们只要符合难度值，就都是有效的。这个时候，网络上的其他矿工应该接收哪个区块并添加到区块链的末尾呢？答案是，都有可能。</p><p>通常，矿工接收先收到的有效区块，由于P2P网络广播的顺序是不确定的，不同的矿工先收到的区块是有可能的不同的。这个时候，我们说区块发生了分叉：</p><p>在分叉的情况下，有的矿工在绿色的分叉上继续挖矿，有的矿工在蓝色的分叉上继续挖矿：</p><p>但是最终，总有一个分叉首先挖到后续区块，这个时候，由于比特币网络采用最长分叉的共识算法，绿色分叉胜出，蓝色分叉被废弃，整个网络上的所有矿工又会继续在最长的链上继续挖矿。</p><p>由于区块链虽然最终会保持数据一致，但是，一个交易可能被打包到一个后续被孤立的区块中。所以，要确认一个交易被永久记录到区块链中，需要对交易进行确认。如果后续的区块被追加到区块链上，实际上就会对原有的交易进行确认，因为链越长，修改的难度越大。一般来说，经过6个区块确认的交易几乎是不可能被修改的。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">小结</h3><p>比特币挖矿是一种带经济激励的工作量证明机制；</p><p>工作量证明保证了修改区块链需要极高的成本，从而使得区块链的不可篡改特性得到保护；</p><p>比特币的网络安全实际上就是依靠强大的算力保障的。</p>]]></content:encoded>
            <author>0xfanjf@newsletter.paragraph.com (0xfanjf)</author>
        </item>
        <item>
            <title><![CDATA[签名和算法]]></title>
            <link>https://paragraph.com/@0xfanjf/OGXnpHF0VJYUKlUMIJLP</link>
            <guid>OGXnpHF0VJYUKlUMIJLP</guid>
            <pubDate>Wed, 15 Dec 2021 09:15:41 GMT</pubDate>
            <description><![CDATA[签名算法是使用私钥签名，公钥验证的方法，对一个消息的真伪进行确认。如果一个人持有私钥，他就可以使用私钥对任意的消息进行签名，即通过私钥sk对消息message进行签名，得到signature：signature = sign(message, sk); 签名的目的是为了证明，该消息确实是由持有私钥sk的人发出的，任何其他人都可以对签名进行验证。验证方法是，由私钥持有人公开对应的公钥pk，其他人用公钥pk对消息message和签名signature进行验证：isValid = verify(message, signature, pk); 如果验证通过，则可以证明该消息确实是由持有私钥sk的人发出的，并且未经过篡改。 数字签名算法在电子商务、在线支付这些领域有非常重要的作用，因为它能通过密码学理论证明：签名不可伪造，因为私钥只有签名人自己知道，所以其他人无法伪造签名；消息不可篡改，如果原始消息被人篡改了，对签名进行验证将失败；签名不可抵赖，如果对签名进行验证通过了，签名人不能抵赖自己曾经发过这一条消息。简单地说来，数字签名可以防伪造，防篡改，防抵赖。 对消息进行签名，实际上是对消息...]]></description>
            <content:encoded><![CDATA[<p>签名算法是使用私钥签名，公钥验证的方法，对一个消息的真伪进行确认。如果一个人持有私钥，他就可以使用私钥对任意的消息进行签名，即通过私钥<code>sk</code>对消息<code>message</code>进行签名，得到<code>signature</code>：</p><pre data-type="codeBlock" text="signature = sign(message, sk);
"><code><span class="hljs-attr">signature</span> = sign(message, sk)<span class="hljs-comment">;</span>
</code></pre><p>签名的目的是为了证明，该消息确实是由持有私钥<code>sk</code>的人发出的，任何其他人都可以对签名进行验证。验证方法是，由私钥持有人公开对应的公钥<code>pk</code>，其他人用公钥<code>pk</code>对消息<code>message</code>和签名<code>signature</code>进行验证：</p><pre data-type="codeBlock" text="isValid = verify(message, signature, pk);
"><code><span class="hljs-attr">isValid</span> = verify(message, signature, pk)<span class="hljs-comment">;</span>
</code></pre><p>如果验证通过，则可以证明该消息确实是由持有私钥<code>sk</code>的人发出的，并且未经过篡改。</p><p>数字签名算法在电子商务、在线支付这些领域有非常重要的作用，因为它能通过密码学理论证明：</p><ol><li><p>签名不可伪造，因为私钥只有签名人自己知道，所以其他人无法伪造签名；</p></li><li><p>消息不可篡改，如果原始消息被人篡改了，对签名进行验证将失败；</p></li><li><p>签名不可抵赖，如果对签名进行验证通过了，签名人不能抵赖自己曾经发过这一条消息。</p></li></ol><p>简单地说来，数字签名可以防伪造，防篡改，防抵赖。</p><p>对消息进行签名，实际上是对消息的哈希进行签名，这样可以使任意长度的消息在签名前先转换为固定长度的哈希数据。对哈希进行签名相当于保证了原始消息的不可伪造性。</p><p>我们来看看使用ECDSA如何通过私钥对消息进行签名。关键代码是通过<code>sign()</code>方法签名，并获取一个<code>ECSignature</code>对象表示签名：</p><pre data-type="codeBlock" text="const bitcoin = require(&apos;bitcoinjs-lib&apos;);
"><code>const <span class="hljs-attr">bitcoin</span> = require(<span class="hljs-string">'bitcoinjs-lib'</span>)<span class="hljs-comment">;</span>
</code></pre><p>Run</p><p><code>ECSignature</code>对象可序列化为十六进制表示的字符串。</p><p>在获得签名、原始消息和公钥的基础上，可以对签名进行验证。验证签名需要先构造一个<em>不含</em>私钥的<code>ECPair</code>，然后调用<code>verify()</code>方法验证签名：</p><pre data-type="codeBlock" text="const bitcoin = require(&apos;bitcoinjs-lib&apos;);
"><code>const <span class="hljs-attr">bitcoin</span> = require(<span class="hljs-string">'bitcoinjs-lib'</span>)<span class="hljs-comment">;</span>
</code></pre><p>Run</p><p>注意上述代码只引入了公钥，并没有引入私钥。</p><p>修改<code>signAsStr</code>、<code>message</code>和<code>pubKeyAsStr</code>的任意一个变量的任意一个字节，再尝试验证签名，看看是否通过。</p><p>比特币对交易数据进行签名和对消息进行签名的原理是一样的，只是格式更加复杂。对交易签名确保了只有持有私钥的人才能够花费对应地址的资金。</p>]]></content:encoded>
            <author>0xfanjf@newsletter.paragraph.com (0xfanjf)</author>
        </item>
        <item>
            <title><![CDATA[公钥和地址]]></title>
            <link>https://paragraph.com/@0xfanjf/y5XoY8RcZ077sdFdtIGZ</link>
            <guid>y5XoY8RcZ077sdFdtIGZ</guid>
            <pubDate>Wed, 15 Dec 2021 09:14:27 GMT</pubDate>
            <description><![CDATA[比特币的公钥是根据私钥计算出来的。 私钥本质上是一个256位整数，记作k。根据比特币采用的ECDSA算法，可以推导出两个256位整数，记作(x, y)，这两个256位整数即为非压缩格式的公钥。 由于ECC曲线的特点，根据非压缩格式的公钥(x, y)的x实际上也可推算出y，但需要知道y的奇偶性，因此，可以根据(x, y)推算出x&apos;，作为压缩格式的公钥。 压缩格式的公钥实际上只保存x这一个256位整数，但需要根据y的奇偶性在x前面添加02或03前缀，y为偶数时添加02，否则添加03，这样，得到一个1+32=33字节的压缩格式的公钥数据，记作x&apos;。 注意压缩格式的公钥和非压缩格式的公钥是可以互相转换的，但均不可反向推导出私钥。 非压缩格式的公钥目前已很少使用，原因是非压缩格式的公钥签名脚本数据会更长。 我们来看看如何根据私钥推算出公钥：const bitcoin = require('bitcoinjs-lib'); Run 构造出ECPair对象后，即可通过getPublicKeyBuffer()以Buffer对象返回公钥数据。地址要特别注意，比特币的地址并不是公...]]></description>
            <content:encoded><![CDATA[<p>比特币的公钥是根据私钥计算出来的。</p><p>私钥本质上是一个256位整数，记作<code>k</code>。根据比特币采用的ECDSA算法，可以推导出两个256位整数，记作<code>(x, y)</code>，这两个256位整数即为非压缩格式的公钥。</p><p>由于ECC曲线的特点，根据非压缩格式的公钥<code>(x, y)</code>的<code>x</code>实际上也可推算出<code>y</code>，但需要知道<code>y</code>的奇偶性，因此，可以根据<code>(x, y)</code>推算出<code>x&apos;</code>，作为压缩格式的公钥。</p><p>压缩格式的公钥实际上只保存<code>x</code>这一个256位整数，但需要根据<code>y</code>的奇偶性在<code>x</code>前面添加<code>02</code>或<code>03</code>前缀，<code>y</code>为偶数时添加<code>02</code>，否则添加<code>03</code>，这样，得到一个1+32=33字节的压缩格式的公钥数据，记作<code>x&apos;</code>。</p><p>注意压缩格式的公钥和非压缩格式的公钥是可以互相转换的，但均不可反向推导出私钥。</p><p>非压缩格式的公钥目前已很少使用，原因是非压缩格式的公钥签名脚本数据会更长。</p><p>我们来看看如何根据私钥推算出公钥：</p><pre data-type="codeBlock" text="const bitcoin = require(&apos;bitcoinjs-lib&apos;);
"><code>const <span class="hljs-attr">bitcoin</span> = require(<span class="hljs-string">'bitcoinjs-lib'</span>)<span class="hljs-comment">;</span>
</code></pre><p>Run</p><p>构造出<code>ECPair</code>对象后，即可通过<code>getPublicKeyBuffer()</code>以<code>Buffer</code>对象返回公钥数据。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">地址</h3><p>要特别注意，比特币的地址并不是公钥，而是公钥的哈希，即从公钥能推导出地址，但从地址不能反推公钥，因为哈希函数是单向函数。</p><p>以压缩格式的公钥为例，从公钥计算地址的方法是，首先对1+32=33字节的公钥数据进行Hash160（即先计算SHA256，再计算RipeMD160），得到20字节的哈希。然后，添加<code>0x00</code>前缀，得到1+20=21字节数据，再计算4字节校验码，拼在一起，总计得到1+20+4=25字节数据：</p><pre data-type="codeBlock" text="0x00      hash160         check
┌─┬──────────────────────┬─────┐
│1│          20          │  4  │
└─┴──────────────────────┴─────┘
"><code><span class="hljs-number">0x00</span>      hash160         <span class="hljs-keyword">check</span>
┌─┬──────────────────────┬─────┐
│<span class="hljs-number">1</span>│          <span class="hljs-number">20</span>          │  <span class="hljs-number">4</span>  │
└─┴──────────────────────┴─────┘
</code></pre><p>对上述25字节数据进行Base58编码，得到总是以<code>1</code>开头的字符串，该字符串即为比特币地址，整个过程如下：</p><p>使用JavaScript实现公钥到地址的编码如下：</p><pre data-type="codeBlock" text="const bitcoin = require(&apos;bitcoinjs-lib&apos;);
"><code>const <span class="hljs-attr">bitcoin</span> = require(<span class="hljs-string">'bitcoinjs-lib'</span>)<span class="hljs-comment">;</span>
</code></pre><p>Run</p><p>计算地址的时候，不必知道私钥，可以直接从公钥计算地址，即通过<code>ECPair.fromPublicKeyBuffer</code>构造一个不带私钥的<code>ECPair</code>即可计算出地址。</p><p>要注意，对非压缩格式的公钥和压缩格式的公钥进行哈希编码得到的地址，都是以<code>1</code>开头的，因此，从地址本身并无法区分出使用的是压缩格式还是非压缩格式的公钥。</p><p>以<code>1</code>开头的字符串地址即为比特币收款地址，可以安全地公开给任何人。</p><p>仅提供地址并不能让其他人得知公钥。通常来说，公开公钥并没有安全风险。实际上，如果某个地址上有对应的资金，要花费该资金，就需要提供公钥。如果某个地址的资金被花费过至少一次，该地址的公钥实际上就公开了。</p><p>私钥、公钥以及地址的推导关系如下：</p><pre data-type="codeBlock" text="┌───────────┐      ┌───────────┐
│Private Key│─────▶│Public Key │
└───────────┘      └───────────┘
      ▲                  │
      │                  │
      ▼                  ▼
┌───────────┐      ┌───────────┐
│    WIF    │      │  Address  │
└───────────┘      └───────────┘
"><code>┌───────────┐      ┌───────────┐
│<span class="hljs-keyword">Private</span> <span class="hljs-keyword">Key</span>│─────▶│<span class="hljs-keyword">Public</span> <span class="hljs-keyword">Key</span> │
└───────────┘      └───────────┘
      ▲                  │
      │                  │
      ▼                  ▼
┌───────────┐      ┌───────────┐
│    WIF    │      │  Address  │
└───────────┘      └───────────┘
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">小结</h3><p>比特币的公钥是根据私钥由ECDSA算法推算出来的，公钥有压缩和非压缩两种表示方法，可互相转换。</p><p>比特币的地址是公钥哈希的编码，并不是公钥本身，通过公钥可推导出地址。</p><p>通过地址不可推导出公钥，通过公钥不可推导出私钥。</p>]]></content:encoded>
            <author>0xfanjf@newsletter.paragraph.com (0xfanjf)</author>
        </item>
        <item>
            <title><![CDATA[什么是私钥？]]></title>
            <link>https://paragraph.com/@0xfanjf/MkJ7ngbdoTRudjcE4GPM</link>
            <guid>MkJ7ngbdoTRudjcE4GPM</guid>
            <pubDate>Wed, 15 Dec 2021 09:13:37 GMT</pubDate>
            <description><![CDATA[在比特币中，私钥本质上就是一个256位的随机整数。我们以JavaScript为例，演示如何创建比特币私钥。 在JavaScript中，内置的Number类型使用56位表示整数和浮点数，最大可表示的整数最大只有9007199254740991。其他语言如Java一般也仅提供64位的整数类型。要表示一个256位的整数，只能使用数组来模拟。bitcoinjs使用bigi这个库来表示任意大小的整数。 下面的代码演示了通过ECPair创建一个新的私钥后，表示私钥的整数就是字段d，我们把它打印出来：const bitcoin = require('bitcoinjs-lib'); Run 注意：每次运行上述程序，都会生成一个随机的ECPair，即每次生成的私钥都是不同的。 256位的整数通常以十六进制表示，使用toHex(32)我们可以获得一个固定64字符的十六进制字符串。注意每两个十六进制字符表示一个字节，因此，64字符的十六进制字符串表示的是32字节=256位整数。 想要记住一个256位的整数是非常困难的，并且，如果记错了其中某些位，这个记错的整数仍然是一个有效的私钥，因此，比特币有一种...]]></description>
            <content:encoded><![CDATA[<p>在比特币中，私钥本质上就是一个256位的随机整数。我们以JavaScript为例，演示如何创建比特币私钥。</p><p>在JavaScript中，内置的Number类型使用56位表示整数和浮点数，最大可表示的整数最大只有<code>9007199254740991</code>。其他语言如Java一般也仅提供64位的整数类型。要表示一个256位的整数，只能使用数组来模拟。<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/bitcoinjs">bitcoinjs</a>使用<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/cryptocoinjs/bigi">bigi</a>这个库来表示任意大小的整数。</p><p>下面的代码演示了通过<code>ECPair</code>创建一个新的私钥后，表示私钥的整数就是字段<code>d</code>，我们把它打印出来：</p><pre data-type="codeBlock" text="const bitcoin = require(&apos;bitcoinjs-lib&apos;);
"><code>const <span class="hljs-attr">bitcoin</span> = require(<span class="hljs-string">'bitcoinjs-lib'</span>)<span class="hljs-comment">;</span>
</code></pre><p>Run</p><p>注意：每次运行上述程序，都会生成一个随机的<code>ECPair</code>，即每次生成的私钥都是不同的。</p><p>256位的整数通常以十六进制表示，使用<code>toHex(32)</code>我们可以获得一个固定64字符的十六进制字符串。注意每两个十六进制字符表示一个字节，因此，64字符的十六进制字符串表示的是32字节=256位整数。</p><p>想要记住一个256位的整数是非常困难的，并且，如果记错了其中某些位，这个记错的整数仍然是一个<em>有效的私钥</em>，因此，比特币有一种对私钥进行编码的方式，这种编码方式就是带校验的<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://zh.wikipedia.org/wiki/Base58">Base58编码</a>。</p><p>对私钥进行Base58编码有两种方式，一种是非压缩的私钥格式，一种是压缩的私钥格式，它们分别对应非压缩的公钥格式和压缩的公钥格式。</p><p>具体地来说，非压缩的私钥格式是指在32字节的私钥前添加一个<code>0x80</code>字节前缀，得到33字节的数据，对其计算4字节的校验码，附加到最后，一共得到37字节的数据：</p><pre data-type="codeBlock" text="0x80           256bit             check
┌─┬──────────────────────────────┬─────┐
│1│              32              │  4  │
└─┴──────────────────────────────┴─────┘
"><code><span class="hljs-number">0x80</span>           <span class="hljs-number">256</span>bit             <span class="hljs-keyword">check</span>
┌─┬──────────────────────────────┬─────┐
│<span class="hljs-number">1</span>│              <span class="hljs-number">32</span>              │  <span class="hljs-number">4</span>  │
└─┴──────────────────────────────┴─────┘
</code></pre><p>计算校验码非常简单，对其进行两次SHA256，取开头4字节作为校验码。</p><p>对这37字节的数据进行Base58编码，得到总是以<code>5</code>开头的字符串编码，这个字符串就是我们需要非常小心地保存的私钥地址，又称为钱包导入格式：WIF（Wallet Import Format），整个过程如下图所示：</p><p>可以使用<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/bitcoinjs/wif">wif</a>这个库实现WIF编码：</p><pre data-type="codeBlock" text="const wif = require(&apos;wif&apos;);
"><code>const <span class="hljs-attr">wif</span> = require(<span class="hljs-string">'wif'</span>)<span class="hljs-comment">;</span>
</code></pre><p>Run</p><p>另一种压缩格式的私钥编码方式，与非压缩格式不同的是，压缩的私钥格式会在32字节的私钥前后各添加一个<code>0x80</code>字节前缀和<code>0x01</code>字节后缀，共34字节的数据，对其计算4字节的校验码，附加到最后，一共得到38字节的数据：</p><pre data-type="codeBlock" text="0x80           256bit           0x01 check
┌─┬──────────────────────────────┬─┬─────┐
│1│              32              │1│  4  │
└─┴──────────────────────────────┴─┴─────┘
"><code><span class="hljs-number">0x80</span>           <span class="hljs-number">256</span>bit           <span class="hljs-number">0x01</span> <span class="hljs-keyword">check</span>
┌─┬──────────────────────────────┬─┬─────┐
│<span class="hljs-number">1</span>│              <span class="hljs-number">32</span>              │<span class="hljs-number">1</span>│  <span class="hljs-number">4</span>  │
└─┴──────────────────────────────┴─┴─────┘
</code></pre><p>对这38字节的数据进行Base58编码，得到总是以<code>K</code>或<code>L</code>开头的字符串编码，整个过程如下图所示：</p><p>通过代码实现压缩格式的WIF编码如下：</p><pre data-type="codeBlock" text="const wif = require(&apos;wif&apos;);
"><code>const <span class="hljs-attr">wif</span> = require(<span class="hljs-string">'wif'</span>)<span class="hljs-comment">;</span>
</code></pre><p>Run</p><p>目前，非压缩的格式几乎已经不使用了。bitcoinjs提供的<code>ECPair</code>总是使用压缩格式的私钥表示：</p><pre data-type="codeBlock" text="const
    bitcoin = require(&apos;bitcoinjs-lib&apos;),
    BigInteger = require(&apos;bigi&apos;);
"><code>const
    <span class="hljs-attr">bitcoin</span> = require(<span class="hljs-string">'bitcoinjs-lib'</span>),
    <span class="hljs-attr">BigInteger</span> = require(<span class="hljs-string">'bigi'</span>)<span class="hljs-comment">;</span>
</code></pre><p>Run</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">小结</h3><p>比特币的私钥本质上就是一个256位整数，对私钥进行WIF格式编码可以得到一个带校验的字符串。</p><p>使用非压缩格式的WIF是以<code>5</code>开头的字符串。</p><p>使用压缩格式的WIF是以<code>K</code>或<code>L</code>开头的字符串。</p>]]></content:encoded>
            <author>0xfanjf@newsletter.paragraph.com (0xfanjf)</author>
        </item>
        <item>
            <title><![CDATA[区块链原理]]></title>
            <link>https://paragraph.com/@0xfanjf/dHGvjGG5OEUJFHjOTkj1</link>
            <guid>dHGvjGG5OEUJFHjOTkj1</guid>
            <pubDate>Wed, 15 Dec 2021 09:12:38 GMT</pubDate>
            <description><![CDATA[区块链就是一个不断增长的全网总账本，每个完全节点都拥有完整的区块链，并且，节点总是信任最长的区块链，伪造区块链需要拥有超过51%的全网算力。 区块链的一个重要特性就是不可篡改。为什么区块链不可篡改？我们先来看区块链的结构。 区块链是由一个一个区块构成的有序链表，每一个区块都记录了一系列交易，并且，每个区块都指向前一个区块，从而形成一个链条： 如果我们观察某一个区块，就可以看到，每个区块都有一个唯一的哈希标识，被称为区块哈希，同时，区块通过记录上一个区块的哈希来指向上一个区块： 每一个区块还有一个Merkle哈希用来确保该区块的所有交易记录无法被篡改。 区块链中的主要数据就是一系列交易，第一条交易通常是Coinbase交易，也就是矿工的挖矿奖励，后续交易都是用户的交易。 区块链的不可篡改特性是由哈希算法保证的。哈希算法我们来简单介绍一下什么是哈希算法。 哈希算法，又称散列算法，它是一个单向函数，可以把任意长度的输入数据转化为固定长度的输出： h=H(x)h=H(x) 例如，对morning和bitcoin两个输入进行某种哈希运算，得到的结果是固定长度的数字：H("morning")...]]></description>
            <content:encoded><![CDATA[<p>区块链就是一个不断增长的全网总账本，每个完全节点都拥有完整的区块链，并且，节点总是信任最长的区块链，伪造区块链需要拥有超过51%的全网算力。</p><p>区块链的一个重要特性就是不可篡改。为什么区块链不可篡改？我们先来看区块链的结构。</p><p>区块链是由一个一个区块构成的有序链表，每一个区块都记录了一系列交易，并且，每个区块都指向前一个区块，从而形成一个链条：</p><p>如果我们观察某一个区块，就可以看到，每个区块都有一个唯一的哈希标识，被称为区块哈希，同时，区块通过记录上一个区块的哈希来指向上一个区块：</p><p>每一个区块还有一个Merkle哈希用来确保该区块的所有交易记录无法被篡改。</p><p>区块链中的主要数据就是一系列交易，第一条交易通常是Coinbase交易，也就是矿工的挖矿奖励，后续交易都是用户的交易。</p><p>区块链的不可篡改特性是由哈希算法保证的。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">哈希算法</h3><p>我们来简单介绍一下什么是哈希算法。</p><p>哈希算法，又称散列算法，它是一个单向函数，可以把任意长度的输入数据转化为固定长度的输出：</p><p>h=H(x)<em>h</em>=<em>H</em>(<em>x</em>)</p><p>例如，对<code>morning</code>和<code>bitcoin</code>两个输入进行某种哈希运算，得到的结果是固定长度的数字：</p><pre data-type="codeBlock" text="H(&quot;morning&quot;) = c7c3169c21f1d92e9577871831d067c8
H(&quot;bitcoin&quot;) = cd5b1e4947e304476c788cd474fb579a
"><code><span class="hljs-built_in">H</span>("morning") = c7c3169c21f1d92e9577871831d067c8
<span class="hljs-built_in">H</span>("bitcoin") = cd5b1e4947e304476c788cd474fb579a
</code></pre><p>我们通常用十六进制表示哈希输出。</p><p>因为哈希算法是一个单向函数，要设计一个安全的哈希算法，就必须满足：通过输入可以很容易地计算输出，但是，反过来，通过输出无法反推输入，只能暴力穷举。</p><pre data-type="codeBlock" text="H(&quot;???????&quot;) = c7c3169c21f1d92e9577871831d067c8
H(&quot;???????&quot;) = cd5b1e4947e304476c788cd474fb579a
"><code><span class="hljs-built_in">H</span>("???????") = c7c3169c21f1d92e9577871831d067c8
<span class="hljs-built_in">H</span>("???????") = cd5b1e4947e304476c788cd474fb579a
</code></pre><p>想要根据上述结果反推输入，只能由计算机暴力穷举。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">哈希碰撞</h3><p>一个安全的哈希算法还需要满足另一个条件：碰撞率低。</p><p>碰撞是指，如果两个输入数据不同，却恰好计算出了相同的哈希值，那么我们说发生了碰撞：</p><pre data-type="codeBlock" text="H(&quot;data-123456&quot;) = a76b1fb579a02a476c789d9115d4b201
H(&quot;data-ABCDEF&quot;) = a76b1fb579a02a476c789d9115d4b201
"><code><span class="hljs-built_in">H</span>("data-<span class="hljs-number">123456</span>") = a76b1fb579a02a476c789d9115d4b201
<span class="hljs-built_in">H</span>("data-ABCDEF") = a76b1fb579a02a476c789d9115d4b201
</code></pre><p>因为输入数据长度是不固定的，所以输入数据是一个无限大的集合，而输出数据长度是固定的，所以，输出数据是一个有限的集合。把一个无限的集合中的每个元素映射到一个有限的集合，就必然存在某些不同的输入得到了相同的输出。</p><p>哈希碰撞的本质是把无限的集合映射到有限的集合时必然会产生碰撞。我们需要计算的是碰撞的概率。很显然，碰撞的概率和输出的集合大小相关。输出位数越多，输出集合就越大，碰撞率就越低。</p><p>安全哈希算法还需要满足一个条件，就是输出无规律。输入数据任意一个bit（某个字节的某一个二进制位）的改动，会导致输出完全不同，从而让攻击者无法逐步猜测输入，只能依赖暴力穷举来破解：</p><pre data-type="codeBlock" text="H(&quot;hello-1&quot;) = 970db54ab8a93b7173cb48f55e67fd2c
H(&quot;hello-2&quot;) = 8284353b768977f05ac600baad8d3d17
"><code><span class="hljs-built_in">H</span>("hello-<span class="hljs-number">1</span>") = <span class="hljs-number">970</span>db54ab8a93b7173cb48f55e67fd2c
<span class="hljs-built_in">H</span>("hello-<span class="hljs-number">2</span>") = <span class="hljs-number">8284353</span>b768977f05ac600baad8d3d17
</code></pre><p>哈希算法有什么作用？假设我们相信一个安全的哈希算法，那么我们认为，如果两个输入的哈希相同，我们认为两个输入是相同的。</p><p>如果输入的内容就是文件内容，而两个文件的哈希相同，说明文件没有被修改过。当我们从网站上下载一个非常大的文件时，我们如何确定下载到本地的文件和官方网站发布的原始文件是完全相同，没有经过修改的呢？哈希算法就体现出了作用：我们只需要计算下载到本地的文件哈希，再和官方网站给出的哈希对比，如果一致，说明下载文件是正确的，没有经过篡改，如果不一致，则说明下载的文件肯定被篡改过。</p><p>大多数软件的官方下载页面会同时给出该文件的哈希值，以便让用户下载后验证文件是否被篡改：</p><p>和文件类似，如果两份数据的哈希相同，则可以100%肯定，两份数据是相同的。比特币使用哈希算法来保证所有交易不可修改，就是计算并记录交易的哈希，如果交易被篡改，那么哈希验证将无法通过，说明这个区块是无效的。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">常用哈希算法</h3><p>常用的哈希算法以及它们的输出长度如下：</p><p>哈希算法输出长度(bit)输出长度(字节)MD5128 bit16 bytesRipeMD160160 bits20 bytesSHA-1160 bits20 bytesSHA-256256 bits32 bytesSHA-512512 bits64 bytes</p><p>比特币使用的哈希算法有两种：SHA-256和RipeMD160</p><p>SHA-256的理论碰撞概率是：尝试2的130次方的随机输入，有99.8%的概率碰撞。注意2130是一个非常大的数字，大约是1361万亿亿亿亿。以现有的计算机的计算能力，是不可能在短期内破解的。</p><p>比特币使用两种哈希算法，一种是对数据进行两次SHA-256计算，这种算法在比特币协议中通常被称为hash256或者dhash。</p><p>另一种算法是先计算SHA-256，再计算RipeMD160，这种算法在比特币协议中通常被称为hash160。</p><pre data-type="codeBlock" text="const
    bitcoin = require(&apos;bitcoinjs-lib&apos;),
    createHash = require(&apos;create-hash&apos;);
"><code>const
    <span class="hljs-attr">bitcoin</span> = require(<span class="hljs-string">'bitcoinjs-lib'</span>),
    <span class="hljs-attr">createHash</span> = require(<span class="hljs-string">'create-hash'</span>)<span class="hljs-comment">;</span>
</code></pre><p>Run</p><p>运行上述代码，观察对一个字符串进行SHA-256、RipeMD160、hash256和hash160的结果。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">区块链不可篡改特性</h3><p>有了哈希算法的预备知识，我们来看比特币的区块链如何使用哈希算法来防止交易记录被篡改。</p><p>区块本身记录的主要数据就是一系列交易，所以，区块链首先要保证任何交易数据都不可修改。</p><h3 id="h-merkle-hash" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Merkle Hash</h3><p>在区块的头部，有一个Merkle Hash字段，它记录了本区块所有交易的Merkle Hash：</p><p>Merkle Hash是把一系列数据的哈希根据一个简单算法变成一个汇总的哈希。</p><p>假设一个区块有4个交易，我们对每个交易数据做dhash，得到4个哈希值<code>a1</code>，<code>a2</code>，<code>a3</code>和<code>a4</code>：</p><pre data-type="codeBlock" text="a1 = dhash(tx1)
a2 = dhash(tx2)
a3 = dhash(tx3)
a4 = dhash(tx4)
"><code><span class="hljs-attr">a1</span> = dhash(tx1)
<span class="hljs-attr">a2</span> = dhash(tx2)
<span class="hljs-attr">a3</span> = dhash(tx3)
<span class="hljs-attr">a4</span> = dhash(tx4)
</code></pre><p>注意到哈希值也可以看做数据，所以可以把<code>a1</code>和<code>a2</code>拼起来，<code>a3</code>和<code>a4</code>拼起来，再计算出两个哈希值<code>b1</code>和<code>b2</code>：</p><pre data-type="codeBlock" text="       ┌───────────────┐               ┌───────────────┐
       │b1=dhash(a1+a2)│               │b2=dhash(a3+a4)│
       └───────────────┘               └───────────────┘
               ▲                               ▲
       ┌───────┴───────┐               ┌───────┴───────┐
       │               │               │               │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│a1=dhash(tx1)│ │a2=dhash(tx2)│ │a3=dhash(tx3)│ │a4=dhash(tx4)│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
"><code>       ┌───────────────┐               ┌───────────────┐
       │b1=<span class="hljs-built_in">dhash</span>(a1+a2)│               │b2=<span class="hljs-built_in">dhash</span>(a3+a4)│
       └───────────────┘               └───────────────┘
               ▲                               ▲
       ┌───────┴───────┐               ┌───────┴───────┐
       │               │               │               │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│a1=<span class="hljs-built_in">dhash</span>(tx1)│ │a2=<span class="hljs-built_in">dhash</span>(tx2)│ │a3=<span class="hljs-built_in">dhash</span>(tx3)│ │a4=<span class="hljs-built_in">dhash</span>(tx4)│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
</code></pre><p>最后，把<code>b1</code>和<code>b2</code>这两个哈希值拼起来，计算出最终的哈希值，这个哈希就是Merkle Hash：</p><pre data-type="codeBlock" text="                     ┌───────────────────┐
                     │merkle=dhash(b1+b2)│
                     └───────────────────┘
                               ▲
               ┌───────────────┴───────────────┐
               │                               │
       ┌───────────────┐               ┌───────────────┐
       │b1=dhash(a1+a2)│               │b2=dhash(a3+a4)│
       └───────────────┘               └───────────────┘
               ▲                               ▲
       ┌───────┴───────┐               ┌───────┴───────┐
       │               │               │               │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│a1=dhash(tx1)│ │a2=dhash(tx2)│ │a3=dhash(tx3)│ │a4=dhash(tx4)│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
"><code>                     ┌───────────────────┐
                     │merkle=<span class="hljs-built_in">dhash</span>(b1+b2)│
                     └───────────────────┘
                               ▲
               ┌───────────────┴───────────────┐
               │                               │
       ┌───────────────┐               ┌───────────────┐
       │b1=<span class="hljs-built_in">dhash</span>(a1+a2)│               │b2=<span class="hljs-built_in">dhash</span>(a3+a4)│
       └───────────────┘               └───────────────┘
               ▲                               ▲
       ┌───────┴───────┐               ┌───────┴───────┐
       │               │               │               │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│a1=<span class="hljs-built_in">dhash</span>(tx1)│ │a2=<span class="hljs-built_in">dhash</span>(tx2)│ │a3=<span class="hljs-built_in">dhash</span>(tx3)│ │a4=<span class="hljs-built_in">dhash</span>(tx4)│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
</code></pre><p>如果交易的数量不恰好是4个怎么办？例如，只有3个交易时，第一个和第二个交易的哈希<code>a1</code>和<code>a2</code>可以拼起来算出<code>b1</code>，第三个交易只能算出一个哈希<code>a3</code>，这个时候，就把a3直接复制一份，算出<code>b2</code>，这样，我们也能最终计算出Merkle Hash：</p><pre data-type="codeBlock" text="                     ┌───────────────────┐
                     │merkle=dhash(b1+b2)│
                     └───────────────────┘
                               ▲
               ┌───────────────┴───────────────┐
               │                               │
       ┌───────────────┐               ┌───────────────┐
       │b1=dhash(a1+a2)│               │b2=dhash(a3+a3)│
       └───────────────┘               └───────────────┘
               ▲                               ▲
       ┌───────┴───────┐               ┌───────┴───────┐
       │               │               │               │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌ ─ ─ ─ ─ ─ ─ ┐
│a1=dhash(tx1)│ │a2=dhash(tx2)│ │a3=dhash(tx3)│
└─────────────┘ └─────────────┘ └─────────────┘ └ ─ ─ ─ ─ ─ ─ ┘
"><code>                     ┌───────────────────┐
                     │merkle=<span class="hljs-built_in">dhash</span>(b1+b2)│
                     └───────────────────┘
                               ▲
               ┌───────────────┴───────────────┐
               │                               │
       ┌───────────────┐               ┌───────────────┐
       │b1=<span class="hljs-built_in">dhash</span>(a1+a2)│               │b2=<span class="hljs-built_in">dhash</span>(a3+a3)│
       └───────────────┘               └───────────────┘
               ▲                               ▲
       ┌───────┴───────┐               ┌───────┴───────┐
       │               │               │               │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌ ─ ─ ─ ─ ─ ─ ┐
│a1=<span class="hljs-built_in">dhash</span>(tx1)│ │a2=<span class="hljs-built_in">dhash</span>(tx2)│ │a3=<span class="hljs-built_in">dhash</span>(tx3)│
└─────────────┘ └─────────────┘ └─────────────┘ └ ─ ─ ─ ─ ─ ─ ┘
</code></pre><p>如果有5个交易，我们可以看到，<code>a5</code>被复制了一份，以便计算出<code>b3</code>，随后<code>b3</code>也被复制了一份，以便计算出<code>c2</code>。总之，在每一层计算中，如果有单数，就把最后一份数据复制，最后一定能计算出Merkle Hash：</p><pre data-type="codeBlock" text="                  ┌─────────┐
                  │ merkle  │
                  └─────────┘
                       ▲
           ┌───────────┴───────────┐
           │                       │
         ┌───┐                   ┌───┐
         │c1 │                   │c2 │
         └───┘                   └───┘
           ▲                       ▲
     ┌─────┴─────┐           ┌─────┴─────┐
     │           │           │           │
   ┌───┐       ┌───┐       ┌───┐       ┌ ─ ┐
   │b1 │       │b2 │       │b3 │        b3
   └───┘       └───┘       └───┘       └ ─ ┘
     ▲           ▲           ▲
  ┌──┴──┐     ┌──┴──┐     ┌──┴──┐
  │     │     │     │     │     │
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌ ─ ┐
│a1 │ │a2 │ │a3 │ │a4 │ │a5 │  a5
└───┘ └───┘ └───┘ └───┘ └───┘ └ ─ ┘
"><code><span class="hljs-code">                  ┌─────────┐
                  │ merkle  │
                  └─────────┘
                       ▲
           ┌───────────┴───────────┐
           │                       │
         ┌───┐                   ┌───┐
         │c1 │                   │c2 │
         └───┘                   └───┘
           ▲                       ▲
     ┌─────┴─────┐           ┌─────┴─────┐
     │           │           │           │
   ┌───┐       ┌───┐       ┌───┐       ┌ ─ ┐
   │b1 │       │b2 │       │b3 │        b3
   └───┘       └───┘       └───┘       └ ─ ┘
     ▲           ▲           ▲
  ┌──┴──┐     ┌──┴──┐     ┌──┴──┐
  │     │     │     │     │     │
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌ ─ ┐
│a1 │ │a2 │ │a3 │ │a4 │ │a5 │  a5
└───┘ └───┘ └───┘ └───┘ └───┘ └ ─ ┘
</span></code></pre><p>从Merkle Hash的计算方法可以得出结论：修改任意一个交易哪怕一个字节，或者交换两个交易的顺序，都会导致Merkle Hash验证失败，也就会导致这个区块本身是无效的，所以，Merkle Hash记录在区块头部，它的作用就是保证交易记录永远无法修改。</p><h3 id="h-block-hash" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Block Hash</h3><p>区块本身用Block Hash——也就是区块哈希来标识。但是，一个区块自己的区块哈希并没有记录在区块头部，而是通过计算区块头部的哈希得到的：</p><p>区块头部的Prev Hash记录了上一个区块的Block Hash，这样，可以通过Prev Hash追踪到上一个区块。</p><p>由于下一个区块的Prev Hash又会指向当前区块，这样，每个区块的Prev Hash都指向自己的上一个区块，这些区块串起来就形成了区块链。</p><p>区块链的第一个区块（又称创世区块）并没有上一个区块，因此，它的Prev Hash被设置为<code>00000000...000</code>。</p><p>如果一个恶意的攻击者修改了一个区块中的某个交易，那么Merkle Hash验证就不会通过。所以，他只能重新计算Merkle Hash，然后把区块头的Merkle Hash也修改了。这时，我们就会发现，这个区块本身的Block Hash就变了，所以，下一个区块指向它的链接就断掉了。</p><p>由于比特币区块的哈希必须满足一个难度值，因此，攻击者必须先重新计算这个区块的Block Hash，然后，再把后续所有区块全部重新计算并且伪造出来，才能够修改整个区块链。</p><p>在后面的挖矿中，我们会看到，修改一个区块的成本就已经非常非常高了，要修改后续所有区块，这个攻击者必须掌握全网51%以上的算力才行，所以，修改区块链的难度是非常非常大的，并且，由于正常的区块链在不断增长，同样一个区块，修改它的难度会随着时间的推移而不断增加。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">小结</h3><p>区块链依靠安全的哈希算法保证所有区块数据不可更改；</p><p>交易数据依靠Merkle Hash确保无法修改，整个区块依靠Block Hash确保区块无法修改；</p><p>工作量证明机制（挖矿）保证修改区块链的难度非常巨大从而无法实现。</p>]]></content:encoded>
            <author>0xfanjf@newsletter.paragraph.com (0xfanjf)</author>
        </item>
        <item>
            <title><![CDATA[你好啊]]></title>
            <link>https://paragraph.com/@0xfanjf/ZuF4rL6BX8snIXWJolNJ</link>
            <guid>ZuF4rL6BX8snIXWJolNJ</guid>
            <pubDate>Tue, 14 Dec 2021 08:38:58 GMT</pubDate>
            <description><![CDATA[大家好]]></description>
            <content:encoded><![CDATA[<p>大家好</p>]]></content:encoded>
            <author>0xfanjf@newsletter.paragraph.com (0xfanjf)</author>
        </item>
    </channel>
</rss>