1
0
Fork 0
mirror of https://github.com/bitcoin/bips.git synced 2025-02-26 00:14:27 +01:00

Merge pull request #81 from jonasnick/tweaks

Improve readability of bip-taproot wallet section
This commit is contained in:
Pieter Wuille 2019-10-07 10:43:55 -07:00 committed by GitHub
commit 730feed75a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 34 deletions

View file

@ -26,7 +26,7 @@ transactions. These are [http://www.secg.org/sec1-v2.pdf standardized], but have
compared to [https://en.wikipedia.org/wiki/Schnorr_signature Schnorr signatures] over the same curve:
* '''Security proof''': The security of Schnorr signatures is easily [https://www.di.ens.fr/~pointche/Documents/Papers/2000_joc.pdf provable] in the random oracle model assuming the elliptic curve discrete logarithm problem (ECDLP) is hard. Such a proof does not exist for ECDSA.
* '''Non-malleability''': ECDSA signatures are inherently malleable; a third party without access to the private key can alter an existing valid signature for a given public key and message into another signature that is valid for the same key and message. This issue is discussed in [https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki BIP62] and [https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki BIP66]. On the other hand, Schnorr signatures are provably non-malleable<ref>More precisely they are '' '''strongly''' unforgeable under chosen message attacks '' (SUF-CMA), which informally means that without knowledge of the secret key but given a valid signature of a message, it is not possible to come up with a second valid signature for the same message. A security proof in the random oracle model can be found for example in [https://eprint.iacr.org/2016/191 a paper by Kiltz, Masny and Pan], which essentially restates [https://www.di.ens.fr/~pointche/Documents/Papers/2000_joc.pdf the original security proof of Schnorr signatures by Pointcheval and Stern] more explicitly. These proofs are for the Schnorr signature variant using ''(e,s)'' instead of ''(R,s)'' (see Design above). Since we use a unique encoding of ''R'', there is an efficiently computable bijection that maps ''(R, s)'' to ''(e, s)'', which allows to convert a successful SUF-CMA attacker for the ''(e, s)'' variant to a successful SUF-CMA attacker for the ''(r, s)'' variant (and vice-versa). Furthermore, the aforementioned proofs consider a variant of Schnorr signatures without key prefixing (see Design above), but it can be verified that the proofs are also correct for the variant with key prefixing. As a result, the aforementioned security proofs apply to the variant of Schnorr signatures proposed in this document.</ref>.
* '''Non-malleability''': ECDSA signatures are inherently malleable; a third party without access to the secret key can alter an existing valid signature for a given public key and message into another signature that is valid for the same key and message. This issue is discussed in [https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki BIP62] and [https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki BIP66]. On the other hand, Schnorr signatures are provably non-malleable<ref>More precisely they are '' '''strongly''' unforgeable under chosen message attacks '' (SUF-CMA), which informally means that without knowledge of the secret key but given a valid signature of a message, it is not possible to come up with a second valid signature for the same message. A security proof in the random oracle model can be found for example in [https://eprint.iacr.org/2016/191 a paper by Kiltz, Masny and Pan], which essentially restates [https://www.di.ens.fr/~pointche/Documents/Papers/2000_joc.pdf the original security proof of Schnorr signatures by Pointcheval and Stern] more explicitly. These proofs are for the Schnorr signature variant using ''(e,s)'' instead of ''(R,s)'' (see Design above). Since we use a unique encoding of ''R'', there is an efficiently computable bijection that maps ''(R, s)'' to ''(e, s)'', which allows to convert a successful SUF-CMA attacker for the ''(e, s)'' variant to a successful SUF-CMA attacker for the ''(r, s)'' variant (and vice-versa). Furthermore, the aforementioned proofs consider a variant of Schnorr signatures without key prefixing (see Design above), but it can be verified that the proofs are also correct for the variant with key prefixing. As a result, the aforementioned security proofs apply to the variant of Schnorr signatures proposed in this document.</ref>.
* '''Linearity''': Schnorr signatures have the remarkable property that multiple parties can collaborate to produce a signature that is valid for the sum of their public keys. This is the building block for various higher-level constructions that improve efficiency and privacy, such as multisignatures and others (see Applications below).
For all these advantages, there are virtually no disadvantages, apart
@ -40,7 +40,7 @@ made:
[[File:bip-schnorr/speedup-batch.png|frame|This graph shows the ratio between the time it takes to verify ''n'' signatures individually and to verify a batch of ''n'' signatures. This ratio goes up logarithmically with the number of signatures, or in other words: the total time to verify ''n'' signatures grows with ''O(n / log n)''.]]
By reusing the same curve as Bitcoin has used for ECDSA, we are able to retain existing mechanisms for choosing private and public keys, and we avoid introducing new assumptions about elliptic curve group security.
By reusing the same curve as Bitcoin has used for ECDSA, we are able to retain existing mechanisms for choosing secret and public keys, and we avoid introducing new assumptions about elliptic curve group security.
== Description ==
@ -83,7 +83,7 @@ Implicit Y coordinates are not a reduction in security when expressed as the num
'''Tagged Hashes''' Cryptographic hash functions are used for multiple purposes in the specification below and in Bitcoin in general. To make sure hashes used in one context can't be reinterpreted in another one, hash functions can be tweaked with a context-dependent tag name, in such a way that collisions across contexts can be assumed to be infeasible. Such collisions obviously can not be ruled out completely, but only for schemes using tagging with a unique name. As for other schemes collisions are at least less likely with tagging than without.
For example, without tagged hashing a bip-schnorr signature could also be valid for a signature scheme where the only difference is that the arguments to the hash function are reordered. Worse, if the bip-schnorr nonce derivation function was copied or independently created, then the nonce could be accidentally reused in the other scheme leaking the private key.
For example, without tagged hashing a bip-schnorr signature could also be valid for a signature scheme where the only difference is that the arguments to the hash function are reordered. Worse, if the bip-schnorr nonce derivation function was copied or independently created, then the nonce could be accidentally reused in the other scheme leaking the secret key.
This proposal suggests to include the tag by prefixing the hashed data with ''SHA256(tag) || SHA256(tag)''. Because this is a 64-byte long context-specific constant and the ''SHA256'' block size is also 64 bytes, optimized implementations are possible (identical to SHA256 itself, but with a modified initial state). Using SHA256 of the tag name itself is reasonably simple and efficient for implementations that don't choose to use the optimization.

View file

@ -11,19 +11,25 @@ def tagged_hash(tag, msg):
tag_hash = hashlib.sha256(tag.encode()).digest()
return hashlib.sha256(tag_hash + tag_hash + msg).digest()
def x(P):
return P[0]
def y(P):
return P[1]
def point_add(P1, P2):
if (P1 is None):
return P2
if (P2 is None):
return P1
if (P1[0] == P2[0] and P1[1] != P2[1]):
if (x(P1) == x(P2) and y(P1) != y(P2)):
return None
if (P1 == P2):
lam = (3 * P1[0] * P1[0] * pow(2 * P1[1], p - 2, p)) % p
lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p
else:
lam = ((P2[1] - P1[1]) * pow(P2[0] - P1[0], p - 2, p)) % p
x3 = (lam * lam - P1[0] - P2[0]) % p
return (x3, (lam * (P1[0] - x3) - P1[1]) % p)
lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p
x3 = (lam * lam - x(P1) - x(P2)) % p
return (x3, (lam * (x(P1) - x3) - y(P1)) % p)
def point_mul(P, n):
R = None
@ -37,7 +43,7 @@ def bytes_from_int(x):
return x.to_bytes(32, byteorder="big")
def bytes_from_point(P):
return bytes_from_int(P[0])
return bytes_from_int(x(P))
def point_from_bytes(b):
x = int_from_bytes(b)
@ -56,6 +62,9 @@ def hash_sha256(b):
def jacobi(x):
return pow(x, (p - 1) // 2, p)
def is_quad(x):
return jacobi(x) == 1
def pubkey_gen(seckey):
P = point_mul(G, seckey)
return bytes_from_point(P)
@ -66,12 +75,12 @@ def schnorr_sign(msg, seckey0):
if not (1 <= seckey0 <= n - 1):
raise ValueError('The secret key must be an integer in the range 1..n-1.')
P = point_mul(G, seckey0)
seckey = seckey0 if (jacobi(P[1]) == 1) else n - seckey0
seckey = seckey0 if is_quad(y(P)) else n - seckey0
k0 = int_from_bytes(tagged_hash("BIPSchnorrDerive", bytes_from_int(seckey) + msg)) % n
if k0 == 0:
raise RuntimeError('Failure. This happens only with negligible probability.')
R = point_mul(G, k0)
k = n - k0 if (jacobi(R[1]) != 1) else k0
k = n - k0 if not is_quad(y(R)) else k0
e = int_from_bytes(tagged_hash("BIPSchnorr", bytes_from_point(R) + bytes_from_point(P) + msg)) % n
return bytes_from_point(R) + bytes_from_int((k + e * seckey) % n)
@ -91,7 +100,7 @@ def schnorr_verify(msg, pubkey, sig):
return False
e = int_from_bytes(tagged_hash("BIPSchnorr", sig[0:32] + pubkey + msg)) % n
R = point_add(point_mul(G, s), point_mul(P, n - e))
if R is None or jacobi(R[1]) != 1 or R[0] != r:
if R is None or not is_quad(y(R)) or x(R) != r:
return False
return True

View file

@ -169,14 +169,40 @@ MuSig key aggregation does not have this issue because it already causes the int
The attack works as follows: Assume Alice and Mallory want to aggregate their keys into a taproot output key without a script path.
In order to prevent key cancellation and related attacks they use [https://eprint.iacr.org/2018/483.pdf MSDL-pop] instead of MuSig.
The MSDL-pop protocol requires all parties to provide a proof of possession of their corresponding private key and the aggregated key is just the sum of the individual keys.
The MSDL-pop protocol requires all parties to provide a proof of possession of their corresponding secret key and the aggregated key is just the sum of the individual keys.
After Mallory receives Alice's key ''A'', Mallory creates ''M = M<sub>0</sub> + int(t)G'' where ''M<sub>0</sub>'' is Mallory's original key and ''t'' allows a script path spend with internal key ''P = A + M<sub>0</sub>'' and a script that only contains Mallory's key.
Mallory sends a proof of possession of ''M'' to Alice and both parties compute output key ''Q = A + M = P + int(t)G''.
Alice will not be able to notice the script path, but Mallory can unilaterally spend any coin with output key ''Q''.
</ref>
* The remaining scripts should be organized into the leaves of a binary tree. This can be a balanced tree if each of the conditions these scripts correspond to are equally likely. If probabilities for each condition are known, consider constructing the tree as a Huffman tree.
'''Computing the output script''' Once the spending conditions are split into an internal key <code>internal_pubkey</code> and a binary tree whose leaves are (leaf_version, script) tuples, the following Python3 algorithm can be used to compute the output script. In the code below, <code>ser_script</code> prefixes its input with a CCompactSize-encoded length. Public key objects hold 32-byte public keys according to bip-schnorr, have a method <code>get_bytes</code> to get the byte array and a method <code>tweak_add</code> which returns a new public key corresponding to the sum of the public key point and a multiple of the secp256k1 generator (similar to BIP32's derivation). The second return value of <code>tweak_add</code> is a boolean indicating the quadratic residuosity of the Y coordinate of the resulting point. <code>tagged_hash</code> computes the tagged hash according to bip-schnorr.
'''Computing the output script''' Once the spending conditions are split into an internal key <code>internal_pubkey</code> and a binary tree whose leaves are (leaf_version, script) tuples, the output script can be computed using the following Python3 algorithms with helper functions from the bip-schnorr reference code for integer conversion, point multiplication and tagged hashes.
First, we define <code>taproot_tweak_pubkey</code> for 32-byte bip-schnorr public key arrays.
In addition to the tweaked public key byte array, the function returns a boolean for the quadratic residuosity of the tweaked points' Y coordinate modulo the secp256k1 field order.
This will be required for spending the output with a script path.
In order to allow spending with the key path, we define <code>taproot_tweak_seckey</code> to compute the secret key for a tweaked public key.
For any byte string <code>h</code> it holds that <code>taproot_tweak_pubkey(pubkey_gen(seckey), h)[0] == pubkey_gen(taproot_tweak_seckey(seckey, h))</code>.
<source lang="python">
def taproot_tweak_pubkey(pubkey, h):
t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
if t >= SECP256K1_ORDER:
raise ValueError
Q = point_mul(point(pubkey), t)
return bytes_from_int(x(Q)), is_quad(y(Q))
def taproot_tweak_seckey(seckey0, h):
P = point_mul(G, int_from_bytes(seckey0))
seckey = SECP256K1_ORDER - seckey0 if not is_quad(y(R)) else seckey
t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h))
if t >= SECP256K1_ORDER:
raise ValueError
return (seckey + t) % SECP256K1_ORDER
</source>
The following function, <code>taproot_output_script</code>, returns a byte array with the scriptPubKey (see BIP141).
<code>ser_script</code> refers to a function that prefixes its input with a CCompactSize-encoded length.
<source lang="python">
def taproot_tree_helper(script_tree):
@ -201,34 +227,25 @@ def taproot_output_script(internal_pubkey, script_tree):
h = b''
else:
_, h = taproot_tree_helper(script_tree)
t = tagged_hash("TapTweak", internal_pubkey.get_bytes() + h)
assert int.from_bytes(t, 'big') < SECP256K1_ORDER
output_pubkey, _ = internal_pubkey.tweak_add(t)
return bytes([0x51, 0x20]) + output_pubkey.get_bytes()
output_pubkey, _ = taproot_tweak_pubkey(internal_pubkey, h)
return bytes([0x51, 0x20]) + output_pubkey
</source>
The function <code>taproot_output_script</code> returns a byte array with the scriptPubKey (see BIP141).
[[File:bip-taproot/tree.png|frame|This diagram shows the hashing structure to obtain the tweak from an internal key ''P'' and a Merkle tree consisting of 5 script leaves. ''A'', ''B'', ''C'' and ''E'' are ''TapLeaf'' hashes similar to ''D'' and ''AB'' is a ''TapBranch'' hash. Note that when ''CDE'' is computed ''E'' is hashed first because ''E'' is less than ''CD''.]]
'''Spending using the key path''' A Taproot output can be spent with the private key corresponding to the <code>internal_pubkey</code>. To do so, a witness stack consists of a single element: a bip-schnorr signature on the signature hash as defined above, with the private key tweaked by the same <code>t</code> as in the above snippet. In the code below, <code>internal_privkey</code> has a method <code>pubkey_gen</code> that returns a public key according to bip-schnorr and a boolean indicating the quadratic residuosity of the Y coordinate of the underlying point.
See the code below:
'''Spending using the key path''' A Taproot output can be spent with the secret key corresponding to the <code>internal_pubkey</code>. To do so, a witness stack consists of a single element: a bip-schnorr signature on the signature hash as defined above, with the secret key tweaked by the same <code>h</code> as in the above snippet. See the code below:
<source lang="python">
def taproot_sign_key(script_tree, internal_privkey, hash_type):
def taproot_sign_key(script_tree, internal_seckey, hash_type):
_, h = taproot_tree_helper(script_tree)
internal_pubkey, is_y_qresidue = internal_privkey.pubkey_gen()
if not is_y_qresidue:
internal_privkey = internal_privkey.negate()
t = tagged_hash("TapTweak", internal_pubkey.get_bytes() + h)
output_privkey = internal_privkey.tweak_add(t)
sig = output_privkey.schnorr_sign(sighash(hash_type))
output_seckey = taproot_tweak_seckey(internal_seckey, h)
sig = schnorr_sign(sighash(hash_type), output_seckey)
if hash_type != 0:
sig += bytes([hash_type])
return [sig]
</source>
This function returns the witness stack necessary, and assumes a <code>tweak_add</code> method on private keys, and a <code>sighash</code> function to compute the signature hash as defined above (for simplicity, the snippet above ignores passing information like the transaction, the input position, ... to the sighashing code).
This function returns the witness stack necessary and a <code>sighash</code> function to compute the signature hash as defined above (for simplicity, the snippet above ignores passing information like the transaction, the input position, ... to the sighashing code).
'''Spending using one of the scripts''' A Taproot output can be spent by satisfying any of the scripts used in its construction. To do so, a witness stack consisting of the script's inputs, plus the script itself and the control block are necessary. See the code below:
@ -236,10 +253,9 @@ This function returns the witness stack necessary, and assumes a <code>tweak_add
def taproot_sign_script(internal_pubkey, script_tree, script_num, inputs):
info, h = taproot_tree_helper(script_tree)
(leaf_version, script), path = info[script_num]
t = tagged_hash("TapTweak", internal_pubkey.get_bytes() + h)
_, is_y_qresidue = internal_pubkey.tweak_add(t)
output_pubkey_tag = 0 if is_y_qresidue else 1
pubkey_data = bytes([output_pubkey_tag + leaf_version]) + internal_pubkey.get_bytes()
_, is_y_quad = taproot_tweak_pubkey(internal_pubkey, t)
output_pubkey_tag = 0 if is_y_quad else 1
pubkey_data = bytes([output_pubkey_tag + leaf_version]) + internal_pubkey
return inputs + [script, pubkey_data + path]
</source>