Skip to content

Instantly share code, notes, and snippets.

@moonsettler
Last active April 3, 2024 11:10
Show Gist options
  • Save moonsettler/42b588fa97a1da3ac0adea0dd16dadf2 to your computer and use it in GitHub Desktop.
Save moonsettler/42b588fa97a1da3ac0adea0dd16dadf2 to your computer and use it in GitHub Desktop.
Bitcoin denominated ecash without custodial risk

Bitcoin denominated ecash without custodial risk

In this scheme the ecash notes don't represent IOUs, the spent ecash notes represent the liability of the User towards the Mint

Abstract

Bitcoin denominated ecash credit secured by publicly arbitrated escrow providing symmetric trust/incentive relationship between Mint and User. Works similar to a credit card top-up scheme. Instead of depositing bitcoin to the Mint to get ecash issued, the Mint issues credit in the form of ecash. The spent ecash tokens represent the User's liability towards the Mint. The User must periodically provide proof of the unspent balance (turning in expired tokens, which can no longer be spent) and settle the difference by paying a Lightning Network invoice to get a new batch of tokens issued.

Motivation

Lightning Network interoperable Chaumian ecash implemented on bitcoin provides excellent privacy, but many have complained about the Mint's ability to rugpull the Users. There is also an increased regulatory risk concerning custodians, that provide anonymous payment services.

Mechanism

The Bank/Mint extends you credit in the form of ecash, and the Users spend that.

The User and the Mint form a multi-sig escrow with a specific amount, but that TXO is not touched. The User gets ecash notes/tokens issued each month. When they expire, the Users have to turn in their remaining notes and settle the outstanding balance over lightning or by other means, to get a new batch of ecash tokens issued. It's like any p2p escrow solution.

Escrow and dispute resolution

Dispute resolution can be arbitrarily complicated and decentralized. Reputable trusted arbitration Oracle Services may arise (that don't actually custody user funds so they are not a financial provider) could slip under the radar. Typically the User and the Mint would agree on a set of trusted Arbiters to handle dispute over the escrow.

Opening an account

When opening an account the User will deposit the limit + penalty amount into the escrow contract. After that limit amount of ecash is issued to the User. Limits would typically be selected from a standard set of amounts to increase the anonymity set. Before funding the 2-of-2 escrow contract, both parties sign and retain a transaction that spends it over to the arbitration contract. SIGHASH SINGLE|ANYONECANPAY ensures that the escrow input and arbitration output are signed but fees can be paid by additional inputs. This allows for the initiating party to pay the current feerate at the time.

Spend and Receive

Spending and Receiving works exactly the same as with custodial ecash.

Re-up the balance

To re-up the balance first the User will ask for a re-up invoice for the spent amount in the previous month. This will give him a full limit amount of ecash tokens/notes. Then the User turns these tokens in by signing an authentication message with his escrow key. This does not allow the Mint to learn how much of the balance each User actually spent, as they always turn in a full balance, unless they wish to close the account and cooperatively settle on-chain.

Fines and Interest

When the User is not able to present the exact full balance amount, the User will have to pay interest on the outstanding sum. For example this interest could be 5%. Same thing happens if the User has too much ecash from the previous month, the User pays a fine which is again 5% of the surplus amount. After deducting the fine or interest from the presented amount, the User is issued new ecash notes in the remaining sum.

Closing the account

Alternatively instead of getting new ecash tokens issued, the User may request an on-chain settlement with the Mint and thus closing the account. In case of a dispute or non-cooperation a presigned transaction will spend the 2-of-2 escrow to the arbitration contract (for example 3-of-4 with the 2 Oracles), which can have relative timelocks on exit conditions. In case an Oracle Service goes out of business the Mint and the User can always sign a transaction to a new arbitration contract, with a new set of Oracles they can agree on.

@oflowshow
Copy link

This is an interesting way to introduce credit into the system while minimizing credit risk.

@1440000bytes
Copy link

Basic PoC without arbitration contract:

image

from bitcoin.wallet import P2SHBitcoinAddress
from bitcoin.core.script import CScript, OP_CHECKMULTISIG
import bitcoin
import requests
import time
import sys
import json

bitcoin.SelectParams('signet')

def generate_multisig_address(user_public_key):
    mint_public_key = "029d0a21422cd7a35be06ebe8f5796d310500a553022e41ea1c5d4481f0337860a"

    user_pubkey_bytes = bytes.fromhex(user_public_key)
    mint_pubkey_bytes = bytes.fromhex(mint_public_key)

    multisig_script = CScript([user_pubkey_bytes, mint_pubkey_bytes, 2, OP_CHECKMULTISIG])
    p2sh_address = P2SHBitcoinAddress.from_redeemScript(multisig_script)

    return p2sh_address

def check_utxos(multisig_address):
    url = f"https://mempool.space/signet/api/address/{multisig_address}/utxo"
    response = requests.get(url)
    utxos = response.json()
    if utxos:
        print("\nUTXO found for multisig address:")
        for utxo in utxos:
            print(utxo)
            amount = utxo['value']
            mint_ecash(amount)
        return True

def check_utxos_continuously(multisig_address):
    while True:
        if check_utxos(multisig_address):
            break 
        for _ in range(6):
            time.sleep(10)
            sys.stdout.write("|")
            sys.stdout.flush()

def mint_ecash(value):
    url = f"http://127.0.0.1:4448/send?amount={value}&nosplit=false"
    response = requests.post(url, headers={}, json="")  
    if response.status_code == 200:
        print(response.json().get('token')) 
    else:
        print("Error:", response.text)

user_public_key = input("Enter your public key: ")
multisig_address = generate_multisig_address(user_public_key)
print("Multisig P2SH Address:", multisig_address)
check_utxos_continuously(multisig_address)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment