Skip to content

Instantly share code, notes, and snippets.

@harding
Created March 6, 2024 19:55

Revisions

  1. harding created this gist Mar 6, 2024.
    164 changes: 164 additions & 0 deletions casa-helper.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,164 @@
    #!/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