05-02 Bitcoin钱包Schnorr地址生成与签名

1.Schnorr签名

1.1 签名流程

密钥生成(Key Generation)

  • 私钥 𝑘k 是一个随机选择的整数,范围为 [1,𝑛−1],其中 𝑛 是椭圆曲线的阶。,
  • 公钥 𝑃P 是通过将私钥乘以椭圆曲线的基点 𝐺G 得到的,即 𝑃=𝑘⋅𝐺。

签名生成(Signature Generation)

假设消息为 𝑚。

  • 生成随机数 𝑟:选择一个随机数 𝑟r 作为临时私钥,范围为 [1,𝑛−1]。
  • 计算非交互式挑战:计算 𝑅=𝑟⋅𝐺,其中 𝐺 是椭圆曲线的基点。
  • 计算哈希值:计算哈希值 𝑒:

𝑒=H(𝑅∥𝑃∥𝑚)

  • 其中 H 是一个哈希函数,如 SHA-256,𝑅∥𝑃∥𝑚 表示 𝑅、公钥 𝑃 和消息 𝑚 的连接。
  • 计算签名:计算签名 𝑠:

𝑠=𝑟+𝑒⋅𝑘(mod𝑛)

  • 其中 𝑘 是私钥,𝑒 是哈希值,𝑛n是椭圆曲线的阶。

签名由 (𝑅,𝑠)组成。

签名验证(Signature Verification)

  • 计算哈希值:重新计算哈希值 𝑒:

𝑒=H(𝑅∥𝑃∥𝑚)

  • 验证等式:验证以下等式是否成立:

𝑠⋅𝐺=𝑅+𝑒⋅𝑃

  • 如果等式成立,签名是有效的;否则无效

看不懂没关系,我也看不懂,有个概念知道是这三个步骤即可

1.2 签名特点

简洁性

  • Schnorr签名算法结构简单,签名过程和验证过程都较为直观。
  • 这种简洁性有助于实现和分析安全性

安全性

  • 不可伪造:基于离散对数问题的困难性,Schnorr签名在当前已知的计算能力下被认为是安全的
  • 欧式证明:有严格的欧式安全性证明。

高效性

  • 计算效率:签名生成和验证的计算效率较高。尤其在批量签名验证中,Schnorr 签名具有显著的性能优势。
  • 签名长度:Schnorr 签名的长度较短,相比于 ECDSA 签名,Schnorr 签名占用的存储空间更少。因为签名越短—–>所以花费的gas fees就越少,

扩展性和兼容性

Schnorr 签名支持多种扩展功能,如多重签名(MuSig)和聚合签名。这些扩展功能在增强隐私性和可扩展性方面表现出色。

  • 多重签名(MuSig):Schnorr 签名允许多个签名者的公钥和签名聚合为单一的公钥和签名,这显著提高了多重签名的效率和隐私性。
  • 兼容性:Schnorr 签名与现有的椭圆曲线加密标准兼容,便于在现有系统中实现和部署。

1.3 签名算法Python代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# schnorr.py
import hashlib
from ecdsa import SECP256k1, SigningKey, VerifyingKey
from ecdsa.curves import Curve


class SchnorrSignObj:
curve: Curve
def __init__(self):
# 椭圆曲线参数
self.curve = SECP256k1 # curve就是椭圆曲线对象,SECP256k1就是常见的椭圆曲线

def schnorr_sign(self, private_key, message): # 签名函数,传私钥和msg
# 生成私钥和公钥
signing_key = SigningKey.from_string(private_key, curve=self.curve) # 使用私钥和椭圆曲线对象生成一个签名对象
verifying_key = signing_key.get_verifying_key() # 从签名秘钥对象获得公钥,verifying_key就是公钥,返回一个公钥key可以用来验证签名

# 生成随机数 r
r = SigningKey.generate(curve=self.curve).privkey.secret_multiplier # 生成随机数作为签名的一部分
R = VerifyingKey.from_public_point(r * self.curve.generator, curve=self.curve) # 使用随机数r乘以椭圆曲线的生成元,得到签名中的挑战点R。

# 计算哈希 e
e = hashlib.sha256(R.to_string() + verifying_key.to_string() + message).digest() # 这个之前学过,sha256加密返回哈希计算结果的二进制表示,传入挑战点R+公钥+msg
e = int.from_bytes(e, 'big') # 将字节对象转成大整数,big表示大端字节数

# 计算签名 s
s = (r + e * signing_key.privkey.secret_multiplier) % self.curve.order # 计算签名中的s,根据Schnorr签名算法的计算公式,将随机数r和哈希e与私钥的倍数相加,并对椭圆曲线的阶数取模。
return R.to_string(), s.to_bytes(32, 'big') # 返回挑战点 R 的字符串,和签名s的32字节大端字节串表示。

def schnorr_verify(self, public_key, message, signature): # 签名验证,传入公钥,msg, 签名signature
# 解析签名
R = VerifyingKey.from_string(signature[0], curve=self.curve) # 从签名中解析挑战点 R ,传入对应的椭圆曲线对象
s = int.from_bytes(signature[1], 'big') # 从签名消息中解析签名 s大端字节串表示

# 计算哈希 e
e = hashlib.sha256(signature[0] + public_key.to_string() + message).digest() # sha256加密返回哈希计算结果的二进制表示,传入签名[0] + 公钥 + msg
e = int.from_bytes(e, 'big') # 将字节对象转成大整数,big表示大端字节数

# 验证签名
sG = VerifyingKey.from_public_point(s * self.curve.generator, curve=self.curve) # 从公钥获取指针(初始化验证公钥对象),传入将生成元乘以 s 值,得到一个新的点。self.curve:表示使用相同的椭圆曲线对象进行计算。
# R:是挑战点,它是一个椭圆曲线上的点,R.pubkey.point:表示挑战点 R 的公钥点。
# e:是哈希值,它是一个整数public_key.pubkey.point:表示公钥的点,
# public_key.pubkey.point:表示公钥的点。
ReP = VerifyingKey.from_public_point(R.pubkey.point + e * public_key.pubkey.point, curve=self.curve)
return sG.to_string() == ReP.to_string() # 验证是否通过

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# test_schnorr.py
import schnorr as schnorr
# from schnorr import SchnorrSignObj
from ecdsa import SECP256k1, SigningKey, VerifyingKey

# 初始化类
schnorr_test = schnorr.SchnorrSignObj()

# 产生密钥并对交易签名
private_key = SigningKey.generate(curve=SECP256k1).to_string()
message = b"Hello, Schnorr!2222"
signature = schnorr_test.schnorr_sign(private_key, message)
print("Signature:", signature)# 打印的是字节

# 验证交易签名
public_key = VerifyingKey.from_string(
SigningKey.from_string(private_key, curve=SECP256k1).get_verifying_key().to_string(), curve=SECP256k1)
is_valid = schnorr_test.schnorr_verify(public_key, message, signature)

print("Signature valid:", is_valid) # Signature valid Ture

2. BIP86 协议

2.1 结构路径 BIP86 是一种专门为生成 Taproot 地址的路径标准,简化并优化了生成单密钥 Taproot 地址的过程。BIP86 的路径结构如下:

1
m / 86' / coin_type' / account' / change / address_index
  • Purpose: 固定为 86’,表示遵循 BIP86 标准。
  • Coin Type: 定义特定加密货币的类型,如 0’ 表示比特币
  • Account: 账户索引,允许钱包管理多个独立账户。
  • Change: 0 表示外部链(收款地址),1 表示内部链(找零地址)。
  • Address Index: 用于生成具体地址的索引

2.2.地址生成方法

BIP86 使用新的路径结构生成 Taproot 地址,通过椭圆曲线算法派生公钥,并将其编码为 Bech32 格式的地址。生成的 Taproot 地址具有更高的隐私性、安全性和扩展性。

2.3.BIP86 的优势

  • 简洁性:BIP86 提供了一种简洁而直观的路径结构,用于生成 Taproot 地址。
  • 安全性:BIP86 使用椭圆曲线算法生成公钥,具有与比特币的现有地址生成方法相当的安全性。
  • 可扩展性:BIP86 支持不同的币种和账户,具有良好的可扩展性。

3. BIP44 与 BIP86 的区别与联系

3.1.用途上的区别

  • BIP44: 通用的多币种、多账户管理,适用于生成多种类型的地址(P2PKH, P2SH, P2WPKH)。
  • BIP86: 专门为生成单密钥 Taproot 地址设计,简化了路径和使用场景。

3.2. 路径结构区别

  • BIP44: m/44’/coin_type’/account’/change/address_index,包含多币种、多账户和多地址类型
  • BIP86: m/86’/coin_type’/account’/change/address_index,专注于单密钥 Taproot 地址生成。

3.3.复杂性区别

  • BIP44: 适用于更复杂的多币种和多账户需求,路径层次更丰富。
  • BIP86: 专注于单一用途(Taproot 地址),路径层次简化

3.4.标准化区别

  • BIP44: 广泛应用于各种钱包和加密货币管理系统,已经成为生成确定性钱包路径的标准。
  • BIP86: 主要用于比特币的 Taproot 地址生成,随着 Taproot 的采用而变得更加流行。

3.5.总结

  • BIP44 适用于多种类型地址生成,适合更复杂的钱包管理需求。
  • BIP86 专注于生成比特币的 Taproot 地址,路径简化专门为 Taproot 设计。

还有BIP84协议,协议很多

4.Taproot 为什么需要 BIP86

4.1. 专注于单密钥 Taproot 地址 :BIP86 专门设计用于生成单密钥 Taproot 地址。Taproot 是比特币的一项重要升级,旨在提高隐私性可扩展性和智能合约功能。

4.2.符合新标准和优化: BIP86 引入了一些新的优化和改进,以支持比特币协议的新特性,如 Taproot 和 Schnorr 签名。Taproot 和 Schnorr 签名提供了更高的安全性和更好的隐私特性。BIP86 的设计是为了最大限度地利用这些新特性。

4.3.专注于比特币和未来扩展

BIP44 是一个通用的多币种和多账户路径标准,设计用于管理各种加密货币的地址。这种通用性虽然提供了很大的灵活性,但在特定的优化和新特性支持上可能不如专门设计的标准有效。BIP86 专门为比特币设计,更好地支持比特币的未来扩展和改进。

4.4.简化路径和提高效率

BIP86 路径简化,专注于单一用途,使其更适合于生成和管理比特币的 Taproot 地址。这种简化可以减少实现和使用中的复杂性,提高效率和安全性。

4.5.避免混淆和错误

BIP86 的专用路径减少了使用过程中可能出现的混淆和错误。由于它专门设计用于比特币的 Taproot 地址,开发者和用户不必在多个标准之间切换,减少了出错的可能性。

4.6.代码比较

BIP44 代码示范

1
2
3
4
5
6
7
8
9
10
11
12
13
def create_address(mnemonic):
seed = bip39.phrase_to_seed(mnemonic)
bip32_root_key = BIP32Key.fromEntropy(seed)
path = "m/44'/0'/0'/0/0"
key = bip32_root_key
for level in path.split('/')[1:]:
if level.endswith("'"):
index = BIP32_HARDEN + int(level[:-1])
else:
index = int(level)
key = key.ChildKey(index)
address = key.Address()
return address

BIP86 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import bip39
import bech32
from bip32 import BIP32, HARDENED_INDEX
from hashlib import sha256
from ecdsa import SECP256k1, SigningKey

def generate_taproot_address(mnemonic):
seed = bip39.phrase_to_seed(mnemonic)
bip32 = BIP32.from_seed(seed)
path = "m/86'/0'/0'/0/0"
child_key = bip32.get_privkey_from_path(path)
private_key = SigningKey.from_string(child_key, curve=SECP256k1)
public_key = private_key.get_verifying_key().to_string("compressed")
tweak = sha256(b'TapTweak' + public_key[1:]).digest()
tweaked_pubkey = bytearray(public_key)
for i in range(32):
tweaked_pubkey[1 + i] ^= tweak[i]
witver = 1 # witness version for Taproot
witprog = tweaked_pubkey[1:33]
address = bech32.encode('bc', witver, witprog)
return address

5. Taproot 格式地址离线生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import bip39
import bech32
from bip32 import BIP32, HARDENED_INDEX
from hashlib import sha256
from ecdsa import SECP256k1, SigningKey

def generate_taproot_address(mnemonic):
seed = bip39.phrase_to_seed(mnemonic)
bip32 = BIP32.from_seed(seed)
path = "m/86'/0'/0'/0/0"
child_key = bip32.get_privkey_from_path(path)
private_key = SigningKey.from_string(child_key, curve=SECP256k1)
public_key = private_key.get_verifying_key().to_string("compressed")
tweak = sha256(b'TapTweak' + public_key[1:]).digest()
tweaked_pubkey = bytearray(public_key)
for i in range(32):
tweaked_pubkey[1 + i] ^= tweak[i]
witver = 1 # witness version for Taproot
witprog = tweaked_pubkey[1:33]
address = bech32.encode('bc', witver, witprog)
return address

6. 离线签名

  • 构建交易: 创建并填充交易输入和输出。
  • 生成交易摘要: 使用交易数据生成 Taproot 交易摘要。
  • 签名交易: 使用私钥对交易摘要进行签名。
  • 构建完整交易: 将签名附加到交易中并生成最终的交易。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def buildAndsignTx(input_list, output_list, private_key, taproot_pubkey):
tx = Transaction()
for input in input_list:
tx.add_input(input['previous_txid'], input['index'])
for output in output_list:
tx.add_output(output["address"], output["value"])
tx.version = 2
tx.locktime = 0
sighash = tx.signature_hash()

tweaked_privkey = taproot_tweak_seckey(private_key, taproot_pubkey)
signature = schnorr.sign(tweaked_privkey, sighash)

witness = TaprootWitness(taproot_pubkey, signature)
tx.inputs[0].witness = witness

signed_tx = tx.serialize()

return signed_tx

离线签名搞出来,基本交易就OK了,中心化钱包和去中心化钱包的本质区别是私钥管理,签名是一样的。


05-02 Bitcoin钱包Schnorr地址生成与签名
http://example.com/2024/05/28/05-2Bitcoin钱包Schnorr地址生成与签名/
作者
Wangxiaowang
发布于
2024年5月28日
许可协议