消息的签名与验证 —— 证明BTC地址的所有权

译:众所周知,本人现在正在开发一个新型的区块链架构NGIN。最近看到Schnorr签名算法很热门就在NG上部署了。但是部署完之后就陷入了更深层的思考——如何更好的利用Schnorr的MultiSig(多重签名)机制?现在我构思将Account也转为利用Schnorr来判定所有权。但是发现如此设计的话Account结构中不包含PublicKey,将很难通过anonymous表来搜索账户余额。故希望从本文中获得启发。

原文地址

引言

许多人在问,如何证明或声明属于某个公共地址的比特币的所有权?

假设,您叫A,并且想与B做生意。B告诉你,他的公开地址是19Ho6eA4hBtb7hkN1S6GdmHD611Egd7gtC,而他在2015年10月15日余额恰好是2.04531538BTC。通过怎样,B可以毫无疑问地证明他确实拥有这个地址,让A能绝对信任?

在这个简短的教程中,我们将向您展示如何使用我们最喜欢的钱包Electrum完成所有这些工作。您根本不需要任何编程技能,但是您应该熟悉私钥和公钥的概念,并且至少应牢记不要与任何人共享私钥或以任何形式在线发送私钥的重要性。

其中的所有私钥和公钥都是出于教育目的,切勿使用——否则余额将立马被抢走。

区块链还不够 —— 屏幕截图还不够

B向A发送了这个交易到区块链。

当然,那里有公共地址,金额和日期。但是不久,A意识到A可以选择区块链上的任何随机地址,并将其作为“证明”发送给她。

A请B提供更好的证据,然后B用他的钱包的两个屏幕截图回复:

(图略,建议都用ascii图,不然图床真不靠谱)

好吧,所以看起来B好像在说实话。但是!图像很容易被操纵。B可以使用Photoshop来创建这些图像。

因此,A仍然没有被说服,A也不可能被这样说服。A需要确切的证据。最好的部分是A可以拥有的!

消息的签名与验证 —— 完美的证明

比特币地址由一个私钥(您应该始终保持私有)和一个公钥组成,该公钥与您的公网地址相同。这是最重要的部分:密钥对可用于对消息进行签名。甚至不涉及区块链。

如果B真正拥有他声称的公共地址,那么他也拥有其私钥。它可以用来签名消息。

在这个例子中,B确实拥有他声称的比特币地址。具体来说,他的证据是:

1
2
Private key: 5KSFWJRuCuTFFsPQgokmLuKbY84f8e9pcWaJX2C7r8jsLi2RuJF
Public key: 19Ho6eA4hBtb7hkN1S6GdmHD611Egd7gtC

A现在意识到,如果B是诚实的,他可以访问他的密钥对,从而能够为珍妮签名消息。

现在,A要求B使用他的密钥对签署特定的消息。简希望约翰用他的公钥(即他的比特币地址)来签署“我拥有这个地址,A!”这个消息。

这可以在Electrum中快速完成。 John只是将他的私钥导入一个新的钱包,选择“工具/签名/验证”消息,然后…钱包吐出以下代码…

1
2
3
4
5
I own this address, Jane!

19Ho6eA4hBtb7hkN1S6GdmHD611Egd7gtC

GxHVqiSW+WxEA+IlNB5oqDpzLba7MJxYJl5Gr3ijm1SQYb9xEukrvnoXPbfi1k+wa54k0F/0tQ0hdsEjw1paa5w=

…他寄给A。特别要注意的是,如果没有他的私钥,他将无法签署此消息。更重要的是,A从未将自己的私钥发送给简。

A现在可以在自己的计算机上使用Electrum。A完全不需要导入任何特定的钱包详细信息,现在可以使用相同的功能并粘贴自己的EXACT消息,B的比特币地址,签名的代码,然后按“验证”。 她得到…(图略,反正就是ok了)

结论

任务完成!A现在可以100%确信B拥有他声称拥有的地址。B毫无疑问地证明了他控制着他所说的比特币地址。

如果您正在考虑与不确定的人打交道,只需要求他们为您签名。这真的很容易,它使整个加密生态系统变得更加安全和可信。双赢!

后文

妈的感觉白翻译了等于啥也没讲。都是超基础的……

也不知道哪里能找到,干脆就自己写吧。

就是说,BTC的地址是个公钥——这句话是不那么正确的,BTC地址是个hash后的公钥。换句话说,第三方没法通过你的地址得到你的公钥。

之前看到一个ETH的攻击,就是通过signature之后的R和S得到ETH的account地址,然后再结合别的R,S来降低爆破privateKey的难度(原文找不到了)。

恢复PublicKey的如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* ECDSA public key recovery from signature
* @param {Buffer} msgHash
* @param {Number} v
* @param {Buffer} r
* @param {Buffer} s
* @return {Buffer} publicKey
*/
exports.ecrecover = function (msgHash, v, r, s) {
var signature = Buffer.concat([exports.setLength(r, 32), exports.setLength(s, 32)], 64)
var recovery = v - 27
if (recovery !== 0 && recovery !== 1) {
throw new Error('Invalid signature v value')
}
var senderPubKey = secp256k1.recover(msgHash, signature, recovery)
return secp256k1.publicKeyConvert(senderPubKey, false).slice(1)
}

而BTC的PK->Addr转换方式又是sha256又是ripemd160能recover个鬼……

这我就很好奇了,没PublicKey咋鉴权?

本来是打算btcd里看的,不过没立即找到。后来还是搜到别的实现

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
def verify_message(address, signature, message):
""" See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """
from ecdsa import numbertheory, ellipticcurve, util
curve = curve_secp256k1
G = generator_secp256k1
order = G.order()
# extract r,s from signature
sig = base64.b64decode(signature)
if len(sig) != 65: raise BaseException("Wrong encoding")
r,s = util.sigdecode_string(sig[1:], order)
nV = ord(sig[0])
if nV < 27 or nV >= 35:
return False
if nV >= 31:
compressed = True
nV -= 4
else:
compressed = False
recid = nV - 27
# 1.1
x = r + (recid/2) * order
# 1.3
alpha = ( x * x * x + curve.a() * x + curve.b() ) % curve.p()
beta = modular_sqrt(alpha, curve.p())
y = beta if (beta - recid) % 2 == 0 else curve.p() - beta
# 1.4 the constructor checks that nR is at infinity
R = ellipticcurve.Point(curve, x, y, order)
# 1.5 compute e from message:
h = Hash( msg_magic( message ) )
e = string_to_number(h)
minus_e = -e % order
# 1.6 compute Q = r^-1 (sR - eG)
inv_r = numbertheory.inverse_mod(r,order)
Q = inv_r * ( s * R + minus_e * G )
public_key = ecdsa.VerifyingKey.from_public_point( Q, curve = SECP256k1 )
# check that Q is the public key
public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
# check that we get the original signing address
addr = public_key_to_bc_address(encode_point(public_key, compressed))
if address == addr:
return True
else:
#print addr
return False

这tm就很nb了,居然还真又是反推PublicKey的。

对应的是dcrd(一个btcd的fork)的这部分

那么NGIN里的multi-own的设计就有点难了【本来还以为有什么黑科技

现在难点在于,用Account里增加Sign来实现了multi-own之后,怎么实现去publickey情况下的address?

一种办法是把balance写进Account里,这样的话anonymous表里的balance该怎么办?无Account的矿工或者买家该怎么办?

另一种办法是看看Schnorr能不能生成一个可以MultiSig的PublicKey。

还有种办法就是放弃这个特性。

看了下go-schnorr对聚合签名鉴权的确是用的一个新的PublicKey,索性就把这个拿来当address。也用不着放弃了。