print("Unsigned Refund Transaction Fee:", unsigned_refund_transaction.fee()) print("Unsigned Refund Transaction Hash:", unsigned_refund_transaction.hash()) print("Unsigned Refund Transaction Raw:", unsigned_refund_transaction.raw()) # print("Unsigned Refund Transaction Json:", json.dumps(unsigned_refund_transaction.json(), indent=4)) print("Unsigned Refund Transaction Type:", unsigned_refund_transaction.type()) unsigned_fund_raw = unsigned_refund_transaction.unsigned_raw() print("Unsigned Fund Transaction Unsigned Raw:", unsigned_fund_raw) print("=" * 10, "Signed Refund Transaction") # Initializing refund solver refund_solver = RefundSolver( private_key=sender_private_key, secret_hash=sha256("Hello Meheret!".encode()).hex(), recipient_address=recipient_address, sender_address=sender_address, sequence=1000 ) # Singing unsigned claim transaction signed_refund_transaction = unsigned_refund_transaction.sign(refund_solver) print("Signed Refund Transaction Fee:", signed_refund_transaction.fee()) print("Signed Refund Transaction Hash:", signed_refund_transaction.hash()) print("Signed Refund Transaction Raw:", signed_refund_transaction.raw()) # print("Signed Refund Transaction Json:", json.dumps(signed_refund_transaction.json(), indent=4)) print("Signed Refund Transaction Type:", signed_refund_transaction.type()) print("=" * 10, "Refund Signature")
def test_bitcoin_refund(): # Initialization refund transaction unsigned_refund_transaction = RefundTransaction(version=2, network=network) # Building refund transaction unsigned_refund_transaction.build_transaction( transaction_id=fund_transaction_id, wallet=recipient_wallet, amount=2000 ) assert unsigned_refund_transaction.fee == 576 assert unsigned_refund_transaction.hash() == "0a22fd29c2d2a8f0e162028737c8cbc1ea9e266d5f4cf42248aa22c1c96d1e15" assert unsigned_refund_transaction.raw() == \ "0200000001888be7ec065097d95664763f276d425552d735fb1d974ae78bf72106dca0f3910000000000ffffffff0190050000" \ "000000001976a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac00000000" assert unsigned_refund_transaction.json() unsigned_refund_raw = unsigned_refund_transaction.unsigned_raw() assert unsigned_refund_raw == \ "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMTg4OGJlN2VjMDY1MDk3ZDk1NjY0NzYzZjI3NmQ0MjU1NTJkNzM1ZmIxZDk3NG" \ "FlNzhiZjcyMTA2ZGNhMGYzOTEwMDAwMDAwMDAwZmZmZmZmZmYwMTkwMDUwMDAwMDAwMDAwMDAxOTc2YTkxNDY0YTgzOTBiMGIxNjg1" \ "ZmNiZjJkNGI0NTcxMThkYzhkYTkyZDU1MzQ4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IFt7ImFtb3VudCI6IDIwMDAsICJuIjogMC" \ "wgInNjcmlwdCI6ICJhOTE0NmYwOGIyNTRlNGM1OGRjNjVmNmYzOTljM2JlNzE3N2I5MDFmNGE2Njg3In1dLCAicmVjaXBpZW50X2Fk" \ "ZHJlc3MiOiAibXVUbmZmTERSNUx0RmVMUjJpM1dzS1ZmZHl2emZ5UG5WQiIsICJzZW5kZXJfYWRkcmVzcyI6ICJtcGhCUFpmMTVjUk" \ "ZjTDV0VXE2bUNiRTg0WG9iWjF2ZzdRIiwgInNlY3JldCI6IG51bGwsICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRj" \ "b2luX3JlZnVuZF91bnNpZ25lZCJ9" # Refunding HTLC solver refund_solver = RefundSolver( secret="Hello Meheret!", private_key=sender_private_key ) signed_refund_transaction = unsigned_refund_transaction.sign(refund_solver) assert signed_refund_transaction.fee assert signed_refund_transaction.hash() assert signed_refund_transaction.raw() assert signed_refund_transaction.json() # Singing Hash Time Lock Contract (HTLC) refund_signature = RefundSignature(network=network)\ .sign(unsigned_raw=unsigned_refund_raw, solver=refund_solver) signature = Signature(network=network) \ .sign(unsigned_raw=unsigned_refund_raw, solver=refund_solver) assert signature.fee == refund_signature.fee == signed_refund_transaction.fee == 576 assert signature.hash() == refund_signature.hash() == signed_refund_transaction.hash() == \ "cab253322cfeaa066b0d52ab6f4a18a035d723271d4bbfb39d43d0aa0ca86c2e" assert signature.raw() == refund_signature.raw() == signed_refund_transaction.raw() == \ "0200000001888be7ec065097d95664763f276d425552d735fb1d974ae78bf72106dca0f39100000000ca483045022100ce6ad4" \ "458c1ba40b4d08f02b5bdf4aa0a2f73abf9cd7af3c4a1909187f00b7810220049a4733b994732fe52afd78b714bce067a4d31b" \ "e4b02716f4fb7384f53786160121039213ebcaefdd3e109720c17867ce1bd6d076b0e65e3b6390e6e38548a65e76af004c5c63" \ "aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a03" \ "27b792fbe332b888ac670164b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac686400000001900500000000" \ "00001976a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac00000000" assert signature.json() == refund_signature.json() == signed_refund_transaction.json() signed_refund_raw = refund_signature.signed_raw() assert signature.signed_raw() == signed_refund_raw == \ "eyJyYXciOiAiMDIwMDAwMDAwMTg4OGJlN2VjMDY1MDk3ZDk1NjY0NzYzZjI3NmQ0MjU1NTJkNzM1ZmIxZDk3NGFlNzhiZjcyMTA2ZG" \ "NhMGYzOTEwMDAwMDAwMGNhNDgzMDQ1MDIyMTAwY2U2YWQ0NDU4YzFiYTQwYjRkMDhmMDJiNWJkZjRhYTBhMmY3M2FiZjljZDdhZjNj" \ "NGExOTA5MTg3ZjAwYjc4MTAyMjAwNDlhNDczM2I5OTQ3MzJmZTUyYWZkNzhiNzE0YmNlMDY3YTRkMzFiZTRiMDI3MTZmNGZiNzM4NG" \ "Y1Mzc4NjE2MDEyMTAzOTIxM2ViY2FlZmRkM2UxMDk3MjBjMTc4NjdjZTFiZDZkMDc2YjBlNjVlM2I2MzkwZTZlMzg1NDhhNjVlNzZh" \ "ZjAwNGM1YzYzYWEyMDgyMTEyNGI1NTRkMTNmMjQ3YjFlNWQxMGI4NGU0NGZiMTI5NmYxOGYzOGJiYWExYmVhMzRhMTJjODQzZTAxNT" \ "g4ODc2YTkxNDk4Zjg3OWZiN2Y4YjQ5NTFkZWU5YmM4YTAzMjdiNzkyZmJlMzMyYjg4OGFjNjcwMTY0YjI3NTc2YTkxNDY0YTgzOTBi" \ "MGIxNjg1ZmNiZjJkNGI0NTcxMThkYzhkYTkyZDU1MzQ4OGFjNjg2NDAwMDAwMDAxOTAwNTAwMDAwMDAwMDAwMDE5NzZhOTE0NjRhOD" \ "M5MGIwYjE2ODVmY2JmMmQ0YjQ1NzExOGRjOGRhOTJkNTUzNDg4YWMwMDAwMDAwMCIsICJmZWUiOiA1NzYsICJuZXR3b3JrIjogInRl" \ "c3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX3JlZnVuZF9zaWduZWQifQ=="
def test_signature_exceptions(): with pytest.raises( ValueError, match= "invalid network, please choose only mainnet or testnet networks"): Signature(network="solonet") with pytest.raises(ValueError, match="transaction script is none, sign first"): Signature().hash() with pytest.raises(ValueError, match="transaction script is none, sign first"): Signature().json() with pytest.raises( ValueError, match="transaction script is none, build transaction first"): Signature().raw() with pytest.raises(ValueError, match="not found type, sign first"): Signature().type() with pytest.raises(ValueError, match="there is no signed data, sign first"): Signature().signed_raw() with pytest.raises( TypeError, match=r"invalid Bitcoin fund unsigned transaction type, .*"): FundSignature().sign( unsigned_raw= "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMDAwMDAwMDAwZmZmZmZmZmYwMWQwMjQwMDAwMDAwMDAwMDAxOTc2YTkxNDY0YTgzOTBiMGIxNjg1ZmNiZjJkNGI0NTcxMThkYzhkYTkyZDU1MzQ4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiAxMDAwMCwgIm4iOiAwLCAic2NyaXB0X3B1YmtleSI6ICJhOTE0MmJiMDEzYzNlNGJlYjA4NDIxZGVkY2Y4MTVjYjY1YTVjMzg4MTc4Yjg3In0sICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX3JlZnVuZF91bnNpZ25lZCJ9", solver=FundSolver(private_key=sender_wallet.private_key())) with pytest.raises( TypeError, match=r"invalid Bitcoin claim unsigned transaction type, .*"): ClaimSignature().sign( unsigned_raw= "eyJmZWUiOiA2NzgsICJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMTAwMDAwMDAwZmZmZmZmZmYwMjEwMjcwMDAwMDAwMDAwMDAxN2E5MTQyYmIwMTNjM2U0YmViMDg0MjFkZWRjZjgxNWNiNjVhNWMzODgxNzhiODc1MDhhMGUwMDAwMDAwMDAwMTk3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYzAwMDAwMDAwIiwgIm91dHB1dHMiOiBbeyJhbW91bnQiOiA5NjM1OTAsICJuIjogMSwgInNjcmlwdCI6ICI3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYyJ9XSwgIm5ldHdvcmsiOiAidGVzdG5ldCIsICJ0eXBlIjogImJpdGNvaW5fZnVuZF91bnNpZ25lZCJ9", solver=ClaimSolver(private_key=recipient_wallet.private_key(), secret="Hello Meheret!", secret_hash=sha256( "Hello Meheret!".encode()).hex(), recipient_address=recipient_wallet.address(), sender_address=sender_wallet.address(), sequence=1000)) with pytest.raises( TypeError, match=r"invalid Bitcoin refund unsigned transaction type, .*"): RefundSignature().sign( unsigned_raw= "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMDAwMDAwMDAwZmZmZmZmZmYwMWQwMjQwMDAwMDAwMDAwMDAxOTc2YTkxNDk4Zjg3OWZiN2Y4YjQ5NTFkZWU5YmM4YTAzMjdiNzkyZmJlMzMyYjg4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsiYW1vdW50IjogMTAwMDAsICJuIjogMCwgInNjcmlwdCI6ICJhOTE0MmJiMDEzYzNlNGJlYjA4NDIxZGVkY2Y4MTVjYjY1YTVjMzg4MTc4Yjg3In0sICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX2NsYWltX3Vuc2lnbmVkIn0", solver=RefundSolver(private_key=sender_wallet.private_key(), secret_hash=sha256( "Hello Meheret!".encode()).hex(), recipient_address=recipient_wallet.address(), sender_address=sender_wallet.address(), sequence=1000)) with pytest.raises(ValueError, match="invalid Bitcoin unsigned transaction raw"): Signature().sign("eyJtIjogImFzZCJ9", "") with pytest.raises(ValueError, match="invalid Bitcoin unsigned fund transaction raw"): FundSignature().sign("eyJtIjogImFzZCJ9", "") with pytest.raises(ValueError, match="invalid Bitcoin unsigned claim transaction raw"): ClaimSignature().sign("eyJtIjogImFzZCJ9", "") with pytest.raises( ValueError, match="invalid Bitcoin unsigned refund transaction raw"): RefundSignature().sign("eyJtIjogImFzZCJ9", "") with pytest.raises( TypeError, match= "invalid Bitcoin solver, it's only takes Bitcoin FundSolver class" ): FundSignature().sign( unsigned_raw= "eyJmZWUiOiA2NzgsICJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMTAwMDAwMDAwZmZmZmZmZmYwMjEwMjcwMDAwMDAwMDAwMDAxN2E5MTQyYmIwMTNjM2U0YmViMDg0MjFkZWRjZjgxNWNiNjVhNWMzODgxNzhiODc1MDhhMGUwMDAwMDAwMDAwMTk3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYzAwMDAwMDAwIiwgIm91dHB1dHMiOiBbeyJhbW91bnQiOiA5NjM1OTAsICJuIjogMSwgInNjcmlwdCI6ICI3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYyJ9XSwgIm5ldHdvcmsiOiAidGVzdG5ldCIsICJ0eXBlIjogImJpdGNvaW5fZnVuZF91bnNpZ25lZCJ9", solver=RefundSolver(private_key=sender_wallet.private_key(), secret_hash=sha256( "Hello Meheret!".encode()).hex(), recipient_address=recipient_wallet.address(), sender_address=sender_wallet.address(), sequence=1000)) with pytest.raises( TypeError, match= "invalid Bitcoin solver, it's only takes Bitcoin ClaimSolver class" ): ClaimSignature().sign( unsigned_raw= "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMDAwMDAwMDAwZmZmZmZmZmYwMWQwMjQwMDAwMDAwMDAwMDAxOTc2YTkxNDk4Zjg3OWZiN2Y4YjQ5NTFkZWU5YmM4YTAzMjdiNzkyZmJlMzMyYjg4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsiYW1vdW50IjogMTAwMDAsICJuIjogMCwgInNjcmlwdCI6ICJhOTE0MmJiMDEzYzNlNGJlYjA4NDIxZGVkY2Y4MTVjYjY1YTVjMzg4MTc4Yjg3In0sICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX2NsYWltX3Vuc2lnbmVkIn0", solver=FundSolver(private_key=sender_wallet.private_key())) with pytest.raises( TypeError, match= "invalid Bitcoin solver, it's only takes Bitcoin RefundSolver class" ): RefundSignature().sign( unsigned_raw= "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMDAwMDAwMDAwZmZmZmZmZmYwMWQwMjQwMDAwMDAwMDAwMDAxOTc2YTkxNDY0YTgzOTBiMGIxNjg1ZmNiZjJkNGI0NTcxMThkYzhkYTkyZDU1MzQ4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiAxMDAwMCwgIm4iOiAwLCAic2NyaXB0X3B1YmtleSI6ICJhOTE0MmJiMDEzYzNlNGJlYjA4NDIxZGVkY2Y4MTVjYjY1YTVjMzg4MTc4Yjg3In0sICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX3JlZnVuZF91bnNpZ25lZCJ9", solver=ClaimSolver(private_key=recipient_wallet.private_key(), secret="Hello Meheret!", secret_hash=sha256( "Hello Meheret!".encode()).hex(), recipient_address=recipient_wallet.address(), sender_address=sender_wallet.address(), sequence=1000))
def sign(private, raw, bytecode, secret, sequence, version): if len(private) != 64: click.echo( click.style("Error: {}").format("invalid Bitcoin private key"), err=True) sys.exit() # Cleaning unsigned raw unsigned_raw = str(raw + "=" * (-len(raw) % 4)) try: transaction = json.loads(b64decode(unsigned_raw.encode()).decode()) except (binascii.Error, json.decoder.JSONDecodeError) as exception: click.echo(click.style("Error: {}").format( "invalid Bitcoin unsigned transaction raw"), err=True) sys.exit() if "type" not in transaction or "network" not in transaction: click.echo(click.style("Warning: {}", fg="yellow").format( "there is no type & network provided in Bitcoin unsigned transaction raw" ), err=True) click.echo(click.style("Error: {}").format( "invalid Bitcoin unsigned transaction raw"), err=True) sys.exit() try: if transaction["type"] == "bitcoin_fund_unsigned": # Fund HTLC solver fund_solver = FundSolver(private_key=private) # Fund signature fund_signature = FundSignature(network=transaction["network"], version=version) fund_signature.sign(unsigned_raw=unsigned_raw, solver=fund_solver) click.echo(fund_signature.signed_raw()) elif transaction["type"] == "bitcoin_claim_unsigned": if secret is None: click.echo(click.style("Error: {}").format( "secret key is required for claim, use -s or --secret \"Hello Meheret!\"" ), err=True) sys.exit() if bytecode is None: click.echo(click.style("Error: {}").format( "witness bytecode is required for claim, use -b or --bytecode \"016...\"" ), err=True) sys.exit() # Claim HTLC solver claim_solver = ClaimSolver(private_key=private, secret=secret, bytecode=bytecode) # Claim signature claim_signature = ClaimSignature(network=transaction["network"], version=version) claim_signature.sign(unsigned_raw=unsigned_raw, solver=claim_solver) click.echo(claim_signature.signed_raw()) elif transaction["type"] == "bitcoin_refund_unsigned": if bytecode is None: click.echo(click.style("Error: {}").format( "witness bytecode is required for refund, use -b or --bytecode \"016...\"" ), err=True) sys.exit() # Refunding HTLC solver refund_solver = RefundSolver(private_key=private, sequence=int(sequence), bytecode=bytecode) # Refund signature refund_signature = RefundSignature(network=transaction["network"], version=version) refund_signature.sign(unsigned_raw=unsigned_raw, solver=refund_solver) click.echo(refund_signature.signed_raw()) else: click.echo(click.style("Error: {}").format( "unknown Bitcoin unsigned transaction raw type"), err=True) sys.exit() except Exception as exception: click.echo(click.style("Error: {}").format(str(exception)), err=True) sys.exit()
def test_bitcoin_refund_signature(): unsigned_refund_transaction_raw = "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMDAwMDAwMDAwZmZmZmZmZmYwMWQwMjQwMDAwMDAwMDAwMDAxOTc2YTkxNDY0YTgzOTBiMGIxNjg1ZmNiZjJkNGI0NTcxMThkYzhkYTkyZDU1MzQ4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiAxMDAwMCwgIm4iOiAwLCAic2NyaXB0X3B1YmtleSI6ICJhOTE0MmJiMDEzYzNlNGJlYjA4NDIxZGVkY2Y4MTVjYjY1YTVjMzg4MTc4Yjg3In0sICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX3JlZnVuZF91bnNpZ25lZCJ9" signature = Signature(version=2, network=network).sign( unsigned_raw=unsigned_refund_transaction_raw, solver=RefundSolver(private_key=sender_wallet.private_key(), secret_hash=sha256( "Hello Meheret!".encode()).hex(), recipient_address=recipient_wallet.address(), sender_address=sender_wallet.address(), sequence=1000)) refund_signature = RefundSignature(version=2, network=network).sign( unsigned_raw=unsigned_refund_transaction_raw, solver=RefundSolver(private_key=sender_wallet.private_key(), secret_hash=sha256( "Hello Meheret!".encode()).hex(), recipient_address=recipient_wallet.address(), sender_address=sender_wallet.address(), sequence=1000)) assert signature.fee() == refund_signature.fee() == 576 assert signature.hash() == refund_signature.hash( ) == "9b429fdff11ccb19e4642521fed4ae7d89129c49a08214f41e709bd3e2a0e4f5" assert signature.raw() == refund_signature.raw( ) == "0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef300000000ca47304402200b0fc3b3b891761e5cbee5bc1f3c6d7b0904c13475eef5b7965c9bfda1d08a2a02205ebdc72cb763a2e7f290787b8d7defd972b41a2b4dc1c499d69991f0e4506659012103c56a6005d4a8892d28cc3f7265e5685b548627d59108973e474c4e26f69a4c84004c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68e803000001d0240000000000001976a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac00000000" assert signature.json() == refund_signature.json() == { 'hex': '0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef300000000ca47304402200b0fc3b3b891761e5cbee5bc1f3c6d7b0904c13475eef5b7965c9bfda1d08a2a02205ebdc72cb763a2e7f290787b8d7defd972b41a2b4dc1c499d69991f0e4506659012103c56a6005d4a8892d28cc3f7265e5685b548627d59108973e474c4e26f69a4c84004c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68e803000001d0240000000000001976a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac00000000', 'txid': '9b429fdff11ccb19e4642521fed4ae7d89129c49a08214f41e709bd3e2a0e4f5', 'hash': '9b429fdff11ccb19e4642521fed4ae7d89129c49a08214f41e709bd3e2a0e4f5', 'size': 287, 'vsize': 287, 'version': 2, 'locktime': 0, 'vin': [{ 'txid': 'f34eb8e86f7753e681dc44387f7d14ab624b3418498d23f6157b38d8922e31ec', 'vout': 0, 'scriptSig': { 'asm': '304402200b0fc3b3b891761e5cbee5bc1f3c6d7b0904c13475eef5b7965c9bfda1d08a2a02205ebdc72cb763a2e7f290787b8d7defd972b41a2b4dc1c499d69991f0e450665901 03c56a6005d4a8892d28cc3f7265e5685b548627d59108973e474c4e26f69a4c84 OP_0 63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68', 'hex': '47304402200b0fc3b3b891761e5cbee5bc1f3c6d7b0904c13475eef5b7965c9bfda1d08a2a02205ebdc72cb763a2e7f290787b8d7defd972b41a2b4dc1c499d69991f0e4506659012103c56a6005d4a8892d28cc3f7265e5685b548627d59108973e474c4e26f69a4c84004c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68' }, 'sequence': '1000' }], 'vout': [{ 'value': '0.00009424', 'n': 0, 'scriptPubKey': { 'asm': 'OP_DUP OP_HASH160 64a8390b0b1685fcbf2d4b457118dc8da92d5534 OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac', 'type': 'p2pkh', 'address': 'mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q' } }] } assert signature.type() == refund_signature.type( ) == "bitcoin_refund_signed" assert signature.signed_raw() == refund_signature.signed_raw( ) == "eyJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMDAwMDAwMGNhNDczMDQ0MDIyMDBiMGZjM2IzYjg5MTc2MWU1Y2JlZTViYzFmM2M2ZDdiMDkwNGMxMzQ3NWVlZjViNzk2NWM5YmZkYTFkMDhhMmEwMjIwNWViZGM3MmNiNzYzYTJlN2YyOTA3ODdiOGQ3ZGVmZDk3MmI0MWEyYjRkYzFjNDk5ZDY5OTkxZjBlNDUwNjY1OTAxMjEwM2M1NmE2MDA1ZDRhODg5MmQyOGNjM2Y3MjY1ZTU2ODViNTQ4NjI3ZDU5MTA4OTczZTQ3NGM0ZTI2ZjY5YTRjODQwMDRjNWQ2M2FhMjA4MjExMjRiNTU0ZDEzZjI0N2IxZTVkMTBiODRlNDRmYjEyOTZmMThmMzhiYmFhMWJlYTM0YTEyYzg0M2UwMTU4ODg3NmE5MTQ5OGY4NzlmYjdmOGI0OTUxZGVlOWJjOGEwMzI3Yjc5MmZiZTMzMmI4ODhhYzY3MDJlODAzYjI3NTc2YTkxNDY0YTgzOTBiMGIxNjg1ZmNiZjJkNGI0NTcxMThkYzhkYTkyZDU1MzQ4OGFjNjhlODAzMDAwMDAxZDAyNDAwMDAwMDAwMDAwMDE5NzZhOTE0NjRhODM5MGIwYjE2ODVmY2JmMmQ0YjQ1NzExOGRjOGRhOTJkNTUzNDg4YWMwMDAwMDAwMCIsICJmZWUiOiA1NzYsICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX3JlZnVuZF9zaWduZWQifQ=="
def sign(private, raw, secret, version): if secret is None: secret = str() if len(private) != 64: click.echo(click.style("Error: {}") .format("invalid bitcoin private key"), err=True) sys.exit() unsigned_raw = str(raw + "=" * (-len(raw) % 4)) try: transaction = json.loads(b64decode(unsigned_raw.encode()).decode()) except (binascii.Error, json.decoder.JSONDecodeError) as exception: click.echo(click.style("Error: {}") .format("invalid bitcoin unsigned transaction raw"), err=True) sys.exit() if "type" not in transaction or "network" not in transaction: click.echo(click.style("Warning: {}", fg="yellow") .format("there is no type & network provided in bitcoin unsigned transaction raw"), err=True) click.echo(click.style("Error: {}") .format("invalid bitcoin unsigned transaction raw"), err=True) sys.exit() if transaction["type"] == "bitcoin_fund_unsigned": # Fund HTLC solver fund_solver = FundSolver(private_key=private) try: # Fund signature fund_signature = FundSignature(network=transaction["network"], version=version) fund_signature.sign(unsigned_raw=unsigned_raw, solver=fund_solver) click.echo(fund_signature.signed_raw()) except Exception as exception: click.echo(click.style("Error: {}").format(str(exception)), err=True) sys.exit() elif transaction["type"] == "bitcoin_claim_unsigned": if secret != str(): _secret = secret elif "secret" not in transaction or transaction["secret"] is None: click.echo(click.style("Warning: {}") .format("secret key is empty, use -s or --secret \"Hello Meheret!\""), err=False) _secret = str() else: _secret = transaction["secret"] # Claim HTLC solver claim_solver = ClaimSolver( secret=_secret, private_key=private ) try: # Claim signature claim_signature = ClaimSignature(network=transaction["network"], version=version) claim_signature.sign(unsigned_raw=unsigned_raw, solver=claim_solver) click.echo(claim_signature.signed_raw()) except Exception as exception: click.echo(click.style("Error: {}").format(str(exception)), err=True) sys.exit() elif transaction["type"] == "bitcoin_refund_unsigned": if secret != str(): _secret = secret elif "secret" not in transaction or transaction["secret"] is None: click.echo(click.style("Warning: {}") .format("secret key is empty, use -s or --secret \"Hello Meheret!\""), err=False) _secret = str() else: _secret = transaction["secret"] # Refunding HTLC solver refund_solver = RefundSolver( secret=_secret, private_key=private ) try: # Refund signature refund_signature = RefundSignature(network=transaction["network"], version=version) refund_signature.sign(unsigned_raw=unsigned_raw, solver=refund_solver) click.echo(refund_signature.signed_raw()) except Exception as exception: click.echo(click.style("Error: {}").format(str(exception)), err=True) sys.exit()
def test_bitcoin_refund_solver(): htlc_refund_solver = RefundSolver( private_key=sender_wallet.private_key(), # Witness from HTLC agreements secret_hash=sha256("Hello Meheret!".encode()).hex(), recipient_address=recipient_wallet.address(), sender_address=sender_wallet.address(), sequence=1000) assert sender_wallet.private_key( ) == htlc_refund_solver.private_key.hexlify() assert isinstance(htlc_refund_solver.solve(), IfElseSolver) assert isinstance(htlc_refund_solver.witness("testnet"), IfElseScript) bytecode_refund_solver = RefundSolver( private_key=sender_wallet.private_key(), # Witness from HTLC bytecode bytecode= "63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8" "b4951dee9bc8a0327b792fbe332b888ac670164b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68" ) assert sender_wallet.private_key( ) == bytecode_refund_solver.private_key.hexlify() assert isinstance(bytecode_refund_solver.solve(), IfElseSolver) assert isinstance(bytecode_refund_solver.witness("testnet"), IfElseScript) with pytest.raises(TypeError, match="private key must be string format"): RefundSolver(int()) with pytest.raises(TypeError, match="secret hash must be string format"): RefundSolver(str(), int()) with pytest.raises(ValueError, match="invalid secret hash, length must be 64"): RefundSolver(str(), str()) with pytest.raises(AddressError, match=r"invalid recipient *.* address"): RefundSolver(str(), sha256(b"Hello Meheret!").hex(), "adsfsdfsd") with pytest.raises(AddressError, match=r"invalid sender *.* address"): RefundSolver(str(), sha256(b"Hello Meheret!").hex(), "2N729UBGZB3xjsGFRgKivy4bSjkaJGMVSpB", "adsfsdfsd") with pytest.raises(TypeError, match="sequence must be integer format"): RefundSolver(str(), sha256(b"Hello Meheret!").hex(), "2N729UBGZB3xjsGFRgKivy4bSjkaJGMVSpB", "2N729UBGZB3xjsGFRgKivy4bSjkaJGMVSpB", float()) with pytest.raises(TypeError, match="bytecode must be string format"): RefundSolver(str(), bytecode=123423423423)
def test_bitcoin_refund_transaction(): unsigned_refund_transaction = RefundTransaction(version=2, network=network) unsigned_refund_transaction.build_transaction( transaction_id=transaction_id, wallet=sender_wallet, amount=10_000) assert unsigned_refund_transaction.fee() == 576 assert unsigned_refund_transaction.hash( ) == "a2022290e62f4073bc642d6b45f92ec3686c6524d0ef3d67d9edfd5f7dab0ea1" assert unsigned_refund_transaction.raw( ) == "0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef30000000000ffffffff01d0240000000000001976a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac00000000" assert unsigned_refund_transaction.json() == { 'hex': '0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef30000000000ffffffff01d0240000000000001976a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac00000000', 'txid': 'a2022290e62f4073bc642d6b45f92ec3686c6524d0ef3d67d9edfd5f7dab0ea1', 'hash': 'a2022290e62f4073bc642d6b45f92ec3686c6524d0ef3d67d9edfd5f7dab0ea1', 'size': 85, 'vsize': 85, 'version': 2, 'locktime': 0, 'vin': [{ 'txid': 'f34eb8e86f7753e681dc44387f7d14ab624b3418498d23f6157b38d8922e31ec', 'vout': 0, 'scriptSig': { 'asm': '', 'hex': '' }, 'sequence': '4294967295' }], 'vout': [{ 'value': '0.00009424', 'n': 0, 'scriptPubKey': { 'asm': 'OP_DUP OP_HASH160 64a8390b0b1685fcbf2d4b457118dc8da92d5534 OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac', 'type': 'p2pkh', 'address': 'mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q' } }] } assert unsigned_refund_transaction.type() == "bitcoin_refund_unsigned" assert unsigned_refund_transaction.unsigned_raw( ) == "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMDAwMDAwMDAwZmZmZmZmZmYwMWQwMjQwMDAwMDAwMDAwMDAxOTc2YTkxNDY0YTgzOTBiMGIxNjg1ZmNiZjJkNGI0NTcxMThkYzhkYTkyZDU1MzQ4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsidmFsdWUiOiAxMDAwMCwgIm4iOiAwLCAic2NyaXB0X3B1YmtleSI6ICJhOTE0MmJiMDEzYzNlNGJlYjA4NDIxZGVkY2Y4MTVjYjY1YTVjMzg4MTc4Yjg3In0sICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX3JlZnVuZF91bnNpZ25lZCJ9" signed_refund_transaction = unsigned_refund_transaction.sign( solver=RefundSolver(private_key=sender_wallet.private_key(), secret_hash=sha256( "Hello Meheret!".encode()).hex(), recipient_address=recipient_wallet.address(), sender_address=sender_wallet.address(), sequence=1000)) assert signed_refund_transaction.fee() == 576 assert signed_refund_transaction.hash( ) == "9b429fdff11ccb19e4642521fed4ae7d89129c49a08214f41e709bd3e2a0e4f5" assert signed_refund_transaction.raw( ) == "0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef300000000ca47304402200b0fc3b3b891761e5cbee5bc1f3c6d7b0904c13475eef5b7965c9bfda1d08a2a02205ebdc72cb763a2e7f290787b8d7defd972b41a2b4dc1c499d69991f0e4506659012103c56a6005d4a8892d28cc3f7265e5685b548627d59108973e474c4e26f69a4c84004c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68e803000001d0240000000000001976a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac00000000" assert signed_refund_transaction.json() == { 'hex': '0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef300000000ca47304402200b0fc3b3b891761e5cbee5bc1f3c6d7b0904c13475eef5b7965c9bfda1d08a2a02205ebdc72cb763a2e7f290787b8d7defd972b41a2b4dc1c499d69991f0e4506659012103c56a6005d4a8892d28cc3f7265e5685b548627d59108973e474c4e26f69a4c84004c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68e803000001d0240000000000001976a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac00000000', 'txid': '9b429fdff11ccb19e4642521fed4ae7d89129c49a08214f41e709bd3e2a0e4f5', 'hash': '9b429fdff11ccb19e4642521fed4ae7d89129c49a08214f41e709bd3e2a0e4f5', 'size': 287, 'vsize': 287, 'version': 2, 'locktime': 0, 'vin': [{ 'txid': 'f34eb8e86f7753e681dc44387f7d14ab624b3418498d23f6157b38d8922e31ec', 'vout': 0, 'scriptSig': { 'asm': '304402200b0fc3b3b891761e5cbee5bc1f3c6d7b0904c13475eef5b7965c9bfda1d08a2a02205ebdc72cb763a2e7f290787b8d7defd972b41a2b4dc1c499d69991f0e450665901 03c56a6005d4a8892d28cc3f7265e5685b548627d59108973e474c4e26f69a4c84 OP_0 63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68', 'hex': '47304402200b0fc3b3b891761e5cbee5bc1f3c6d7b0904c13475eef5b7965c9bfda1d08a2a02205ebdc72cb763a2e7f290787b8d7defd972b41a2b4dc1c499d69991f0e4506659012103c56a6005d4a8892d28cc3f7265e5685b548627d59108973e474c4e26f69a4c84004c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68' }, 'sequence': '1000' }], 'vout': [{ 'value': '0.00009424', 'n': 0, 'scriptPubKey': { 'asm': 'OP_DUP OP_HASH160 64a8390b0b1685fcbf2d4b457118dc8da92d5534 OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac', 'type': 'p2pkh', 'address': 'mphBPZf15cRFcL5tUq6mCbE84XobZ1vg7Q' } }] } assert signed_refund_transaction.type() == "bitcoin_refund_signed"
wallet=recipient_wallet, amount=5000) print("Unsigned Refund Transaction Fee:", unsigned_refund_transaction.fee) print("Unsigned Refund Transaction Hash:", unsigned_refund_transaction.hash()) print("Unsigned Refund Transaction Raw:", unsigned_refund_transaction.raw()) # print("Unsigned Refund Transaction Json:", json.dumps(unsigned_refund_transaction.json(), indent=4)) unsigned_fund_raw = unsigned_refund_transaction.unsigned_raw() print("Unsigned Fund Transaction Unsigned Raw:", unsigned_fund_raw) print("=" * 10, "Signed Refund Transaction") # Refunding HTLC solver refund_solver = RefundSolver(secret="Hello Meheret!", private_key=sender_private_key, sequence=5) signed_refund_transaction = unsigned_refund_transaction.sign(refund_solver) print("Signed Refund Transaction Fee:", signed_refund_transaction.fee) print("Signed Refund Transaction Hash:", signed_refund_transaction.hash()) print("Signed Refund Transaction Raw:", signed_refund_transaction.raw()) # print("Signed Refund Transaction Json:", json.dumps(signed_refund_transaction.json(), indent=4)) print("=" * 10, "Refund Signature") # Singing Hash Time Lock Contract (HTLC) refund_signature = RefundSignature(network=network)\ .sign(unsigned_raw=unsigned_fund_raw, solver=refund_solver)