Open-source 2-of-3 policy-enforced threshold HSM: auto-signs cold→hot treasury refills under on-device Coldcard policy, no human in the loop. Includes the full operator manual + quick-start, the reference coordinator/signing code, and a signer-host bootstrap. No keys, seeds, or secrets — placeholders only. Live signet demo: https://multisighsm.mineracks.com Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
41 lines
2.2 KiB
Python
41 lines
2.2 KiB
Python
import subprocess, json, os, time
|
|
V='27.0'; DATA=os.path.expanduser('~/cksim/regtest-data')
|
|
CLI=os.path.expanduser(f'~/bitcoin-{V}/bin/bitcoin-cli')
|
|
def bc(*a, wallet=None):
|
|
base=[CLI,f'-datadir={DATA}','-rpcport=18999','-rpcuser=ck','-rpcpassword=ckms']
|
|
if wallet is not None: base.append(f'-rpcwallet={wallet}')
|
|
r=subprocess.run(base+list(a),capture_output=True,text=True)
|
|
if r.returncode!=0: raise SystemExit(f'CLI {a} FAIL: {r.stderr.strip()}')
|
|
return r.stdout.strip()
|
|
TPUB={'00000001':'tpubREPLACE_WITH_YOUR_SIGNER_1_XPUB',
|
|
'00000002':'tpubREPLACE_WITH_YOUR_SIGNER_2_XPUB',
|
|
'00000003':'tpubREPLACE_WITH_YOUR_SIGNER_3_XPUB'}
|
|
def desc(branch):
|
|
keys=','.join(f'[{x}/48h/1h/0h/2h]{p}/{branch}/*' for x,p in TPUB.items())
|
|
return f'wsh(sortedmulti(2,{keys}))'
|
|
for i in range(40):
|
|
try: bc('getblockchaininfo'); break
|
|
except SystemExit: time.sleep(2)
|
|
def withck(d): return json.loads(bc('getdescriptorinfo', d))['descriptor']
|
|
rcv, chg = withck(desc(0)), withck(desc(1))
|
|
# miner wallet (has keys) to fund
|
|
wl=json.loads(bc('listwallets'))
|
|
if 'miner' not in wl: bc('createwallet','miner')
|
|
if 'ckms23-watch' not in wl: bc('createwallet','ckms23-watch','true','true','','false','true') # disable_privkeys, blank, ..., descriptors
|
|
bc('importdescriptors', json.dumps([{'desc':rcv,'active':True,'internal':False,'timestamp':'now','range':[0,20]},
|
|
{'desc':chg,'active':True,'internal':True,'timestamp':'now','range':[0,20]}]), wallet='ckms23-watch')
|
|
maddr=bc('getnewaddress','','bech32', wallet='ckms23-watch')
|
|
print('MULTISIG_ADDR', maddr)
|
|
mineaddr=bc('getnewaddress', wallet='miner')
|
|
bc('generatetoaddress','101',mineaddr, wallet='miner')
|
|
bc('sendtoaddress',maddr,'2.5', wallet='miner')
|
|
bc('generatetoaddress','1',mineaddr, wallet='miner')
|
|
bal=bc('getbalance', wallet='ckms23-watch')
|
|
print('WATCH_BALANCE', bal)
|
|
dest=bc('getnewaddress', wallet='miner')
|
|
funded=json.loads(bc('walletcreatefundedpsbt','[]',json.dumps([{dest:1.0}]),'0',json.dumps({'fee_rate':2,'change_type':'bech32'}), wallet='ckms23-watch'))
|
|
open(os.path.expanduser('~/cksim/unsigned.psbt'),'w').write(funded['psbt'])
|
|
print('UNSIGNED_PSBT_BYTES', len(funded['psbt']))
|
|
print('PSBT_HEAD', funded['psbt'][:40])
|
|
print('OK')
|