Why cannot a Taproot tweaked public key not just have implicit even Y coordinate?

Please forgive the long pre-amble, but its really needed for my question. Hopefully it has some relevance to the numerous discussions on this topic.

In BIP340 it describes the 32 byte X-coordinate only representation of public keys in which the public key is implicitly assumed to have even Y-coordinate, and it applies this to the signing public key P and the 'ephemeral' public key R.

When we perform a Schnorr signature with a private key d', the algorithm automatically flips the sign of d' (in the field Fn where n = order of the EC group <G>), if needed, to produce d such that signing public key P = dG has even Y-coordinate (when that Y-coordinate is expressed as a number in the range [0, p - 1]). Similarly it flips sign of the initial ephemeral private key k' in Fn, if needed, to produce k such that ephemeral public key R = kG has even Y-coordinate.

The Schnorr signature verification is then designed with the assumption of P and R both having even Y-coordinates, and it verifies that the signature has been performed with the altered private key d (= the unknown discrete log of P) and the altered ephemeral private key k (= the unknown discrete log of R).

But when we validate a Taproot script path spend as described in BIP341 we calculate a tweaked public key Q as :

Q = P + tG

and this Q may have an even or an odd Y-coordinate, even though P implicitly has an even Y-coordinate and is represented in the control block of the spending input's witness in the Schnorr 32 byte X-coordinate only form. We then record the parity of Q in the lowest bit of the control byte of the control block.

However it seems we could define tweaked public key Q to implicitly have even Y-coordinate by first defining Q' as :

Q' = P + tG

and then defining Q to be the even Y-coordinate version of this Q', by flipping the sign (in Fp) of the Y-coordinate of Q' if necessary to make the parity even. Then there would be no need to store the parity bit in the control byte.

This would seem to require more computation, however it doesn't because in the script path spend validation algorithm in BIP341 all we need is the X-coordinate of Q, which we can just obtain directly from Q' : x(Q) = x(Q'). So we don't actually need to compute Q, just Q'. But of course a comment in the code would be welcome to clarify that.

In the taproot_tweak_pubkey function of BIP341 we could rename the variable Q to Q' and return just x(Q') rather than the 2-tuple (parity, x(Q)), again putting in a comment to make clear we didn't actually calculate the tweaked public key Q, we just calculated Q', but that was all we needed to do in order to obtain x(Q), since x(Q) = x(Q'). So no additional computation is needed.

In the function taproot_tweak_seckey of BIP341 we could put the following comment :

# let u' = (seckey + t) % SECP256K1_ORDER
# Then the tweaked private key (ie. discrete log of the tweaked public key Q) is u given by :
# u = u' if uG has even Y-coordinate
# otherwise u = (SECP256K1_ORDER - u') % SECP256K1_ORDER (covering the unusual case of u' = 0)
# ie. we just flip the sign of u' if needed, as we do in the Schnorr signature scheme with d' and k'.

But we wouldn't need to actually do this computation of u and no change in the code would actually be needed, as it suffices to just return u' and we could add another comment at the head of the function to explain why we're doing that :

# This function returns u' rather than u because that suffices for the usage of the function.
# Specifically the function is called by the function taproot_sign_key for key path spends :
# output_seckey = taproot_tweak_seckey(internal_seckey, h)
# But when output_seckey is subsequently used in :
# sig = schnorr_sign(sighash(hash_type), output_seckey, bip340_aux_rand)
# the Schnorr signature algorithm in the function schnorr_sign will flip the sign of output_seckey if needed, thus producing u.
# The function taproot_tweak_seckey is not required for script path spends.

Alternatively we could just do the extra computation in taproot_tweak_seckey to calculate u and then return u, which might be clearer. That approach would then give us the intuitively expected property :

u = taproot_tweak_seckey(d', h) 
=>
uG = lift_x( taproot_tweak_pubkey(PubKey(d'), h) )

for any taproot internal private key d', where PubKey function is as in BIP340.

As it stands we actually do already have this property, but with lift_x an overloaded version of the original lift_x taking two parameters, the first the parity bit and the second the 32 byte X-coordinate - as BIP341 states (using the present notation) :

taproot_tweak_pubkey(PubKey(d'), h)[1] == PubKey(taproot_tweak_seckey(d', h))

though

taproot_tweak_pubkey(PubKey(d'), h)[0] == parity of Y-coordinate of PubKey(taproot_tweak_seckey(d', h))

also holds, and together these two are equivalent to the above property with the overloaded lift_x.

However it would seem less cumbersome to just have the property with our existing single parameter lift_x function.

If things did work as above then in the BIP341 function taproot_output_script :

_, output_pubkey = taproot_tweak_pubkey(internal_pubkey, h)

would change to :

output_pubkey = taproot_tweak_pubkey(internal_pubkey, h)

BIP341 function taproot_sign_script would be simplified to :

def taproot_sign_script(internal_pubkey, script_tree, script_num, inputs):
    info, _ = taproot_tree_helper(script_tree)
    (leaf_version, script), path = info[script_num]
    control_block = bytes([leaf_version]) + internal_pubkey + path
    return inputs + [script] + [control_block]

ie. we would no longer put a parity bit in the control byte.

BIP341 function taproot_sign_key would remain the same.

In the script validation rules in BIP341 we would remove the parity bit check :

c[0] & 1 ≠ y(Q) mod 2

(the implicitly even Y-coordinate y(Q) mod 2 would always equal 0).

I am maybe over-thinking this a little but the present method seems a bit inconsistent and confusing with P and R both using the implicit even Y-coordinate of the Schnorr scheme, and the tweaked public key Q not doing so, even though it is represented in the 32 byte Schnorr format in the witness program of the Taproot UTXO's scriptPubKey. It would just seem better to apply the implicit even Y-coordinate policy consistently when the 32 byte Schnorr form of public key is being used. If Q did have the implicit even Y-coordinate then in batch Schnorr signatures we would know the full tweaked public keys from their X-coordinates only without needing to consult separately stored parity bits.

I think where there is confusion that's where bugs can creep in, maybe even fund loss could occur in some cases.

So my question would be is there a reason for the way it has been done, is there something that I am missing above, or could the above approach I have described have worked?



from Recent Questions - Bitcoin Stack Exchange https://ift.tt/QAzi6fv
via IFTTT

Popular posts from this blog

Future of Bitcoin encryption and security in a QC era

Possible rollback due to lazy reveal in BRC20?

A way to recover scammed Bitcoin investment