Skip to content

Instantly share code, notes, and snippets.

@harding
Created March 6, 2024 19:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save harding/d44c4a44dffb6c5f44eb6f96146978f3 to your computer and use it in GitHub Desktop.
Save harding/d44c4a44dffb6c5f44eb6f96146978f3 to your computer and use it in GitHub Desktop.
#!/bin/bash -eu
# NO WARRANTY PROVIDED OR IMPLIED. USE AT YOUR OWN RISK.
###########
## Paths ##
###########
# This is the keypath provided in the Casa recovery email minus the "m"
KEYPATH="/49/0/0"
# Sub-paths. This not provided by Casa in their recovery
# instructions but seems to follow the Electrum v4 default, which is
# - External: /0/*
# - Internal (change): /1/*
EXTERNAL_SUBPATH="/0/*"
INTERNAL_SUBPATH="/1/*"
###########
## Xpubs ##
###########
# All operations with this script require you to enter the extended
# pubkeys (xpubs) for all five signing keys. If you want to use the
# descriptors for address verification or signing, you will also need to
# provide their fingerprints (FPs).
#
# The Casa recovery email includes ypubs (not xpubs) for the mobile and
# recovery keys. You will need to convert those ypubs to xpubs, which
# can be done with the following tool:
#
# https://jlopp.github.io/xpub-converter/
#
# Note: you can use that page offline for additional privacy, although
# the operator of that website is currently Casa's CTO, so I ran it
# online
MOBILE_FP=FILL_THIS_IN
MOBILE_XPUB=FILL_THIS_IN
RECOVERY_FP=FILL_THIS_IN
RECOVERY_XPUB=FILL_THIS_IN
# For HW wallets, use HWI to get the xpubs. E.g., plug in the device,
# enter its pin (or use `hwi promptpin`), use `hwi enumerate` to get
# it's fingerprint, and use something like this to get its xpub:
# hwi -t ledger getxpub m$KEYPATH
HW1_FP=FILL_THIS_IN
HW1_XPUB=FILL_THIS_IN
HW2_FP=FILL_THIS_IN
HW2_XPUB=FILL_THIS_IN
HW3_FP=FILL_THIS_IN
HW3_XPUB=FILL_THIS_IN
############################## END CONFIGURATION ###################################
## You shouldn't need to edit anything below this point, except for special cases ##
####################################################################################
# sh: P2SH, wsh: witness script hash, sortedmulti: multisig with keys in
# BIP67 order, 3: 3-of-n multisig
external_descriptor="sh(wsh(sortedmulti(3,"
internal_descriptor="sh(wsh(sortedmulti(3,"
# The five fingerprints and xpubs
external_descriptor="${external_descriptor}[$MOBILE_FP$KEYPATH]$MOBILE_XPUB$EXTERNAL_SUBPATH,"
internal_descriptor="${internal_descriptor}[$MOBILE_FP$KEYPATH]$MOBILE_XPUB$INTERNAL_SUBPATH,"
external_descriptor="${external_descriptor}[$RECOVERY_FP$KEYPATH]$RECOVERY_XPUB$EXTERNAL_SUBPATH,"
internal_descriptor="${internal_descriptor}[$RECOVERY_FP$KEYPATH]$RECOVERY_XPUB$INTERNAL_SUBPATH,"
external_descriptor="${external_descriptor}[$HW1_FP$KEYPATH]$HW1_XPUB$EXTERNAL_SUBPATH,"
internal_descriptor="${internal_descriptor}[$HW1_FP$KEYPATH]$HW1_XPUB$INTERNAL_SUBPATH,"
external_descriptor="${external_descriptor}[$HW2_FP$KEYPATH]$HW2_XPUB$EXTERNAL_SUBPATH,"
internal_descriptor="${internal_descriptor}[$HW2_FP$KEYPATH]$HW2_XPUB$INTERNAL_SUBPATH,"
external_descriptor="${external_descriptor}[$HW3_FP$KEYPATH]$HW3_XPUB$EXTERNAL_SUBPATH"
internal_descriptor="${internal_descriptor}[$HW3_FP$KEYPATH]$HW3_XPUB$INTERNAL_SUBPATH"
# Close the sh/wsh/sortedmulti parens
external_descriptor="${external_descriptor})))"
internal_descriptor="${internal_descriptor})))"
# Validate the descriptors and update them to include a checksum
external_descriptor=$( bitcoin-cli getdescriptorinfo "$external_descriptor" | jq -r .descriptor )
if [ "$external_descriptor" == "" ]; then
echo "Error parsing external descriptor. Check fingerprints and keys for typos. Terminating script..."
exit 1
fi
internal_descriptor=$( bitcoin-cli getdescriptorinfo "$internal_descriptor" | jq -r .descriptor )
if [ "$internal_descriptor" == "" ]; then
echo "Error parsing internal descriptor. Check fingerprints and keys for typos. Terminating script..."
exit 1
fi
case "$1" in
get_descriptors)
echo "$external_descriptor"
echo "$internal_descriptor"
;;
derive_receiving_addresses)
# Only derive receiving (external) addresses
bitcoin-cli deriveaddresses "$external_descriptor" 1000
;;
# Note, if you get a "ripemd160 not supported error", see https://github.com/bitcoin-core/HWI/issues/656
verify_receive_address)
shift
if [ $# -lt 1 ] ; then
echo "$0 verify_receive_address <address> <hwi_arguments>"
exit 1
fi
ADDRESS="$1" ; shift
index=$( $0 derive_receiving_addresses | jq 'index("'$ADDRESS'")' )
if ! echo "$index" | grep -q '^[0-9]\+$' ; then
echo "Address not found: $ADDRESS"
exit 1
fi
# Our original ranged descriptor looks like:
# sh(wsh(sortedmulti(3,[.../49/0/0]xpub.../0/*,....
# We need a single-address descriptor that looks like:
# sh(wsh(sortedmulti(3,[.../49/0/0/]xpub.../0/$index,....
address_descriptor=$( echo "$external_descriptor" | sed """
# Remove the checksum
s/#.*//;
# Replace the range "*" with the particular index
s/\*/$index/g;
""" )
# Give the individual descriptor a checksum
address_descriptor=$( bitcoin-cli getdescriptorinfo "$address_descriptor" | jq -r .descriptor )
derived_address=$( bitcoin-cli deriveaddresses "$address_descriptor" | jq -r '.[]' )
if [ "$ADDRESS" != "$derived_address" ] ; then
echo "Something went wrong. Provided address $ADDRESS not the same as computed address $derived_address. Exiting..."
exit 1
fi
echo "Press enter to run the following command to verify address $ADDRESS:"
echo
echo " hwi" "$@" displayaddress --desc "$address_descriptor"
echo
echo "Press CTRL-C if any necessary parameters to hwi, like -t and -d, are missing"
echo "Also remember to unlock your device with its pin before pressing enter"
read
hwi "$@" displayaddress --desc "$address_descriptor"
;;
scan_utxos)
# NB: increase the ranges if you've sent or received more than that
# number of payments using your Casa wallet.
bitcoin-cli scantxoutset start '[{"desc": "'"$internal_descriptor"'", "range": 1000}, {"desc": "'"$external_descriptor"'", "range": 1000}]'
;;
*)
echo "Unknown command: $1"
exit 1
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment