每天进步一点点:学习用私钥签名

in HIVE CN 中文社区4 years ago (edited)

最近打算学习一下私钥/公钥/签名等相关内容,发现这块东西真的很难啃,也许弄懂之后就是一两句话的事情,但是不懂的时候一头雾水不得入门真的很难受。


(图源 :pixabay)

这两天啃了一点签名相关的内容,以ecdsa为例,简单记录一下。

STEEM/HIVE中的签名流程

首先,签名可以直接对消息签名或者对消息摘要签名,对消息签名的实质是对消息进行摘要,然后再签名。

STEEM/HIVE中的签名大致流程如下:

  • 将去除签名的的transaction序列化
  • 加上chainid前缀内容
  • 对上述信息进行摘要(hashlib.sha256(message).digest()
  • 使用私钥对上述摘要信息进行签名
  • 将签名附加进transaction并广播

因为整个过程涉及较多内容,所以我们一点一点地啃,今天只啃签名部分。

准备(摘要 & SigningKey)

为了简化代码,我们假设要处理的信息为"Hello World!",并由此得出对应的摘要信息。

message = "Hello world!"
digest = hashlib.sha256(bytes(message, 'utf-8')).digest()

接下来,我们制定我们使用的私钥,直接用我们之前文章中生成的私钥即可(为了避免编解码,我直接用的原始的字符形式私钥)。

private_key = "415ac848c316b406920e0a4b43adc7f93c45bb89124f80ced8d1f50fae4f080d"

ecdsa中要把私钥弄成SigningKey签名密钥:

sk = ecdsa.SigningKey.from_string(unhexlify(private_key), curve=ecdsa.SECP256k1)

现在可以说我们已经做好准备了,有了要被签名的摘要(digest),有了用于签名的密钥(SigningKey),接下来可以看进行签名了。

签名

首先我们来看一下SigningKeysign_digest方法的接口:

def sign_digest(self, digest, entropy=None, sigencode=sigencode_string, k=None)

其中k是作为nonce的一个随机量,这样相同的密钥相同的信息每次签名出来的结果都不相同,防御了一些猜测私钥的攻击。

但是这篇文章中为了便于对比,我把k指定为1,实际代码中千万不要这样做,好了,我们可以进行签名了:

k = 1
sigder = sk.sign_digest(digest, sigencode=ecdsa.util.sigencode_der, k=k)
print(hexlify(sigder).decode())

ecdsa.util.sigencode_der是一个回调函数,指定签名如何编码,其中关于der编码,我找到的最直接的信息在这里 注意,这里有一些要求,我们之后文章中在详细啃:

Reveal spoiler

image.png

其实呢,签名生成了两个数r、s,我们把r、s输出一下看看:

r, s = ecdsa.util.sigdecode_der(sigder, sk.curve.generator.order())
print(f"r: {r:x}")
print(f"s: {s:x}")

输出如下:

Reveal spoiler

image.png

文字格式:

sigder: 3045022079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798022100a5d2c7770cb6bac8eaa447b1c00903ff8b9b1defd405275e56217db481d99eb1
r: 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
s: a5d2c7770cb6bac8eaa447b1c00903ff8b9b1defd405275e56217db481d99eb1

由此不难看看der就是加上了长度信息的并把r、s编码进去的二进制串。

接下来是由r, s计算出signature

signature = ecdsa.util.sigencode_string(r, s, sk.curve.generator.order())
print("signature: ", hexlify(signature).decode())

输出如下:

signature : 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798a5d2c7770cb6bac8eaa447b1c00903ff8b9b1defd405275e56217db481d99eb1

由此可以看出来,其实就是上边r, s两个串链接到了一起。

ecdsa.util.sigencode_string

除了用上述代码生成signature外,还可以在sk.sign_digest方法中直接指定sigencode=ecdsa.util.sigencode_string来直接生成signature:

sig = sk.sign_digest(digest, sigencode=ecdsa.util.sigencode_string, k=k)
print("signature: ", hexlify(sig).decode())

通过对比输出,我们知道两者生成的signature完全一样:

Reveal spoiler

image.png

结论

我们用ecdsa中的SigningKeysign_digest方法就可以对消息摘要进行签名,除了输入摘要、私钥外,我们还需要提供如何编码的回调函数以及一个随机数k

sign_digest方法本质是生成了rs,提供不同的回调函数只是以不同的编码形式呈现,所以得出的签名是相同的。

其它

上述签名满足STEEM/HIVE的情况吗?除去一些外围的工作外,答案应该是不确定,为什么不确定呢?我们之后的文章再分析。

相关链接