Problem:
Chaumian eCash used in cashu and fedimint is custodial.
Research:
I researched HTLC, Chaumian eCash and Hawala to use certain procedures for solving this problem.
@moonsettler has documented a process to use eCash without custodial risk which helped me understand things better and come up with a solution. @ZmnSCPxj suggested a few ideas in a tweet thread. They could also be used to research further and implement better solutions.
Solution:
- Bob mints eCash tokens for Alice and adds (Bob, Carol, Dave) as redeemers in it.
A fork of moksha could be used for mint because it supports on-chain minting and melting
- Alice creates HTLC with a long preimage secret, her pubkey and redeemers pubkey. She shares
BOB123
part of preimage secret with Bob,CAROL456
with Dave andDAVE789
with Eve.
import hashlib
from bitcoin import SelectParams
from bitcoin.core import Hash160
from bitcoin.core.script import CScript, OP_HASH160, OP_SHA256, OP_EQUALVERIFY, OP_0, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_CHECKSEQUENCEVERIFY, OP_DROP, OP_EQUALVERIFY, OP_CHECKSIG
from bitcoin.wallet import P2WSHBitcoinAddress
SelectParams('signet')
preimage_secret = b"BOB123CAROL456DAVE789EVE012"
preimage = hashlib.sha256(preimage_secret).digest()
pubkey_alice = bytes.fromhex("03b8821767bb83f8928a25735d94ddd2f1f481ed82d3a05e8decdc926bac2e185a")
pubkey_redeemer = bytes.fromhex("03ee8232b06b14d1f996350345a51b715e3df265fec3209b73930dbe3f890f5403")
redeem_script = CScript([
OP_IF,
OP_SHA256, preimage, OP_EQUALVERIFY, OP_DUP, OP_HASH160, Hash160(pubkey_redeemer),
OP_ELSE,
2016, OP_CHECKSEQUENCEVERIFY, OP_DROP, OP_DUP, OP_HASH160, Hash160(pubkey_alice),
OP_ENDIF,
OP_EQUALVERIFY,
OP_CHECKSIG,
])
redeem_script_hash = hashlib.sha256(redeem_script).digest()
script_pubkey = CScript([OP_0, redeem_script_hash])
address = P2WSHBitcoinAddress.from_scriptPubKey(script_pubkey)
print(f"HTLC Address: {address}")
-
Alice gets the HTLC address. She funds it with some bitcoin based on eCash minted, transaction fees, redeem fees etc.
-
Alice uses eCash to pay Eve, shares 3 sha256 hashes and
EVE012
secret along with eCash:
Bob: c6f3e97b0314172f37f84b4383aed77532aa0ed46fe2a59479b81d24148dfe2e
Carol: a76a9c3dd985816d90f66aa679f165c53cbfdc7b26ee9395894a9c31797c3e53
Dave: b734eb0b425d9bb099506e18ebcb50f904d07de94f9d9b9ba0b56d54cc33a3a0
-
Eve verifies that eCash has not expired (2016 blocks). She accepts eCash payment and tries to re-issue eCash using it. Frank mints eCash for her and adds (Frank, Gracy, Henry) as redeemers in it.
-
Eve creates HTLC with a new
preimage_secret
:FRANK345GRACY678HENRY901IAN123
, her pubkey and redeemers pubkey. She shares the HTLC address with Bob. -
Bob creates a transaction to spend the HTLC that was created by Alice earlier and adds 4 outputs in it (1 HTLC created by Eve and others to pay redeem fees to self, Carol and Dave). He shares redeem script and signature with Eve after coordinating with other redeemers to get partial preimage.
If Bob, Carol or Dave are unable to coordinate and complete the preimage required for next steps then eCash would be invalidated and Alice gets back locked bitcoin after 2016 blocks.
txid = "1111111111111111111111111111111111111111111111111111111111111111"
vout = 0
amount_locked = int(1 * COIN)
redeem_fee_bob = int(0.001 * COIN)
redeem_fee_carol = int(0.0005 * COIN)
redeem_fee_dave = int(0.0005 * COIN)
total_redeem_fee = redeem_fee_bob + redeem_fee_carol + redeem_fee_dave
amount_eve_htlc = int(amount_locked - (total_redeem_fee + (0.0001 * COIN)))
txin = CMutableTxIn(COutPoint(lx(txid), vout))
txout_bob = CMutableTxOut(redeem_fee_bob, P2WPKHBitcoinAddress("tb1qq4kmxcjgfmeg2psvth2c905np4pctdeh8rd34x").to_scriptPubKey())
txout_carol = CMutableTxOut(redeem_fee_carol, P2WPKHBitcoinAddress("tb1qnef4evey7592xh56sdsenjr00c7vh2afh2947k").to_scriptPubKey())
txout_dave = CMutableTxOut(redeem_fee_dave, P2WPKHBitcoinAddress("tb1qe5hm92mmgfgflj8l3hrwe03zfme79pmkqr4x53").to_scriptPubKey())
txout_eve_htlc = CMutableTxOut(amount_eve_htlc, P2WPKHBitcoinAddress("tb1qcgrvanjt2y9tckvermnanwsex062vuhq3yqxss").to_scriptPubKey())
tx = CMutableTransaction([txin], [txout_bob, txout_carol, txout_dave, txout_eve_htlc])
sighash = SignatureHash(
script=redeem_script,
txTo=tx,
inIdx=0,
hashtype=SIGHASH_ALL,
amount=amount_locked,
sigversion=SIGVERSION_WITNESS_V0,
)
sig = CBitcoinSecret("cSCdFuVUYAEDhakyRJr4PXwhfwysnfF6khRkdyLnZPRyKFMjiVLq").sign(sighash) + bytes([SIGHASH_ALL])
- Eve adds
preimage_secret
required for witness and broadcasts the transaction
witness = CScriptWitness([sig, pubkey_redeemer, preimage_secret, b'\x01', redeem_script])
tx.wit = CTxWitness([CTxInWitness(witness)])
print("Serialized transaction: \n{}".format(b2x(tx.serialize())))
If Frank, Gracy or Henry are unable to coordinate and complete the preimage required for next steps then eCash would be invalidated and Eve gets back locked bitcoin after 2016 blocks.