"""Isolated Litecoin Core wallet-file recovery drill. No real funds or external peers. Usage: python core-recovery-test.py litecoind litecoin-cli result.json Requires verified Core 0.21.5.8 binaries. Public results never contain private keys. This tests legacy wallet-file RPC workflows, not seed phrases, GUI, QR or MWEB. """ import datetime,hashlib,json,pathlib,socket,subprocess,sys,tempfile,time def main(): daemon,cli,out=map(pathlib.Path,sys.argv[1:4]) root=pathlib.Path(tempfile.mkdtemp(prefix='lw-recovery-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)] process=subprocess.Popen([str(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,wallet=None): result=subprocess.run([str(cli),*opts,*(['-rpcwallet='+wallet] if wallet else []),method,*[json.dumps(a,separators=(',',':')) if isinstance(a,(dict,list,bool)) else str(a) for a in args]],capture_output=True,text=True,timeout=45,creationflags=getattr(subprocess,'CREATE_NO_WINDOW',0)) if result.returncode:raise RuntimeError(method+': '+result.stderr) try:return json.loads(result.stdout) except json.JSONDecodeError:return result.stdout.strip() cases=[] def check(label,condition,detail): if not condition:raise AssertionError(label) cases.append({'test':label,'passed':True,'detail':detail}) try: for _ in range(90): try:version=rpc('getnetworkinfo')['subversion'];break except Exception:time.sleep(.3) else:raise RuntimeError('Core did not start') check('Isolated regtest',rpc('getblockchaininfo')['chain']=='regtest' and rpc('getconnectioncount')==0,'No external peer connections; freshly generated regtest chain.') rpc('createwallet','sender');rpc('createwallet','receiver') mine=rpc('getnewaddress','','bech32',wallet='sender');rpc('generatetoaddress',101,mine) addresses={};funding=[] for kind in ['legacy','p2sh-segwit','bech32']: address=rpc('getnewaddress','recovery-'+kind,kind,wallet='receiver');addresses[kind]=address txid=rpc('sendtoaddress',address,1,wallet='sender');funding.append(txid) check('Receive address: '+kind,rpc('getaddressinfo',address,wallet='receiver')['ismine'],'Address is owned by the fresh receiver wallet.') rpc('generatetoaddress',1,mine) check('Three payments received',rpc('getbalance',wallet='receiver')==3,'Three separate confirmed payments of 1 regtest LTC.') # Encrypt before backup; the restored wallet must remain locked. passphrase='isolated-regtest-only-not-a-user-password' rpc('encryptwallet',passphrase,wallet='receiver') backup=root/'receiver-backup.dat';rpc('backupwallet',str(backup),wallet='receiver');rpc('unloadwallet','receiver') restored=root/'regtest'/'wallets'/'restored';restored.mkdir(parents=True) import shutil shutil.copy2(backup,restored/'wallet.dat');rpc('loadwallet','restored') check('Balance restored',rpc('getbalance',wallet='restored')==3,'Loaded an independent copy of the encrypted wallet backup on the same regtest chain.') check('Encryption preserved',rpc('getwalletinfo',wallet='restored').get('unlocked_until')==0,'Restored wallet is locked.') for kind,address in addresses.items():check('Ownership restored: '+kind,rpc('getaddressinfo',address,wallet='restored')['ismine'],'Backup retains the receiving key and script.') rpc('walletpassphrase',passphrase,60,wallet='restored') for kind,address in addresses.items(): utxo=rpc('listunspent',1,9999999,[address],wallet='restored')[0] destination=rpc('getnewaddress','','bech32',wallet='sender') raw=rpc('createrawtransaction',[{'txid':utxo['txid'],'vout':utxo['vout']}],{destination:.9999}) signed=rpc('signrawtransactionwithwallet',raw,wallet='restored');accepted=rpc('testmempoolaccept',[signed['hex']])[0] check('Spend after restore: '+kind,signed['complete'] and accepted['allowed'],'Signed with restored wallet; regtest mempool policy accepted the candidate. Fixed 0.0001 LTC test fee; not a fee recommendation.') if kind=='bech32': sent=rpc('sendrawtransaction',signed['hex']);rpc('generatetoaddress',1,mine) check('Confirmed send after restore',rpc('gettransaction',sent,wallet='restored')['confirmations']==1,'One restored-wallet candidate was broadcast and mined only on the isolated regtest chain.') rpc('walletlock',wallet='restored') result={'schema':1,'testedAt':datetime.datetime.now(datetime.timezone.utc).isoformat(),'version':version,'network':'regtest','platform':sys.platform,'binarySha256':hashlib.sha256(daemon.read_bytes()).hexdigest(),'scope':'Encrypted legacy wallet-file backup and recovery through Core RPC; three standard address types. No mainnet, device GUI, mnemonic, QR or MWEB interoperability test.','cases':cases} out.write_text(json.dumps(result,indent=2),encoding='utf-8');print(json.dumps({'passed':len(cases),'result':str(out),'version':version})) finally: try:rpc('stop') except Exception:process.terminate() process.wait(timeout=30) if __name__=='__main__':main()