Files
xrpl-dev-portal/_code-samples/airgapped-wallet/py/airgapped-wallet.py

233 lines
7.9 KiB
Python

import os
import shutil
import base64
import qrcode
import platform
from PIL import Image
from pathlib import Path, PureWindowsPath, PurePath
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from xrpl.wallet import Wallet
from xrpl.core import keypairs
from xrpl.utils import xrp_to_drops
from xrpl.models.transactions import Payment
from xrpl.transaction import sign
def create_wallet():
"""
Generates a keypair
"""
seed = keypairs.generate_seed()
pub, priv = keypairs.derive_keypair(seed)
address = keypairs.derive_classic_address(pub)
print(
f"\n\n XRP WALLET CREDENTIALS"
f"\n Wallet Address: {address}"
f"\n Seed: {seed}"
)
return address, seed
def sign_transaction(_xrp_amount, _destination, _ledger_seq, _wallet_seq, password):
"""
Signs transaction and returns signed transaction blob in QR code
"""
with open(get_path("/Wallet/private.txt"), "r") as f:
_seed = f.read()
_seed = bytes.fromhex(_seed)
with open(get_path("/Wallet/salt.txt"), "rb") as f:
salt = f.read()
# Line 49-58: initialize key
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
iterations=100000,
salt=salt
)
key = base64.urlsafe_b64encode(kdf.derive(bytes(password.encode())))
crypt = Fernet(key)
# Decrypts the wallet's private key
_seed = crypt.decrypt(_seed)
_wallet = Wallet.from_seed(seed=_seed.decode())
validated_seq = _ledger_seq
# Construct Payment transaction
my_tx_payment = Payment(
account=_wallet.address,
amount=xrp_to_drops(xrp=_xrp_amount),
destination=_destination,
last_ledger_sequence=validated_seq + 100,
# 100 ledgers usually takes about 6 minutes, so you have about that
# long to submit it before it expires. To give more time, increase
# this number; for unlimited time, remove last_ledger entirely.
sequence=_wallet_seq,
fee="10"
)
# Signs transaction and displays the signed_tx blob in QR code
# Scan the QR code and transmit the signed_tx blob to an online machine (Machine 2) to relay it to the XRPL
my_tx_payment_signed = sign(transaction=my_tx_payment, wallet=_wallet)
img = qrcode.make(my_tx_payment_signed.blob())
img.save(get_path("/Wallet/transactionID.png"))
image = Image.open(get_path("/Wallet/transactionID.png"))
image.show()
def get_path(file):
"""
Get path (filesystem management)
"""
global File_
# Checks what OS is being us
OS = platform.system()
usr = Path.home()
# Get PATH format based on the OS
if OS == "Windows":
File_ = PureWindowsPath(str(usr) + file)
else: # Assuming Linux-style file format, use this path:
File_ = PurePath(str(usr) + file)
return str(File_)
def main():
global File, Path_
# Gets the machine's operating system (OS)
OS = platform.system()
usr = Path.home()
if OS == "Windows":
# If it's Windows, use this path:
File = PureWindowsPath(str(usr) + '/Wallet')
Path_ = str(PureWindowsPath(str(usr)))
else: # Assuming Linux-style file format, use this path:
File = PurePath(str(usr) + '/Wallet')
Path_ = str(PurePath(str(usr)))
# If the Wallet's folder already exists, continue on
if os.path.exists(File) and os.path.exists(get_path("/Wallet/public.txt")):
while True:
try:
ask = int(input("\n 1. Transact XRP"
"\n 2. Generate an XRP wallet (read only)"
"\n 3. Showcase XRP Wallet Address (QR Code)"
"\n 4. Exit"
"\n\n Enter Index: "
))
except ValueError:
continue
if ask == 1:
password = str(input(" Enter Password: "))
amount = float(input("\n Enter XRP To Send: "))
destination = input("If you just want to try it out, you can use the faucet account rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"
"\n Enter Destination: ")
wallet_sequence = int(input("Look up the 'Next Sequence' for the account using test.bithomp.com and enter it below!"
"\n Enter Wallet Sequence: "))
ledger_sequence = int(input("Look up the latest ledger sequence on testnet.xrpl.org and enter it below!"
"\n Enter Ledger Sequence: "))
sign_transaction(_xrp_amount=amount,
_destination=destination,
_ledger_seq=ledger_sequence,
_wallet_seq=wallet_sequence,
password=password
)
print("This transaction is expected to expire in ~6 minutes.")
del destination, amount, wallet_sequence, ledger_sequence
if ask == 2:
_pub, _seed = create_wallet()
if ask == 3:
with open(get_path("/Wallet/public.txt"), "r") as f:
print(f"\n Wallet Address: {f.read()}")
image = Image.open(get_path("/Wallet/public.png"))
image.show()
if ask == 4:
return 0
else:
# If the Wallet's folder does not exist, create one and store wallet data (encrypted private key, encrypted seed, account address)
# If the Wallet's directory exists but files are missing, delete it and generate a new wallet
if os.path.exists(File):
confirmation = input(f"We've detected missing files on {File}, would you like to delete your wallet's credentials & generate new wallet credentials? (YES/NO):")
if confirmation == "YES":
confirmation_1 = input(f"All wallet credentials will be lost if you continue, are you sure? (YES/NO): ")
if confirmation_1 == "YES":
shutil.rmtree(File)
else:
print("Aborted: Wallet credentials are still intact")
return 0
else:
print("- Wallet credentials are still intact")
return 0
os.makedirs(File)
pub, seed = create_wallet()
img = qrcode.make(pub)
img.save(get_path("/Wallet/public.png"))
print("\nCreating a brand new Wallet, please enter a new password")
password = str(input("\n Enter Password: "))
salt = os.urandom(16)
with open(get_path("/Wallet/salt.txt"), "wb") as f:
f.write(salt)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
iterations=100000,
salt=salt
)
key = base64.urlsafe_b64encode(kdf.derive(bytes(password.encode())))
crypt = Fernet(key)
priv = crypt.encrypt(bytes(seed, encoding='utf-8'))
seed = crypt.encrypt(bytes(seed, encoding='utf-8'))
with open(get_path("/Wallet/seed.txt"), "w") as f:
f.write(seed.hex())
with open(get_path("/Wallet/private.txt"), "w") as f:
f.write(priv.hex())
with open(get_path("/Wallet/public.txt"), "w") as f:
f.write(pub)
openimg = Image.open(get_path("/Wallet/public.png"))
openimg.show()
print("\nFinished generating an account.")
print(f"\nWallet Address: {pub}")
print("\nPlease scan the QR code on your phone and use https://test.bithomp.com/faucet/ to fund the account."
"\nAfter that, you're able to sign transactions and transmit them to Machine 2 (online machine).")
# Loop back to the start after setup
main()
if __name__ == '__main__':
main()