4

The steps to convert a private key to a SegWit (version 0, bech32) address are roughly as follows:

1.  public_key = Generator * private_key  (use the compressed version)
2.  public_key_hash = RIPEMD160(SHA256(public_key)))
3.  bech32_encode

For taproot address (version 2 witness), is the tweak mandatory or optional? Are the following calculations correct? (No tweak):

Suppose the private key is 1

1. The corresponding 32-byte compressed key (x-only, without oddness indicator): 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798

2. Directly encode the above 32 bytes using the new modified bech32m encoding, and witness version 1:
    bech32m_encode(hrp='bc', witness_version=1, bytes.fromhex('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'))

result: bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0

Also, what the point of adding a tweak step? It doesn’t seem to improve any security by adding a number to privkey. Thanks.

3 Answers 3

3

The steps for computing a BIP350 (bech32m) address for a P2TR (pay to taproot) output spendable with a given private key depend on what you want that output to do.

The most straightforward way (but not recommended, see further) is the following:

  • Compute the public key P = privkey * G.
  • Let Pb be the serialization of P in x-only form: that's just the 32-byte big endian encoding of its X coordinate. Equivalently, it's the compressed public key encoding with the first byte removed.
  • The address is bech32m_encode(payload=Pb, version=1). See BIP350 for details.

Spending such outputs is then done by providing a BIP340 signature on that public key, using the message hash mechanism described in BIP341.

However, P2TR outputs are effectively a combination of a pay-to-key and a pay-to-script: each output can be spent for either by signing the output directly, or by revealing how the output commits to a script, and then satisfying that script. Even if you don't want any scripts in there, BIP341 still recommends using a "dummy" script. For simple single-key setups this is unlikely to matter, but it may matter for more advanced cases (see footnote 23 in BIP341), and thus the suggested standard is to always do this. In this case, the procedure would be:

  • Compute the public key P = privkey * G.
  • Let Pb be the serialization of P in x-only form.
  • Compute the tweak t = SHA256(SHA256("TapTweak") || SHA256("TapTweak") || Pb), interpreted as 32-byte big endian encoded integer.
  • Compute the tweaked public key Q = P + (t * G).
  • Let Qb be the serialization of Q in x-only form.
  • The address is bech32m_encode(payload=Qb, version=1).

Signing now requires a BIP340 signature with (privkey + t) as private key.

2
  • Thanks. I just re-edited my question. Is the tweak step mandatory?
    – Simon L
    Commented Dec 15, 2022 at 16:51
  • 1
    No, it is not. As I pointed out, it's the standard approach as it is beneficial in some more advanced scenarios, but it is unlikely to matter for simple single-key scenarios. I strongly recommand you use it though, as it avoids needing to understand when it might matter, and will be more interoperable with other software/wallets that do the same. See footnote 23 in BIP341 for details. Commented Dec 15, 2022 at 17:04
0

It is almost the same as doing a version 0 Bech32 encoding with a couple of differences. For all bech32(m) encodings, the data that gets encoded starts with one version byte and ends with 6 checksum bytes. As far as I know, these 7 bytes are the only differences.

The version byte changes from 0 to 1.

The checksum bytes are also determined differently. A function called PolyMod is called to produce the checksum. The result of this function is XORed with a constant. For the version 0 (Bech32) encoding, this constant is 1. But for the version 1 (Bech32M) encoding, the constant is 0x2bc830a3.

As far as I know, every other part of the process is the same. Writing a step-by-step is somewhat difficult because it depends on various parameters. If you don't mind wading through a bunch of C++ code (it actually isn't that much and is pretty straight forward), the exact algorithm is in the files bech32.h and bech32.cpp. The process of doing either a Bech32 or a Bech32M encoding would begin on line 357 in bech32.cpp, with a call to the Encode function.

1
  • 1
    This answer is correct, but only focuses on the difference between bech32 and bech32m. There are many more differences in other steps between the private key and the finding the data that goes into the address; see my own answer. Commented Dec 15, 2022 at 16:10
0

I followed that algorithm step by step and used Samourai Wallet to sweep the final address and it don't worked.

I checked by another script that I already have running and tested the sweep with Samourai with success and there is a difference in this step:

  • Compute the tweaked public key Q = P + (t * G).

The difference is in the P, the working script that I have running don't use the X and Y source coordinates of P, it uses only the X coordinate and a new one for Y that is calculated with:

math.liftX function

So this formula should be Q = P(xP,yliftx) + (t * G)

I don't know yet what liftX does, but is the only way of making it work for sweep the address with success in Samourai.

Can you pronounce about this?

It's very annoying some formula like this don't be correct or should I think that maybe Samourai Wallet use another method ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.