"""Reproduce a 2-of-3 legacy P2SH Litecoin regtest experiment. No mainnet funds. Usage: python multisig-regtest.py /path/to/litecoind[.exe] /path/to/litecoin-cli[.exe] Requires Python 3 and verified Litecoin Core v0.21.5.8 binaries. Creates a new temporary datadir. """ import subprocess,sys,tempfile,time,json,itertools,pathlib,socket,datetime def main(): daemon,cli=sys.argv[1:3] root=pathlib.Path(tempfile.mkdtemp(prefix='ltc-multisig-regtest-')) with socket.socket() as sock:sock.bind(('127.0.0.1',0));port=sock.getsockname()[1] opts=['-regtest','-datadir='+str(root),'-rpcport='+str(port)] proc=subprocess.Popen([daemon,*opts,'-listen=0','-connect=0','-dnsseed=0','-discover=0','-upnp=0','-printtoconsole=0'],stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,creationflags=getattr(subprocess,'CREATE_NO_WINDOW',0)) def rpc(method,*args): v=subprocess.run([cli,*opts,method,*[json.dumps(a,separators=(',',':')) if isinstance(a,(list,dict,bool)) else str(a) for a in args]],capture_output=True,text=True) if v.returncode:raise RuntimeError(v.stderr) try:return json.loads(v.stdout) except json.JSONDecodeError:return v.stdout.strip() try: for _ in range(60): try:version=rpc('getnetworkinfo')['subversion'];break except Exception:time.sleep(.5) else:raise RuntimeError('Regtest did not start') rpc('createwallet','lab') mine=rpc('getnewaddress','','legacy');rpc('generatetoaddress',101,mine) pub=[];priv=[] for _ in range(3): a=rpc('getnewaddress','','legacy');pub.append(rpc('getaddressinfo',a)['pubkey']);priv.append(rpc('dumpprivkey',a)) m=rpc('createmultisig',2,pub,'legacy') txid=rpc('sendtoaddress',m['address'],1);rpc('generatetoaddress',1,mine) funding=rpc('decoderawtransaction',rpc('gettransaction',txid)['hex']) output=next(v for v in funding['vout'] if m['address'] in v['scriptPubKey'].get('addresses',[])) raw=rpc('createrawtransaction',[{'txid':txid,'vout':output['n']}],{rpc('getnewaddress','','legacy'):0.9999}) prev=[{'txid':txid,'vout':output['n'],'scriptPubKey':output['scriptPubKey']['hex'],'redeemScript':m['redeemScript'],'amount':1}] results=[] for count in range(4): for subset in itertools.combinations(range(3),count): signed=rpc('signrawtransactionwithkey',raw,[priv[i] for i in subset],prev) accepted=rpc('testmempoolaccept',[signed['hex']])[0] assert bool(signed['complete'])==(count>=2) assert bool(accepted['allowed'])==(count>=2) results.append({'keys':''.join('ABC'[i] for i in subset) or 'none','complete':signed['complete'],'mempool_allowed':accepted['allowed'],'reject_reason':accepted.get('reject-reason'),'vsize':rpc('decoderawtransaction',signed['hex'])['vsize']}) result={'version':version,'network':'regtest','address_type':'legacy P2SH','threshold':'2-of-3','value_ltc':'1.00000000','fee_ltc':'0.00010000','tested':datetime.datetime.now(datetime.timezone.utc).date().isoformat(),'cases':results,'public_keys':pub,'redeem_script':m['redeemScript'],'note':'Eight independent candidates spend the same unspent test output. None broadcast. Fee chosen for the test, not a recommendation.'} print(json.dumps(result,indent=2)) finally: try:rpc('stop') except Exception:proc.terminate() proc.wait(timeout=30) if __name__=='__main__':main()