【区块链】比特幣錢包和密鑰

admin
admin
管理员
135
文章
0
粉丝
加密货幣百科评论阅读模式

最近發现网上比特幣開發指南已經有了很多翻译版,對比官网上的看明顯快了許多,这里轉一篇對錢包和密鑰的译文。


錢包技術中主要是對BIP32的分層密鑰原理的理解,这是项非常不错的技術。

---------------------------

比特幣錢包涉及到錢包程式或錢包文件。錢包程式創建公鑰来收取satoshis,同时使用相對应的私鑰花掉这些satoshis。錢包文件保存了私鑰,以及為錢包程式保存了一些交易相關的信息(可選)。


錢包程式和錢包文件在下面各个小節会講到,本文档尝試說清楚我们討論的到底是錢包程式了,还是錢包文件。


錢包程式


收取,花掉比特幣是錢包程式最基本的功能 — 但是一个特定的錢包不必包含这两个功能。两个錢包程式能一起协调工作,一个负责分發公鑰,用来收取比特幣,另外一个程式负责簽名交易,来花掉这些比特幣。


為从区块链上獲取信息,广播交易,錢包程式需要跟比特幣p2p網路交互。然而,那些分發公鑰,簽名交易部分的程式是不需要跟跟比特幣P2P網路交互的。


这样我们可以把錢包系統分成三个独立的部分: 一个公鑰分發程式,一个簽名程式,一个網路程式。在下面的小節,我们会講述这几个部分。


注意: 一般的,我谈到分發公鑰时,在很多的情况,分發的是P2PKH或P2SH哈希,而不是公鑰,實際上当他们控制的输出被花掉的时,才分發公鑰。


全服務錢包


最简单的錢包就是包含三个功能:它生成私鑰,然后基于它生成相应的公鑰,在需要时,分發这些公鑰,监控在这些公鑰的输出,創建,簽名交易(用于花掉输出),广播簽名后的交易。



在撰稿本文时,几乎所有主流的錢包被当做全服務錢包使用。


全服務錢包的主要優势在于易用。用户可以用一个程式可以做所有的事情:收發比特幣。


全服務錢包一个最大的缺点就是它把私鑰保存在联网的設備上。这种折中的設備很常見,加上联网,使得很容易把私鑰从一个折中的設備传送到攻擊者。


為防止私鑰被盗,很多錢包程式為用户提供了一个選项:對保存私鑰的錢包文件进行加密。但私鑰不被使用好,可以保護私鑰,避免被盗。但是这無法防止攻擊者有计劃的捕捉到密鑰,或者从記憶體讀到解密后的密鑰。


簽名錢包


為提升安全,在一个更為安全的環境中,私鑰可以在一个独立錢包程式里面生成,保存。这些簽名錢包可以跟網路錢包(可以跟比特幣P2P網路交互)一起协同工作。


簽名錢包程式通常使用确定性密鑰构建方式来創建父私鑰,父公鑰。这些父鑰可以創建子私鑰,子公鑰。

【区块链】比特幣錢包和密鑰-图片1


当第一次運行簽名錢包,它会創建一个父私鑰,并把相应的父公鑰传送到網路錢包。


網路錢包使用父公鑰来派生出子公鑰,并按需分發子公鑰,监控在这些公鑰上的输出,創建花掉这些输出的非簽名交易,然后發送非簽名交易到簽名錢包。


在使用簽名錢包时,用户常常有机会去审核这些非簽名交易的详情(特别是输出详情)。


在审核交易后(这步是可選的),簽名錢包使用父私鑰派生出一个相应的子私鑰,同时簽名这些非簽名交易,并發送这些簽名交易到網路錢包。


然后網路錢包广播这些簽名交易到P2P網路。


下面的小節將会講述最常用的簽名錢包: 离线錢包,硬體錢包。


离线錢包


好几个全服務錢包程式也可以当做两个独立的錢包: 一个程式当做簽名錢包(通常叫做:离线錢包),同时另外一个程式当作網路錢包(通常叫做:线上錢包 或者 觀察錢包)


离线錢包因它有意運行在一个不联网的設備上而得名,这会极大減少攻擊。在这种情况,透過类似USB这样的媒介来轉移資料,通常取决于用户自己。 用户的工作流程如下:


1. (离线) 關闭設備上的所有網路連接,装上錢包軟體。 在离线模式下,启动錢包軟體,創建一个父私鑰,父公鑰,复制父公鑰到可以插拔的媒介上。


2. (在线)在另外一个設備上装上錢包,这錢包要連上互联网,同时从可插拔媒介,導入父公鑰。跟你使用去錢包一样,透過分發公鑰来收款。当准備花比特幣时,准備好输出,并保存生成好的非簽名交易到可插拔媒介。


3. (离线)在离线錢包中,打開非簽名交易,审核输出详情,确保要支付的數量,地址是正确無誤的。这可以防止在线錢包中的恶意軟體欺骗用户在支付给攻擊者上的交易簽名。审核后,簽名交易并保存交易到可插拔媒介。


4. (在线)在在线錢包中,打開簽名交易。现在可以把它广播到P2P網路中。


离线錢包主要的優点是:相比于全服務錢包,他们极大的提升了錢包的安全性。只要离线錢包没有丢失或者有缺陷,并且在簽名前,用户审核所有交易,用户的satoshis是安全的,即使在线錢包泄露了。


离线錢包的主要的缺点在于使用起来太繁琐。為了最大的安全,要求用户用一个設備專用于离线任務。每当需要發送比特幣时,离线設備必须要启动,同时用户必须透過手动把資料在在线設備,离线設備之间来回复制。


硬體錢包


硬體錢包專門用来運行簽名錢包。它们消除了簽名錢包在目前作業係統上很多缺陷,它们允許錢包可以跟其他硬體安全的通信,因此用户没有必要手动的在設備之间传送資料。硬體錢包用户的使用流程如下:


1. (硬體錢包) 創建父私鑰,父公鑰。把硬體錢包連接到一个網路錢包設備,这样它就可以獲取到公鑰。


2. (網路錢包) 同使用全服務錢包一样,分發公鑰来收款。当准備花掉satoshis时,填充好交易細節,并連接到硬體錢包,然后点擊支付。網路錢包会自动把交易細節發送给硬體錢包。


3. (硬體錢包)在硬體錢包熒屏上审核交易細節。一些硬體錢包可能会弹出一个密碼或PIN输入框。硬體錢包簽名交易,然后把它發送给網路錢包。


4. (網路錢包)網路錢包从硬體錢包收到簽名交易后,把它广播到P2P網路。


相對于全服務錢包,硬體錢包的主要優点是极大的提升了安全,同时比起离线錢包又简单很多。


硬體錢包主要的缺点是使用起来繁琐。即使比起离线錢包来没那么繁琐,但是用户仍然需要購买一个硬體錢包,并且每当需要给交易簽名时,需随身携帶。


一个其他的缺点(希望是暂时的),在撰稿本文时,很少有主流的錢包程式支持硬體錢包 — 虽然很多主流的錢包已經發公告說他们有意向支持至少一种硬體錢包。


分發錢包


那些運行在很难保證安全的環境,比如web伺服器,的錢包的功能可以設计成仅仅分發公鑰(包括P2PKH, P2SH地址)。有两个通用的方式来設计这些极简的錢包。


【区块链】比特幣錢包和密鑰-图片2


先预准備一些公鑰或地址在一个資料库里面,然后使用資料库中的条目,按需分發公鑰脚本或者地址。為避免重用,web伺服器跟踪密鑰使用情况,永遠不要用完里面的公鑰。可以透過父公鑰很容易做到这点,这会在下面方面提到。


使用父公鑰生成子公鑰。為避免重用,需要使用一个方法保證一个公鑰不能被分發两次。这可以是一个資料库的条目對应一个密鑰分發,或透過自增指针指向密鑰的索引号。


这两个方法没有增加太多额外的工作量,特别是在使用一个資料库来,透過把每个进来的支付跟独立的公鑰绑定起来跟踪时,其工作量就更少。 具体情况支付處理章節。


錢包文件


比特幣的核心其實就是一堆私鑰集合。这些私鑰以數字内容的形式存在文件里面,或甚至可以存在紙上。


私鑰格式


私鑰用来解鎖在一个比特幣地址上的satoshis。在比特幣中,一个私鑰的标准格式是一个简单的256-bit 數字,值的范围是: 0x01 ~ 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4140, 所代表的值的范围在2^256 - 1. 这范围由比特幣中使用的secp256k1 ECDSA加密标准来控制。


錢包導入格式(WIF)


為使复制密鑰少出错,可以使用錢包導入格式。WIF使用base58Check 来编碼私鑰,可以极大的降低复制引起的错誤,这看起来很像比特幣地址。


  1. 拿到一个私鑰。

  2. 用于主网地址时,在私鑰前面加上0x80,用于测試网地址时,在前面加入0xef。

  3. 如果跟压缩公鑰(后续小節会講到)使用,需要在后面加入0x01,如果跟非压缩公鑰使用,就什么都不用加。

  4. 對擴展后的私鑰进行SHA-256哈希。

  5. 對SHA-256哈希后結果再做一个SHA-256哈希。

  6. 拿掉第二次哈希后的哈希值的前4字節,这4个字節是校验和。

  7. 把这4字節校验和插入到擴展私鑰后面。

  8. 把結果从字節字符串,使用Base58Check轉化成Base58编碼字符串。


使用Base58解碼功能,很容易逆向操作这个过程,删掉那些填充。


-----------------------

由于比較难以理解,这里在网上找了个例子:


1. 随机生成一个256bit的數,用16进制表示如下 

0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D 


2. 在1結果前面增加網路标示,0x80表示mainnet網路,0xef表示测試網路(testnet) 

800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D 


3. 如果使用压缩公鑰,在2的結果后面增加0x01;若使用非压缩公鑰,则不追加0x01 

(本次不使用压缩公鑰) 

800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D 


4. 對3的結果执行SHA-56hash算法 

8147786C4D15106333BF278D71DADAF1079EF2D2440A4DDE37D747DED5403592 


5. 對4的結果再次执行SHA-256 hash算法 

507A5B8DFED0FC6FE8801743720CEDEC06AA5C6FCA72B07C49964492FB98A714 


6. 取5結果的前4个字節,作為校验和 

507A5B8D 


7. 將6的結果追加到3的后面 

800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D 


8. 對6的結果执行Base58Check 编碼算法,得到WIF格式的私鑰 

5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ 


由上面的步驟可以看到,WIF格式的私鑰很容易轉换成256位的符合ECDSA規范的私鑰;而且很容易校验WIF格式的私鑰是否合法。

-------------------

下面繼续指南的内容。


Mini私鑰格式


Mini私鑰格式透過小于30个字符来编碼私鑰的一种方法,可以把密鑰嵌入到非常小的物理空间上,比如一个物理的比特幣,抗破坏的QR二维碼。


  1. 第一个字母是: S

  2. 為判斷一个mini私鑰是否格式化良好,在私鑰中加入了一个問题标志。

  3. 计算私鑰的哈希值。如果哈希值第一个字節是00, 那它就是格式化良好的Mini私鑰。这个密鑰的限制起到校验和的作用。用户可以透過使用随机數字暴力處理,来生成一个格式化良好的mini私鑰。

  4. 為了从完整的私鑰派生,用户可以简单的獲取一个源mini私鑰的SHA256哈希。这是个单向處理:从派生的密鑰去计算出mini私鑰是很困难的。


在很多的Mini私鑰的實现中,禁止在mini私鑰里使用字符 "1",因為它跟字符 “I” 很像。


資源:Casascius 比特幣地址工具: 一个創建,兑换这些密鑰的工具.


公鑰格式


在secp256k1中,比特幣 ECDSA 公鑰代表一条特定椭圆曲线(EC)上的一个点。在传統的非压缩格式,公鑰包含一个标識字節,一个32字節x坐标,一个32字節y坐标。下面的一个简化版插圖顯示了在y2 = x3 + 7 椭圆曲线上的一个点。

【区块链】比特幣錢包和密鑰-图片3


(Secp256k1實際透過一个大的素數對坐标进行取模,这会產生一个非連续整數域, 即使是使用同样的原理,也会導致一个非常不清晰的坐标。)


在不改變任何基本原则,丢弃Y坐标,可以實现公鑰大小減半效果。这是有可能的,因為两个在曲线上的点,共用一个X坐标,因此32字節Y坐标能够用一个bit标識替代,用来表示这个点是在曲线上部分,还是在下部分。


采用这样方法压缩,不会導致資料丢失 — 这仅需要一点点cpu来重建Y坐标,以及訪問非压缩公鑰。在secp256k1文档中,所描述的非压缩、压缩密鑰,在广泛使用的OpenSSL库中,是默认支持的。


因為他们很容易使用,因為比特幣内核,默认会存儲每个输出,加密公鑰,以及其他所有比特幣軟體也如此,為存儲这些公鑰,这种方式几乎能為区块链減少一半空间需求。


然而,比特幣内核0.6之前的版本,使用的是非压缩密鑰。这就帶来一定的复雜,因為非压缩公鑰的哈希跟压缩公鑰的哈希的格式是不一样的,因此同一密鑰会有两个不同的P2PKH,P2SH地址。这也就意味着密鑰在簽名脚本中,必须以正确的格式提交,这样它才能跟之前输出的公鑰脚本匹配上。


因為这个原因,比特幣内核使用几个不斷的标識字節来帮助程式辨識怎么使用密鑰:


  • 使用压缩公鑰的密鑰,在他们进行Base58编碼前,需在其他后面加上0x01

  • 非压缩公鑰以0x04開頭,压缩公鑰以0x03或0x02開頭,至于是0x03,还是0x02,取决于曲线上点在Y轴位置是大于中点,还是小于中点。 这些前缀字節在官方的secp256k1文档中都有被用到。


層級确定性密鑰創建


層級确定密鑰創建和传输协議(HD 协議)极大的简化了錢包備份,減少在同一錢包中,多个程式重复通信,允許創建可独立操作的子账户,每个父账户可以监控,控制它的子账户,即便子账户泄露了,同时可以把账号分成可完全訪問,限制訪問部分,这样不可信用户或程式可以接收或者监控支付情况,但没有不能花里面的錢。


HD协議利用ECDSA公鑰的創建函數 point(), 它采用一个非常大的整數(私鑰),把整數轉化成曲线上的一个点(公鑰)。


point(private_key) == public_key


因為point()内部工作方式,透過组合一个已有的(父)公鑰,另外一个透過任何 整型(i)數值生成的公鑰,可以創建一个子公鑰。 这个子公鑰跟公鑰一样,可以透過point()函數,在源(父)私鑰上加上一个整型數i,然后父公鑰跟 i 相加,再除以一个在比特幣軟體中的全局的常量,求余數, 来創建。


point( (parent_private_key + i) % p ) == parent_public_key + point(i)


这就意味着,两个或多个独立的程式,在不需要更多的通信的情况下,同一串數字能从单个父密鑰對創建一系列独一無二的子密鑰對。更重要的是,為接受付款而分發这些公鑰的程式的确再不需要訪問私鑰,这使公鑰分發程式可以運行在可能不安全的平台上,比如公共的web伺服器。


子公鑰也能透過重复子密鑰派生操作,来創建他们自己的子公鑰(孙公鑰):


point( (child_public_key + i ) % p) == child_public_key + point(i)


是否要創建子公鑰或者下一代公鑰,用一串可预测的整型數創建并不比所有交易使用单个公鑰来更好。因為任何人如果他知道一个子公鑰,他就可以找出所有由其父公鑰生成的其他所有子公鑰。相反,随机种子能够用来生成一串确定的整型數字,这样在没有种子的情况下,子公鑰之间的關系是不可見的。


HD协議使用单个根种子来創建一个層級的子,孙子,其他后代密鑰。他们是一些無相關性的,具有确定生成關系的整數。每个子密鑰也可以从它的父密鑰獲得一个确定性的种子,叫做链碼,所以一个链碼泄露不会導致整个層級里的整數泄露,这就使主链碼就可以繼续使用,即使,比如 一个基于web的公鑰分發程式被黑掉。


【区块链】比特幣錢包和密鑰-图片4


如上圖所示,HD密鑰派生需要4个输入:


  • 父私鑰,父公鑰,他们都是非压缩256位 ECDSA密鑰。

  • 父链碼 - 256位看似随机的數。

  • 索引号 - 程式指定的32位的整數


如上圖所示,单向加密哈希算法(HMAC-SHA512)把父链碼,父公鑰,索引号进行加密,生成一个确定派生的但看起来有点随机的512bit的資料。 看似随机的256位值右侧部分算出的哈希值被用来当做一个新的子链碼。左侧部分算出的哈希值被当做一个整數,跟父私鑰或父公鑰组合一起,生成相应的子私鑰或子公鑰。


child_private_key == (parent_private_key + lefthand_hash_output) % G 

child_public_key == point( (parent_private_key + lefthand_hash_output) % G )

child_public_key == point(child_private_key) == parent_public_key + point(lefthand_hahs_output)


對于同一父密鑰,指定不同的索引數字会創建不同的,無關联的子密鑰。使用子链碼對子密鑰重复这个过程会創建無關的孙密鑰。


因為創建子密鑰需要一个密鑰和一个链碼,他们两合起来叫做擴展密鑰。一个擴展私鑰跟它相应的擴展公鑰有同一个链碼。主私鑰(跟私鑰)和主链碼是从随机資料派生而来,如下圖所示:


【区块链】比特幣錢包和密鑰-图片5


根种子透過128或256或512位随机數創建。為了能让使用特定設置的特定錢包程式派生每个密鑰,用户只需要存儲根种子即可。


警告: 在撰寫本文时,HD錢包程式不能达到完全兼容,因此對于一个特定的根种子,用户必须使用同一个HD錢包程式,并使用同样的HD相關設置。


根种子透過哈希之后生成看似随机的512比特位資料,主密鑰,主链碼透過这512比特的資料来生成。主公鑰使用point()从主私鑰派生,主公鑰和主链碼一起,透過point()来生成主擴展公鑰。主擴展密鑰在功能上跟其他擴展密鑰是一样的;它只是因為在在層級的最頂部而已。


强化密鑰


强化擴展密鑰解决了普通擴展密鑰的一个潛在問题。如果一个攻擊者獲取一个普通父链碼,一个父公鑰,他可以暴力破解从它生成的所有子链碼。如果攻擊者也能獲得一个子/孙子/其他后代的私鑰,他就可以使用链碼来生成所有从这私鑰派生来的所有的擴展私鑰。 如下面孙子,曾孙子插圖中所示:


【区块链】比特幣錢包和密鑰-图片6


更糟糕情况,攻擊者能逆向普通私鑰派生公式,并利用一个子私鑰減去父链碼从而恢复父私鑰,就如上圖子代跟父代之间所顯示的那样。 这就意味着攻擊者拿到一个擴展公鑰,以及它的一个后代密鑰后,他就能恢复父私鑰,以及它所有的后代密鑰。


因為这个原因,相比較于标准公鑰而言,擴展公鑰的链碼部分需要更好的安全保護措施。并建議用户不要把非擴展私鑰導出到可能不可信,不安全的環境。


这可以透過一个折中的方案来解决,透過把普通密鑰派生公式替换成强化密鑰派生公式。


在上節講到的普通密鑰派生公式,是透過把索引号,父链碼,父公鑰组合起来,来創建一个子链碼,同时把一个整數跟父私鑰组合起来,来創建一个子密鑰。


【区块链】比特幣錢包和密鑰-图片7


如上圖所示,强化公式把索引号,父链碼,父私鑰组合起来創建一个用于生成子链碼,子私鑰的資料。这个公式使得在無需知道父密鑰的情况下,創建子公鑰成為可能。 换句话說,父擴展公鑰不能創建强化子公鑰。


因為这原因,强化擴展私鑰比起普通的擴展私鑰用處更少。然而,强化擴展公鑰創建了一个防火前,使得多層級派密鑰泄露不会發生。因為强化子擴展公鑰不能自己生成孙链碼,那么使用泄露的父擴展公鑰,泄露的孙子私鑰是不能創建曾孙子擴展私鑰的。


HD协議使用一个索引号来指示是生成普通的,还是强化的密鑰。范围在0x00 ~ 0x7fffffff(0 到  2^31 - 1)的索引号生成一个普通的密鑰,范围在0x80000000 ~ 0xffffffff(0 到  2^31 - 1)的索引号生成一个强化的密鑰。為方便描述,很多開發者使用撇符来区分强化密鑰,因此第一个普通密鑰(0x00)是 0, 第一个强化密鑰(0x80000000)是 0'。


(比特幣開發者通常使用ASCII编碼的撇号而不是UNICODE编碼的撇号,因此我们也这样使用)。


更為简洁的描述方式: 在/符号前面,加一个m 或者M前缀,来表示層級,以及密鑰类型。 m表示私鑰,M表示公鑰。比如, m/0‘/0/122' 表示主私鑰第一个强化子私鑰的第一个普通私鑰的第123个子私鑰。下面的層級展示了撇会注釋跟强化密鑰防火墙:


【区块链】比特幣錢包和密鑰-图片8


遵循BIP32 HD协議的錢包仅能創建主私鑰的强化后代密鑰,以防止子密鑰泄露導致主密鑰泄露。 因為主密鑰没有普通的子密鑰,在HD錢包中也没有用到主公鑰。其他所有的密鑰可以普通的子密鑰,因此也可以使用相应的擴展公鑰来替代普通的子公鑰。


HD协議中介绍了擴展公鑰,擴展私鑰序列化格式。更多详情,请看開發者参考中錢包章節或者 在BIP32了解HD协議详情。


存儲根种子


在HD协議中,根种子是一些128位,258位,512位比特随机資料。必须要精确的保存好他们。為了更方便使用非數字方式保存他们,比如記忆,手寫,BIP39 定義了一个方法。该方法可以透過由普通单词偽造出的句子(帮组記忆的)来生成512位根种子。 这句子透過128或256位的熵生成的,并且可以使用密碼来保護这些组記词。


单词个數跟消耗熵數量之间的關系如下:


【区块链】比特幣錢包和密鑰-图片9

密碼长度没有限制。它只是简单的加到组記词的后面,然后使用HMAC-SHA512對他们进行2048次哈希计算,獲得一个看似随机的512比特位种子。因為任何输入到这个哈希函數的資料,都会生成一个看似随机的512比特位的种子,因為没有一个基本的方法證明用户输入了正确的密碼,这样即使用户在强迫下,也有可能保護好种子。


想要了解更多详情,请查看BIP39.


松散密鑰錢包


松散密鑰錢包也叫密鑰串(JBOK), 这种形式的錢包源自于比特幣内核客户端,目前已被遗弃不用。比特幣内核客户端錢包会透過偽随机數生成器(PRNG)創建100个私鑰/公鑰對,以備后用。


这些私鑰儲存在一个虚擬的“密鑰池”,当之前的密鑰被使用后,它会生成新的密鑰對,这样确保密鑰池维持有100个未被使用的密鑰。(如果錢包被加密,新的密鑰只有在被解鎖后才可以生成新的密鑰)


考慮到不得不手动備份新生成的私鑰,这会對保存密鑰帶来相對大的困难。如果新的密鑰生成后,被用掉,但在備份前丢失了,那么里面的比特幣就永遠丢失了。很多老式的移动錢包则使用一个简单的格式,它们仅当用户需要的时候,才生成一个新的私鑰。


因為備份太繁琐,这类錢包正逐步被淘汰,也不鼓勵大家使用。





文章末尾固定信息

我的微信
这是我的微信掃一掃
weinxin
我的微信
微訊號已复制
我的微信公眾号
我的微信公眾号掃一掃
weinxin
我的公眾号
公眾号已复制
 
admin
  • 本文由 admin 发表于2024 年 11 月 9 日 07:15:56
  • 轉载请務必保留本文链接:https://zuizhiyou.com/%e3%80%90%e5%8c%ba%e5%9d%97%e9%93%be%e3%80%91%e6%af%94%e7%89%b9%e5%b8%81%e9%92%b1%e5%8c%85%e5%92%8c%e5%af%86%e9%92%a5.html
匿名

发表评论

匿名网友
:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:
确定

拖动滑块以完成验证