Skip to content

Instantly share code, notes, and snippets.

@jaonoctus
Last active December 28, 2024 00:34
Show Gist options
  • Save jaonoctus/fdbfee8c7025edee345da6a76d5199c8 to your computer and use it in GitHub Desktop.
Save jaonoctus/fdbfee8c7025edee345da6a76d5199c8 to your computer and use it in GitHub Desktop.
# If that is inacceptable, pick as internal key a "Nothing Up My Sleeve" (NUMS) point,
# i.e., a point with unknown discrete logarithm.
# One example of such a point is H = lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0)
# which is constructed by taking the hash of the standard uncompressed encoding of the secp256k1 base point G as X coordinate.
# In order to avoid leaking the information that key path spending is not possible
# it is recommended to pick a fresh integer r in the range 0...n-1 uniformly at random and use H + rG as internal key.
# It is possible to prove that this internal key does not have a known discrete logarithm with respect to G
# by revealing r to a verifier who can then reconstruct how the internal key was created.
import hashlib
from sage.all import FiniteField, EllipticCurve, Integer
# secp256k1 parameters
F = FiniteField(Integer(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F))
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 # curve order
E = EllipticCurve([F(Integer(0)), F(Integer(7))])
# Original generator point G in uncompressed format
G_DER = '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'
# Get H point by hashing G
H = E.lift_x(F(int(hashlib.sha256(bytes.fromhex(G_DER)).hexdigest(), 16)))
print("H (NUMS base point) = ")
print('%x %x' % H.xy())
# Generate scalar from string "unspendable"
r = Integer(int(hashlib.sha256("unspendable".encode()).hexdigest(), 16) % n)
print("\nScalar r = ")
print(hex(r))
# Get the original G point
G = E.lift_x(F(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798))
# Calculate rG
rG = r * G
# Calculate final NUMS point: H + rG
final_nums = H + rG
print("\nFinal NUMS point (H + rG) = ")
print('%x %x' % final_nums.xy())
# NUMS = 8335b42143dd67da2ec8cb8b9108777c351b47993bba2a537a04bf72eb7396a

P2TR path spend with bitcoin core

I'm using signet but feel free to use it for mainnet, just make sure to use the right encoding for private keys.

descriptor:       tr(NUMS, {pk(Alice),pk(Bob)})

ALICE
Private Key WIF:  cVEhSJrJCbtUiT4iWo5CF3cwXZyinAQnfbx7mF3ZYFxRgvKbsrsc
Public Key:       027ecb12b874bd90f219fae9bf4e8bd55acaa8036920a33503db981d8f7570586e

BOB
Private Key WIF:  cNUfsHZbxvkbRoUKKQkm7pXAuiqRUW48GmSTm75TCRhfYuLpewEF
Public Key:       02bdda3b5961d2ea2d2e8933a22d787f4f0958995cca5f662b14d50945987cec5e

1. create a descriptor wallet (hot, with private keys)

bitcoin-cli -signet=1 createwallet signetwallet false true

2. get the watch-only address with a descriptor

bitcoin-cli -signet=1 deriveaddresses "tr(8335b42143dd67da2ec8cb8b9108777c351b47993bba2a537a04bf72eb7396a,{pk(027ecb12b874bd90f219fae9bf4e8bd55acaa8036920a33503db981d8f7570586e),pk(02bdda3b5961d2ea2d2e8933a22d787f4f0958995cca5f662b14d50945987cec5e)})#gc7cr0fk"

tb1p73ltr0yz5zpf2cmkd6cca56lhkn2caxxlyqy7hjf3cmvjlnn2n8ss6y82k

3. import the descriptor with private key

s/027ecb12b874bd90f219fae9bf4e8bd55acaa8036920a33503db981d8f7570586e/cVEhSJrJCbtUiT4iWo5CF3cwXZyinAQnfbx7mF3ZYFxRgvKbsrsc/
bitcoin-cli -signet=1 importdescriptors '[{"desc": "tr(8335b42143dd67da2ec8cb8b9108777c351b47993bba2a537a04bf72eb7396a,{pk(cVEhSJrJCbtUiT4iWo5CF3cwXZyinAQnfbx7mF3ZYFxRgvKbsrsc),pk(02bdda3b5961d2ea2d2e8933a22d787f4f0958995cca5f662b14d50945987cec5e)})#d7h2u8rq", "timestamp": "now"}]'

4. deposit and create a psbt

bitcoin-cli -signet=1 createpsbt '[{"txid": "f30e59403ec4d3120cb6839a5bdbee8c6904987f045a700046f873fe26f9f30d","vout":1}]' '[{"tb1p2j90mh5w6zpu6weymef4nmmx0avea5a0enc5gqrmq0f3rh063ldsy0ctp4": 0.00009800}]'

cHNidP8BAF4CAAAAAQ3z+Sb+c/hGAHBaBH+YBGmM7ttbmoO2DBLTxD5AWQ7zAQAAAAD9////AUgmAAAAAAAAIlEgVIr93o7Qg807JN5TWe9mf1me06/M8UQAewPTEd36j9sAAAAAAAAA

5. sign the transaction and broadcast it

bitcoin-cli -signet=1 walletprocesspsbt 'cHNidP8BAF4CAAAAAQ3z+Sb+c/hGAHBaBH+YBGmM7ttbmoO2DBLTxD5AWQ7zAQAAAAD9////AUgmAAAAAAAAIlEgVIr93o7Qg807JN5TWe9mf1me06/M8UQAewPTEd36j9sAAAAAAAAA'

cHNidP8BAF4CAAAAAQ3z+Sb+c/hGAHBaBH+YBGmM7ttbmoO2DBLTxD5AWQ7zAQAAAAD9////AUgmAAAAAAAAIlEgVIr93o7Qg807JN5TWe9mf1me06/M8UQAewPTEd36j9sAAAAAAAEBKxAnAAAAAAAAIlEg9H6xvIKggpVjdm6xjtNfvaasdMb5AE9eSY42yX5zVM8BCKcDQKYx/nD1q88Rk03G8eCrH0rcqCO91Axpv50+7rkf6Kmjof0L8GDstdoGixc7XG9630h0xvUV0igO4EcW/upfOzAiIH7LErh0vZDyGfrpv06L1VrKqANpIKM1A9uYHY91cFhurEHAUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAhFqy6ULYUPk1vgQdKgELxVt8gAOy1RyXLSOxO+pYBVgAA

020000000001010df3f926fe73f84600705a047f9804698ceedb5b9a83b60c12d3c43e40590ef30100000000fdffffff014826000000000000225120548afdde8ed083cd3b24de5359ef667f599ed3afccf144007b03d311ddfa8fdb0340a631fe70f5abcf11934dc6f1e0ab1f4adca823bdd40c69bf9d3eeeb91fe8a9a3a1fd0bf060ecb5da068b173b5c6f7adf4874c6f515d2280ee04716feea5f3b3022207ecb12b874bd90f219fae9bf4e8bd55acaa8036920a33503db981d8f7570586eac41c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac02116acba50b6143e4d6f81074a8042f156df2000ecb54725cb48ec4efa96015600000000


  • "Nothing Up My Sleeve" (NUMS) point, i.e., a point with unknown discrete logarithm. One example of such a point is H = lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) which is constructed by taking the hash of the standard uncompressed encoding of the secp256k1 base point G as X coordinate.
  • Descriptors
  • BIP340
  • BIP342
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment