CryptoNetHub

How I Built a Time-Locked Bitcoin Script with CSV and P2SH

HODLing is the beginning. But Bitcoin was meant to be programmed. “Not Just HODLing: Real Bitcoin Script Engineering” starts here. Enter fullscreen modeExit fullscreen modeWhy Read This Article?In the Bitcoin. . .

· 4 min read

How I Built a Time-Locked Bitcoin Script with CSV and P2SH

HODLing is the beginning. But Bitcoin was meant to be programmed. “Not Just HODLing: Real Bitcoin Script Engineering” starts here. Enter fullscreen modeExit fullscreen modeWhy Read This Article?In the Bitcoin development world, most tutorials only teach you concepts, not how to actually write code, and even fewer guide you to thoroughly dissect a real Bitcoin transaction from the perspective of on-chain data and script principles. This article uses a real case and complete code to help you master, in one go:P2SH script validation mechanismCSV (CheckSequenceVerify) timelock mechanismP2PKH signature validation mechanismYou’ll see not just code, but alsostep-by-step on-chain data animation, as well as debug ideas and common error analysis at every stage. Article Structure PreviewMotivation and Case OverviewBuilding and Spending the P2SH Script (Full Code)Three-Layer Script Mechanism BreakdownAnimated Stack Execution WalkthroughDebugging and On-Chain Data AnalysisSummary and Takeaways1. Motivation and Case OverviewThe goal of this case:Use Python to build a P2SH address that can only be spent after 3 blocks (a P2PKH script with a CSV timelock)Actually send and spend coins,broadcasting on-chain for realThrough this single case, help you fully understand P2SH, CSV, and P2PKH script mechanisms, and be able to reverse every step in a block explorerThe real on-chain transaction for this case:https://mempool. space/testnet/tx/34f5bf0cf328d77059b5674e71442ded8cdcfc723d0136733e0dbf180861906fI recommend opening this link as you read, and comparing each script and data step by step. 2. Building and Spending the P2SH Script (Full Code)1. Build the P2SH (P2PKH with CSV timelock)frombitcoinutils. setupimportsetupfrombitcoinutils. transactionsimportSequencefrombitcoinutils. keysimportP2shAddress,PrivateKeyfrombitcoinutils. scriptimportScriptfrombitcoinutils. constantsimportTYPE_RELATIVE_TIMELOCKimportos,sysimportconfigparserconf=configparser. ConfigParser()conf_file=os. path. join(os. path. abspath(os. path. dirname(sys. argv[0])),"wa_info. conf")conf. read(conf_file)defmain():setup("testnet")relative_blocks=3seq=Sequence(TYPE_RELATIVE_TIMELOCK,relative_blocks)p2pkh_sk=PrivateKey(conf. get("testnet3","private_key_wif"))p2pkh_addr=p2pkh_sk. get_public_key(). get_address()redeem_script=Script([seq. for_script(),"OP_CHECKSEQUENCEVERIFY","OP_DROP","OP_DUP","OP_HASH160",p2pkh_addr. to_hash160(),"OP_EQUALVERIFY","OP_CHECKSIG",])print(f"Redeem script (hex):{redeem_script. to_hex()}")addr=P2shAddress. from_script(redeem_script)print(f"P2SH address:{addr. to_string()}")conf. set('testnet3','p2sh_csv_addr',addr. to_string())withopen(conf_file,"w")asf:conf. write(f)if__name__=="__main__":main()Enter fullscreen modeExit fullscreen mode2. Spend the P2SH (spend script)frombitcoinutils. setupimportsetupfrombitcoinutils. utilsimportto_satoshisfrombitcoinutils. transactionsimportTransaction,TxInput,TxOutput,Sequencefrombitcoinutils. keysimportP2pkhAddress,PrivateKeyfrombitcoinutils. scriptimportScriptfrombitcoinutils. constantsimportTYPE_RELATIVE_TIMELOCKimportos,sysimportconfigparserimportrequestsconf=configparser. ConfigParser()conf_file=os. path. join(os. path. abspath(os. path. dirname(sys. argv[0])),"wa_info. conf")conf. read(conf_file)defmain():setup("testnet")relative_blocks=3txid="8e763d0269963e666949606e84e811a24df9cb525d7c436096b0c8a72be3533f"vout=0seq=Sequence(TYPE_RELATIVE_TIMELOCK,relative_blocks)seq_for_n_seq=seq. for_input_sequence()txin=TxInput(txid,vout,sequence=seq_for_n_seq)p2pkh_sk=PrivateKey(conf. get("testnet3","private_key_wif"))p2pkh_pk=p2pkh_sk. get_public_key(). to_hex()p2pkh_addr=p2pkh_sk. get_public_key(). get_address()redeem_script=Script([seq. for_script(),"OP_CHECKSEQUENCEVERIFY","OP_DROP","OP_DUP","OP_HASH160",p2pkh_addr. to_hash160(),"OP_EQUALVERIFY","OP_CHECKSIG",])to_addr=P2pkhAddress("mn3XG8tZQjyUsoAmECTYFqVUECNJzEdapj")txout=TxOutput(to_satoshis(0. 00000588),to_addr. to_script_pub_key())tx=Transaction([txin],[txout])print("Raw unsigned transaction:",tx. serialize())sig=p2pkh_sk. sign_input(tx,0,redeem_script)txin. script_sig=Script([sig,p2pkh_pk,redeem_script. to_hex()])signed_tx=tx. serialize()print("Raw signed transaction:",signed_tx)print("TxId:",tx. get_txid())mempool_api=""try:response=requests. post(mempool_api,data=signed_tx)ifresponse. status_code==200:txid=response. textprint(f"Success! TxID:{txid}")print(f"View transaction: ")else:print(f"Broadcast failed:{response. text}")exceptExceptionase:print(f"Error:{e}")if__name__=="__main__":main()Enter fullscreen modeExit fullscreen mode3. Three-Layer Script Mechanism Breakdown1. P2SH Validation (Outer Layer)On-chain locking script (scriptPubKey):OP_HASH160 OP_EQUALEnter fullscreen modeExit fullscreen modePurpose: Checks if the redeem script you provide matches the hash locked in the output. 2. CSV Timelock Validation (Middle Layer)First part of the redeem script:3 OP_CHECKSEQUENCEVERIFY OP_DROPEnter fullscreen modeExit fullscreen modePurpose: Only if nSequence is at least 3 (i. e. , after 3 blocks), the script continues. 3. P2PKH Signature Validation (Inner Layer)Second part of the redeem script:OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIGEnter fullscreen modeExit fullscreen modePurpose: Standard Bitcoin signature validation. 4. Animated Stack Execution Walkthrough1. P2SH Validation StageStep 1: ScriptSig pushes redeem script┌───────────────────────────────┐ │53b27576. . . 88ac (redeem script)│ ├───────────────────────────────┤ │. . . (pubkey, signature) │ └───────────────────────────────┘Enter fullscreen modeExit fullscreen modeStep 2: OP_HASH160┌───────────────────────────────┐ │79b6f3. . . cabaadcc (redeem script HASH160)│ ├───────────────────────────────┤ │. . . (pubkey, signature) │ └───────────────────────────────┘Enter fullscreen modeExit fullscreen modeStep 3: OP_PUSHBYTES_20┌───────────────────────────────┐ │79b6f3. . . cabaadcc (script hash in output)│ ├───────────────────────────────┤ │79b6f3. . . cabaadcc (redeem script HASH160)│ ├───────────────────────────────┤ │. . . (pubkey, signature) │ └───────────────────────────────┘Enter fullscreen modeExit fullscreen modeStep 4: OP_EQUALCompares the top two hashes. If equal, continue; otherwise, fail. 2. CSV Timelock Validation StageStep 1: Push 3 (CSV lock value)┌────────────┐ │3 │ ├────────────┤ │0250be. . . │ ├────────────┤ │30440220. . . │ └────────────┘Enter fullscreen modeExit fullscreen modeStep 2: OP_CHECKSEQUENCEVERIFYIf nSequence OP_EQUALStep 3: Manually step through script executionUse ScriptSig data to execute P2SH validation (OP_HASH160 + OP_EQUAL)If valid, execute redeem script: first CSV timelock (OP_CHECKSEQUENCEVERIFY), then P2PKH signature checkUse the stack animation above to follow each stepStep 4: Compare HEX and ASMIn the block explorer, find the HEX and ASM for ScriptSig and scriptPubKeyMatch each byte and opcode to your code and the execution steps3. Debugging TipsWhen you see errors, check mempool. space orblockstream. infofor actual on-chain dataPrint raw transactions and script contents at each step, and compare with on-chain dataIf script execution fails, draw the stack changes by hand to find where it went wrong4. Real Case AnalysisFor this case, you can see onmempool. space:Each ScriptSig item (signature, pubkey, redeem script)The scriptPubKey hashThe actual execution order and stack changesIf you spend too early, you’ll see non-BIP68-finalWith this code-onchain-debug workflow, you can not only write scripts, but also understand and debug every on-chain transaction. 6. Summary and TakeawaysThrough a real case and code, you have mastered:P2SH script validationCSV timelock mechanismP2PKH signature validationAnimated stack-based script executionDebugging and on-chain data analysisThis is the essence ofReal Bitcoin Script Engineering. Source CodeAll code and on-chain data analysis are available at mygithubrepoFeel free to Star and Fork!Referencespython-bitcoin-utils official examplesA Guide to creating TapRoot Scripts with bitcoinjs-libOn-chain case at mempool. spaceByAaron RecompileonJune 29, 2025. Canonical linkExported fromMediumon July 3, 2026.

Source: dev.to

More Bitcoin news