amount=700 ) print("Unsigned Claim Transaction Fee:", unsigned_claim_transaction.fee) print("Unsigned Claim Transaction Hash:", unsigned_claim_transaction.hash()) print("Unsigned Claim Transaction Raw:", unsigned_claim_transaction.raw()) # print("Unsigned Claim Transaction Json:", json.dumps(unsigned_claim_transaction.json(), indent=4)) unsigned_claim_raw = unsigned_claim_transaction.unsigned_raw() print("Unsigned Claim Transaction Unsigned Raw:", unsigned_claim_raw) print("=" * 10, "Signed Claim Transaction") # Claiming HTLC solver claim_solver = ClaimSolver( secret="Hello Meheret!", private_key=recipient_private_key ) signed_claim_transaction = unsigned_claim_transaction.sign(claim_solver) print("Signed Claim Transaction Fee:", signed_claim_transaction.fee) print("Signed Claim Transaction Hash:", signed_claim_transaction.hash()) print("Signed Claim Transaction Raw:", signed_claim_transaction.raw()) # print("Signed Claim Transaction Json:", json.dumps(signed_claim_transaction.json(), indent=4)) print("=" * 10, "Claim Signature") # Singing Hash Time Lock Contract (HTLC) claim_signature = ClaimSignature(network=network)\ .sign(unsigned_raw=unsigned_claim_raw, solver=claim_solver)
def test_bitcoin_claim(): # Initialization claim transaction unsigned_claim_transaction = ClaimTransaction(version=2, network=network) # Building claim transaction unsigned_claim_transaction.build_transaction( transaction_id=fund_transaction_id, wallet=recipient_wallet, amount=2000) assert unsigned_claim_transaction.fee == 576 assert unsigned_claim_transaction.hash( ) == "726e390af02215d346be089dff566ae070f7332e8927d83acbb40b0e9105a787" assert unsigned_claim_transaction.raw() == \ "0200000001888be7ec065097d95664763f276d425552d735fb1d974ae78bf72106dca0f3910000000000ffffffff019005000" \ "0000000001976a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac00000000" assert unsigned_claim_transaction.json() unsigned_claim_raw = unsigned_claim_transaction.unsigned_raw() assert unsigned_claim_raw == \ "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMTg4OGJlN2VjMDY1MDk3ZDk1NjY0NzYzZjI3NmQ0MjU1NTJkNzM1ZmIxZDk3N" \ "GFlNzhiZjcyMTA2ZGNhMGYzOTEwMDAwMDAwMDAwZmZmZmZmZmYwMTkwMDUwMDAwMDAwMDAwMDAxOTc2YTkxNDk4Zjg3OWZiN2Y4Yj" \ "Q5NTFkZWU5YmM4YTAzMjdiNzkyZmJlMzMyYjg4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IFt7ImFtb3VudCI6IDIwMDAsICJuIjo" \ "gMCwgInNjcmlwdCI6ICJhOTE0NmYwOGIyNTRlNGM1OGRjNjVmNmYzOTljM2JlNzE3N2I5MDFmNGE2Njg3In1dLCAicmVjaXBpZW50" \ "X2FkZHJlc3MiOiAibXVUbmZmTERSNUx0RmVMUjJpM1dzS1ZmZHl2emZ5UG5WQiIsICJzZW5kZXJfYWRkcmVzcyI6ICJtcGhCUFpmM" \ "TVjUkZjTDV0VXE2bUNiRTg0WG9iWjF2ZzdRIiwgInNlY3JldCI6IG51bGwsICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6IC" \ "JiaXRjb2luX2NsYWltX3Vuc2lnbmVkIn0=" # Claiming HTLC solver claim_solver = ClaimSolver(secret="Hello Meheret!", private_key=recipient_private_key) signed_claim_transaction = unsigned_claim_transaction.sign(claim_solver) assert signed_claim_transaction.fee assert signed_claim_transaction.hash() assert signed_claim_transaction.raw() assert signed_claim_transaction.json() # Singing Hash Time Lock Contract (HTLC) claim_signature = ClaimSignature(network=network)\ .sign(unsigned_raw=unsigned_claim_raw, solver=claim_solver) signature = Signature(network=network) \ .sign(unsigned_raw=unsigned_claim_raw, solver=claim_solver) assert signature.fee == claim_signature.fee == signed_claim_transaction.fee == 576 assert signature.hash() == claim_signature.hash() == signed_claim_transaction.hash() == \ "910a173757d59492a6e807bf270650e950dde7949d540e70cc0ce5123008a52c" assert signature.raw() == claim_signature.raw() == signed_claim_transaction.raw() == \ "0200000001888be7ec065097d95664763f276d425552d735fb1d974ae78bf72106dca0f39100000000d847304402204e20eeb" \ "3cb82dbe51f7929cd4e819891102d2d7a280919013ae31f12aa49212602206fbce96d734823579e6a4bb4b517d618dca47606" \ "2f54d9df06aee4b73d08eae20121039213ebcaefdd3e109720c17867ce1bd6d076b0e65e3b6390e6e38548a65e76af0e48656" \ "c6c6f204d65686572657421514c5c63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e015888" \ "76a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac670164b27576a91464a8390b0b1685fcbf2d4b457118dc8da92" \ "d553488ac68ffffffff0190050000000000001976a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac00000000" assert signature.json() == claim_signature.json( ) == signed_claim_transaction.json() signed_claim_raw = claim_signature.signed_raw() assert signature.signed_raw() == signed_claim_raw == \ "eyJyYXciOiAiMDIwMDAwMDAwMTg4OGJlN2VjMDY1MDk3ZDk1NjY0NzYzZjI3NmQ0MjU1NTJkNzM1ZmIxZDk3NGFlNzhiZjcyMTA2Z" \ "GNhMGYzOTEwMDAwMDAwMGQ4NDczMDQ0MDIyMDRlMjBlZWIzY2I4MmRiZTUxZjc5MjljZDRlODE5ODkxMTAyZDJkN2EyODA5MTkwMT" \ "NhZTMxZjEyYWE0OTIxMjYwMjIwNmZiY2U5NmQ3MzQ4MjM1NzllNmE0YmI0YjUxN2Q2MThkY2E0NzYwNjJmNTRkOWRmMDZhZWU0Yjc" \ "zZDA4ZWFlMjAxMjEwMzkyMTNlYmNhZWZkZDNlMTA5NzIwYzE3ODY3Y2UxYmQ2ZDA3NmIwZTY1ZTNiNjM5MGU2ZTM4NTQ4YTY1ZTc2" \ "YWYwZTQ4NjU2YzZjNmYyMDRkNjU2ODY1NzI2NTc0MjE1MTRjNWM2M2FhMjA4MjExMjRiNTU0ZDEzZjI0N2IxZTVkMTBiODRlNDRmY" \ "jEyOTZmMThmMzhiYmFhMWJlYTM0YTEyYzg0M2UwMTU4ODg3NmE5MTQ5OGY4NzlmYjdmOGI0OTUxZGVlOWJjOGEwMzI3Yjc5MmZiZT" \ "MzMmI4ODhhYzY3MDE2NGIyNzU3NmE5MTQ2NGE4MzkwYjBiMTY4NWZjYmYyZDRiNDU3MTE4ZGM4ZGE5MmQ1NTM0ODhhYzY4ZmZmZmZ" \ "mZmYwMTkwMDUwMDAwMDAwMDAwMDAxOTc2YTkxNDk4Zjg3OWZiN2Y4YjQ5NTFkZWU5YmM4YTAzMjdiNzkyZmJlMzMyYjg4OGFjMDAw" \ "MDAwMDAiLCAiZmVlIjogNTc2LCAibmV0d29yayI6ICJ0ZXN0bmV0IiwgInR5cGUiOiAiYml0Y29pbl9jbGFpbV9zaWduZWQifQ=="
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()
print("Unsigned Claim Transaction Fee:", unsigned_claim_transaction.fee()) print("Unsigned Claim Transaction Hash:", unsigned_claim_transaction.hash()) print("Unsigned Claim Transaction Raw:", unsigned_claim_transaction.raw()) # print("Unsigned Claim Transaction Json:", json.dumps(unsigned_claim_transaction.json(), indent=4)) print("Unsigned Claim Transaction Type:", unsigned_claim_transaction.type()) unsigned_claim_raw = unsigned_claim_transaction.unsigned_raw() print("Unsigned Claim Transaction Unsigned Raw:", unsigned_claim_raw) print("=" * 10, "Signed Claim Transaction") # Initializing claim solver claim_solver = ClaimSolver(private_key=recipient_private_key, secret="Hello Meheret!", secret_hash=sha256("Hello Meheret!".encode()).hex(), recipient_address=recipient_address, sender_address=sender_address, sequence=1000) # Singing unsigned claim transaction signed_claim_transaction = unsigned_claim_transaction.sign(solver=claim_solver) print("Signed Claim Transaction Fee:", signed_claim_transaction.fee()) print("Signed Claim Transaction Hash:", signed_claim_transaction.hash()) print("Signed Claim Transaction Raw:", signed_claim_transaction.raw()) # print("Signed Claim Transaction Json:", json.dumps(signed_claim_transaction.json(), indent=4)) print("Signed Claim Transaction Type:", signed_claim_transaction.type()) print("=" * 10, "Claim Signature") # Initializing claim signature
def test_bitcoin_claim_signature(): unsigned_claim_transaction_raw = "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMDAwMDAwMDAwZmZmZmZmZmYwMWQwMjQwMDAwMDAwMDAwMDAxOTc2YTkxNDk4Zjg3OWZiN2Y4YjQ5NTFkZWU5YmM4YTAzMjdiNzkyZmJlMzMyYjg4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsiYW1vdW50IjogMTAwMDAsICJuIjogMCwgInNjcmlwdCI6ICJhOTE0MmJiMDEzYzNlNGJlYjA4NDIxZGVkY2Y4MTVjYjY1YTVjMzg4MTc4Yjg3In0sICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX2NsYWltX3Vuc2lnbmVkIn0=" signature = Signature(version=2, network=network).sign( unsigned_raw=unsigned_claim_transaction_raw, 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)) claim_signature = ClaimSignature(version=2, network=network).sign( unsigned_raw=unsigned_claim_transaction_raw, 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)) assert signature.fee() == claim_signature.fee() == 576 assert signature.hash() == claim_signature.hash( ) == "8f98079b6257d65abc2c1c1a14c3bff50a6be949e75a30c127b3a2c0618012e1" assert signature.raw() == claim_signature.raw( ) == "0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef300000000d9473044022060291b5a87474f775dda5c244b21fc2716bfa09c4636ea4c707918c9f759374e02201ef5e768af10d01a1031294e952f6a8ed5c0a75e23f58aee062a89fed2134af70121039213ebcaefdd3e109720c17867ce1bd6d076b0e65e3b6390e6e38548a65e76af0e48656c6c6f204d65686572657421514c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68ffffffff01d0240000000000001976a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac00000000" assert signature.json() == claim_signature.json() == { 'hex': '0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef300000000d9473044022060291b5a87474f775dda5c244b21fc2716bfa09c4636ea4c707918c9f759374e02201ef5e768af10d01a1031294e952f6a8ed5c0a75e23f58aee062a89fed2134af70121039213ebcaefdd3e109720c17867ce1bd6d076b0e65e3b6390e6e38548a65e76af0e48656c6c6f204d65686572657421514c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68ffffffff01d0240000000000001976a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac00000000', 'txid': '8f98079b6257d65abc2c1c1a14c3bff50a6be949e75a30c127b3a2c0618012e1', 'hash': '8f98079b6257d65abc2c1c1a14c3bff50a6be949e75a30c127b3a2c0618012e1', 'size': 302, 'vsize': 302, 'version': 2, 'locktime': 0, 'vin': [{ 'txid': 'f34eb8e86f7753e681dc44387f7d14ab624b3418498d23f6157b38d8922e31ec', 'vout': 0, 'scriptSig': { 'asm': '3044022060291b5a87474f775dda5c244b21fc2716bfa09c4636ea4c707918c9f759374e02201ef5e768af10d01a1031294e952f6a8ed5c0a75e23f58aee062a89fed2134af701 039213ebcaefdd3e109720c17867ce1bd6d076b0e65e3b6390e6e38548a65e76af 48656c6c6f204d65686572657421 OP_1 63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68', 'hex': '473044022060291b5a87474f775dda5c244b21fc2716bfa09c4636ea4c707918c9f759374e02201ef5e768af10d01a1031294e952f6a8ed5c0a75e23f58aee062a89fed2134af70121039213ebcaefdd3e109720c17867ce1bd6d076b0e65e3b6390e6e38548a65e76af0e48656c6c6f204d65686572657421514c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68' }, 'sequence': '4294967295' }], 'vout': [{ 'value': '0.00009424', 'n': 0, 'scriptPubKey': { 'asm': 'OP_DUP OP_HASH160 98f879fb7f8b4951dee9bc8a0327b792fbe332b8 OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac', 'type': 'p2pkh', 'address': 'muTnffLDR5LtFeLR2i3WsKVfdyvzfyPnVB' } }] } assert signature.type() == claim_signature.type() == "bitcoin_claim_signed" assert signature.signed_raw() == claim_signature.signed_raw( ) == "eyJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMDAwMDAwMGQ5NDczMDQ0MDIyMDYwMjkxYjVhODc0NzRmNzc1ZGRhNWMyNDRiMjFmYzI3MTZiZmEwOWM0NjM2ZWE0YzcwNzkxOGM5Zjc1OTM3NGUwMjIwMWVmNWU3NjhhZjEwZDAxYTEwMzEyOTRlOTUyZjZhOGVkNWMwYTc1ZTIzZjU4YWVlMDYyYTg5ZmVkMjEzNGFmNzAxMjEwMzkyMTNlYmNhZWZkZDNlMTA5NzIwYzE3ODY3Y2UxYmQ2ZDA3NmIwZTY1ZTNiNjM5MGU2ZTM4NTQ4YTY1ZTc2YWYwZTQ4NjU2YzZjNmYyMDRkNjU2ODY1NzI2NTc0MjE1MTRjNWQ2M2FhMjA4MjExMjRiNTU0ZDEzZjI0N2IxZTVkMTBiODRlNDRmYjEyOTZmMThmMzhiYmFhMWJlYTM0YTEyYzg0M2UwMTU4ODg3NmE5MTQ5OGY4NzlmYjdmOGI0OTUxZGVlOWJjOGEwMzI3Yjc5MmZiZTMzMmI4ODhhYzY3MDJlODAzYjI3NTc2YTkxNDY0YTgzOTBiMGIxNjg1ZmNiZjJkNGI0NTcxMThkYzhkYTkyZDU1MzQ4OGFjNjhmZmZmZmZmZjAxZDAyNDAwMDAwMDAwMDAwMDE5NzZhOTE0OThmODc5ZmI3ZjhiNDk1MWRlZTliYzhhMDMyN2I3OTJmYmUzMzJiODg4YWMwMDAwMDAwMCIsICJmZWUiOiA1NzYsICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX2NsYWltX3NpZ25lZCJ9"
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, 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_claim_solver(): htlc_claim_solver = ClaimSolver( private_key=recipient_wallet.private_key(), secret="Hello Meheret!", # Witness from HTLC agreements secret_hash=sha256("Hello Meheret!".encode()).hex(), recipient_address=recipient_wallet.address(), sender_address=sender_wallet.address(), sequence=1000) assert recipient_wallet.private_key( ) == htlc_claim_solver.private_key.hexlify() assert isinstance(htlc_claim_solver.solve(), IfElseSolver) assert isinstance(htlc_claim_solver.witness("testnet"), IfElseScript) bytecode_claim_solver = ClaimSolver( private_key=recipient_wallet.private_key(), secret="Hello Meheret!", # Witness from HTLC bytecode bytecode= "63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8" "b4951dee9bc8a0327b792fbe332b888ac670164b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68" ) assert recipient_wallet.private_key( ) == bytecode_claim_solver.private_key.hexlify() assert isinstance(bytecode_claim_solver.solve(), IfElseSolver) assert isinstance(bytecode_claim_solver.witness("testnet"), IfElseScript) with pytest.raises(TypeError, match="private key must be string format"): ClaimSolver(int(), str()) with pytest.raises(TypeError, match="secret must be string format"): ClaimSolver(str(), int()) with pytest.raises(TypeError, match="secret hash must be string format"): ClaimSolver(str(), str(), int()) with pytest.raises(ValueError, match="invalid secret hash, length must be 64"): ClaimSolver(str(), str(), str()) with pytest.raises(AddressError, match=r"invalid recipient *.* address"): ClaimSolver(str(), str(), sha256(b"Hello Meheret!").hex(), "adsfsdfsd") with pytest.raises(AddressError, match=r"invalid sender *.* address"): ClaimSolver(str(), str(), sha256(b"Hello Meheret!").hex(), "2N729UBGZB3xjsGFRgKivy4bSjkaJGMVSpB", "adsfsdfsd") with pytest.raises(TypeError, match="sequence must be integer format"): ClaimSolver(str(), str(), sha256(b"Hello Meheret!").hex(), "2N729UBGZB3xjsGFRgKivy4bSjkaJGMVSpB", "2N729UBGZB3xjsGFRgKivy4bSjkaJGMVSpB", float()) with pytest.raises(TypeError, match="bytecode must be string format"): ClaimSolver(str(), str(), bytecode=123423423423)
def test_bitcoin_claim_transaction(): unsigned_claim_transaction = ClaimTransaction(version=2, network=network) unsigned_claim_transaction.build_transaction(transaction_id=transaction_id, wallet=recipient_wallet, amount=10_000) assert unsigned_claim_transaction.fee() == 576 assert unsigned_claim_transaction.hash( ) == "a179dd565ea771869b0dfe1fd90c629f379cce9bd31d8814137fcb48fdd43b7e" assert unsigned_claim_transaction.raw( ) == "0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef30000000000ffffffff01d0240000000000001976a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac00000000" assert unsigned_claim_transaction.json() == { 'hex': '0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef30000000000ffffffff01d0240000000000001976a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac00000000', 'txid': 'a179dd565ea771869b0dfe1fd90c629f379cce9bd31d8814137fcb48fdd43b7e', 'hash': 'a179dd565ea771869b0dfe1fd90c629f379cce9bd31d8814137fcb48fdd43b7e', '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 98f879fb7f8b4951dee9bc8a0327b792fbe332b8 OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac', 'type': 'p2pkh', 'address': 'muTnffLDR5LtFeLR2i3WsKVfdyvzfyPnVB' } }] } assert unsigned_claim_transaction.type() == "bitcoin_claim_unsigned" assert unsigned_claim_transaction.unsigned_raw( ) == "eyJmZWUiOiA1NzYsICJyYXciOiAiMDIwMDAwMDAwMWVjMzEyZTkyZDgzODdiMTVmNjIzOGQ0OTE4MzQ0YjYyYWIxNDdkN2YzODQ0ZGM4MWU2NTM3NzZmZThiODRlZjMwMDAwMDAwMDAwZmZmZmZmZmYwMWQwMjQwMDAwMDAwMDAwMDAxOTc2YTkxNDk4Zjg3OWZiN2Y4YjQ5NTFkZWU5YmM4YTAzMjdiNzkyZmJlMzMyYjg4OGFjMDAwMDAwMDAiLCAib3V0cHV0cyI6IHsiYW1vdW50IjogMTAwMDAsICJuIjogMCwgInNjcmlwdCI6ICJhOTE0MmJiMDEzYzNlNGJlYjA4NDIxZGVkY2Y4MTVjYjY1YTVjMzg4MTc4Yjg3In0sICJuZXR3b3JrIjogInRlc3RuZXQiLCAidHlwZSI6ICJiaXRjb2luX2NsYWltX3Vuc2lnbmVkIn0=" signed_claim_transaction = unsigned_claim_transaction.sign( 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)) assert signed_claim_transaction.fee() == 576 assert signed_claim_transaction.hash( ) == "8f98079b6257d65abc2c1c1a14c3bff50a6be949e75a30c127b3a2c0618012e1" assert signed_claim_transaction.raw( ) == "0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef300000000d9473044022060291b5a87474f775dda5c244b21fc2716bfa09c4636ea4c707918c9f759374e02201ef5e768af10d01a1031294e952f6a8ed5c0a75e23f58aee062a89fed2134af70121039213ebcaefdd3e109720c17867ce1bd6d076b0e65e3b6390e6e38548a65e76af0e48656c6c6f204d65686572657421514c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68ffffffff01d0240000000000001976a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac00000000" assert signed_claim_transaction.json() == { 'hex': '0200000001ec312e92d8387b15f6238d4918344b62ab147d7f3844dc81e653776fe8b84ef300000000d9473044022060291b5a87474f775dda5c244b21fc2716bfa09c4636ea4c707918c9f759374e02201ef5e768af10d01a1031294e952f6a8ed5c0a75e23f58aee062a89fed2134af70121039213ebcaefdd3e109720c17867ce1bd6d076b0e65e3b6390e6e38548a65e76af0e48656c6c6f204d65686572657421514c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68ffffffff01d0240000000000001976a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac00000000', 'txid': '8f98079b6257d65abc2c1c1a14c3bff50a6be949e75a30c127b3a2c0618012e1', 'hash': '8f98079b6257d65abc2c1c1a14c3bff50a6be949e75a30c127b3a2c0618012e1', 'size': 302, 'vsize': 302, 'version': 2, 'locktime': 0, 'vin': [{ 'txid': 'f34eb8e86f7753e681dc44387f7d14ab624b3418498d23f6157b38d8922e31ec', 'vout': 0, 'scriptSig': { 'asm': '3044022060291b5a87474f775dda5c244b21fc2716bfa09c4636ea4c707918c9f759374e02201ef5e768af10d01a1031294e952f6a8ed5c0a75e23f58aee062a89fed2134af701 039213ebcaefdd3e109720c17867ce1bd6d076b0e65e3b6390e6e38548a65e76af 48656c6c6f204d65686572657421 OP_1 63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68', 'hex': '473044022060291b5a87474f775dda5c244b21fc2716bfa09c4636ea4c707918c9f759374e02201ef5e768af10d01a1031294e952f6a8ed5c0a75e23f58aee062a89fed2134af70121039213ebcaefdd3e109720c17867ce1bd6d076b0e65e3b6390e6e38548a65e76af0e48656c6c6f204d65686572657421514c5d63aa20821124b554d13f247b1e5d10b84e44fb1296f18f38bbaa1bea34a12c843e01588876a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac6702e803b27576a91464a8390b0b1685fcbf2d4b457118dc8da92d553488ac68' }, 'sequence': '4294967295' }], 'vout': [{ 'value': '0.00009424', 'n': 0, 'scriptPubKey': { 'asm': 'OP_DUP OP_HASH160 98f879fb7f8b4951dee9bc8a0327b792fbe332b8 OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a91498f879fb7f8b4951dee9bc8a0327b792fbe332b888ac', 'type': 'p2pkh', 'address': 'muTnffLDR5LtFeLR2i3WsKVfdyvzfyPnVB' } }] } assert signed_claim_transaction.type() == "bitcoin_claim_signed"