Package: thor-devkit
VeChain Thor Devkit (SDK) in Python 3Python 3 (Python 3.6+) library to assist smooth development on VeChain for developers and hobbyists.ContentPublic key, private key, address conversion.Mnemonic Wallets.HD Wallet.Keystore.Various Hashing functions.Signing messages.Verify signature of messages.Bloom filter.Transaction Assembling (Multi-task Transaction, MTT).Fee Delegation Transaction (VIP-191).Self-signed Certificate (VIP-192).ABI decoding of "functions" and "events" in logs.... and will always be updated with thenewestfeatures on VeChain.Installpip3installthor-devkit-UCaveat: Bip32 depends on the ripemd160 hash library, which should be present on your system.TutorialsPrivate/Public Keysfromthor_devkitimportcryfromthor_devkit.cryimportsecp256k1private_key=secp256k1.generate_privateKey()public_key=secp256k1.derive_publicKey(private_key)_address_bytes=cry.public_key_to_address(public_key)address='0x'+_address_bytes.hex()print(address)# 0x86d8cd908e43bc0076bc99e19e1a3c6221436ad0print('is address?',cry.is_address(address))# is address? Trueprint(cry.to_checksum_address(address))# 0x86d8CD908e43BC0076Bc99e19E1a3c6221436aD0Sign & Verify Signaturefromthor_devkitimportcryfromthor_devkit.cryimportsecp256k1# bytesprivate_key=bytes.fromhex('7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a')# bytesmsg_hash,_=cry.keccak256([b'hello world'])# Sign the message hash.# bytessignature=secp256k1.sign(msg_hash,private_key)# Recover public key from given message hash and signature.# bytespublic_key=secp256k1.recover(msg_hash,signature)Mnemonic Walletfromthor_devkit.cryimportmnemonicwords=mnemonic.generate()print(words)# ['fashion', 'reduce', 'resource', 'ordinary', 'seek', 'kite', 'space', 'marriage', 'cube', 'detail', 'bundle', 'latin']flag=mnemonic.validate(words)print(flag)# True# Quickly get a Bip32 master seed for HD wallets. See below "HD Wallet".seed=mnemonic.derive_seed(words)# Quickly get a private key.private_key=mnemonic.derive_private_key(words,0)HD WalletHierarchical Deterministic Wallets. Seebip-32andbip-44.fromthor_devkitimportcryfromthor_devkit.cryimporthdnode# Construct an HD node from words. (Recommended)words='ignore empty bird silly journey junior ripple have guard waste between tenant'.split(' ')hd_node=cry.HDNode.from_mnemonic(words,init_path=hdnode.VET_EXTERNAL_PATH)# VET wallet, you can input other string values to generate BTC/ETH/... wallets.# Or, construct HD node from seed. (Advanced)seed='28bc19620b4fbb1f8892b9607f6e406fcd8226a0d6dc167ff677d122a1a64ef936101a644e6b447fd495677f68215d8522c893100d9010668614a68b3c7bb49f'hd_node=cry.HDNode.from_seed(bytes.fromhex(seed),init_path=hdnode.VET_EXTERNAL_PATH)# VET wallet, you can input other string values to generate BTC/ETH/... wallets.# Access the HD node's properties.priv=hd_node.private_key()pub=hd_node.public_key()addr=hd_node.address()cc=hd_node.chain_code()# Or, construct HD node from a given public key. (Advanced)# Notice: This HD node cannot derive child HD node with "private key".hd_node=cry.HDNode.from_public_key(pub,cc)# Or, construct HD node from a given private key. (Advanced)hd_node=cry.HDNode.from_private_key(priv,cc)# Let it derive further child HD nodes.foriinrange(0,3):print('addr:','0x'+hd_node.derive(i).address().hex())print('priv:',hd_node.derive(i).private_key().hex())# addr: 0x339fb3c438606519e2c75bbf531fb43a0f449a70# priv: 27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425# addr: 0x5677099d06bc72f9da1113afa5e022feec424c8e# priv: 0xcf44074ec3bf912d2a46b7c84fa6eb745652c9c74e674c3760dc7af07fc98b62# addr: 0x86231b5cdcbfe751b9ddcd4bd981fc0a48afe921# priv: 2ca054a50b53299ea3949f5362ee1d1cfe6252fbe30bea3651774790983e9348Keystorefromthor_devkit.cryimportkeystoreks={"version":3,"id":"f437ebb1-5b0d-4780-ae9e-8640178ffd77","address":"dc6fa3ec1f3fde763f4d59230ed303f854968d26","crypto":{"kdf":"scrypt","kdfparams":{"dklen":32,"salt":"b57682e5468934be81217ad5b14ca74dab2b42c2476864592c9f3b370c09460a","n":262144,"r":8,"p":1},"cipher":"aes-128-ctr","ciphertext":"88cb876f9c0355a89cad88ee7a17a2179700bc4306eaf78fa67320efbb4c7e31","cipherparams":{"iv":"de5c0c09c882b3f679876b22b6c5af21"},"mac":"8426e8a1e151b28f694849cb31f64cbc9ae3e278d02716cf5b61d7ddd3f6e728"}}password=b'123456'# Decryptprivate_key=keystore.decrypt(ks,password)# Encryptks_backup=keystore.encrypt(private_key,password)Hash the Messagesfromthor_devkitimportcryresult,length=cry.blake2b256([b'hello world'])result2,length=cry.blake2b256([b'hello',b' world'])# result == result2result,length=cry.keccak256([b'hello world'])result2,length=cry.keccak256([b'hello',b' world'])# result == result2Bloom Filterfromthor_devkitimportBloom# Create a bloom filter that can store 100 items._k=Bloom.estimate_k(100)b=Bloom(_k)# Add an item to the bloom filter.b.add(bytes('hello world','UTF-8'))# Verifyb.test(bytes('hello world','UTF-8'))# Trueb.test(bytes('bye bye blue bird','UTF-8'))# FalseTransactionfromthor_devkitimportcry,transaction# See: https://docs.vechain.org/thor/learn/transaction-model.html#modelbody={"chainTag":int('0x4a',16),# 0x4a/0x27/0xa4 See: https://docs.vechain.org/others/miscellaneous.html#network-identifier"blockRef":'0x00000000aabbccdd',"expiration":32,"clauses":[{"to":'0x7567d83b7b8d80addcb281a71d54fc7b3364ffed',"value":10000,"data":'0x000000606060'},{"to":'0x7567d83b7b8d80addcb281a71d54fc7b3364ffed',"value":20000,"data":'0x000000606060'}],"gasPriceCoef":128,"gas":21000,"dependsOn":None,"nonce":12345678}# Construct an unsigned transaction.tx=transaction.Transaction(body)# Access its properties.tx.get_signing_hash()==cry.blake2b256([tx.encode()])[0]# Truetx.get_signature()==None# Truetx.get_origin()==None# Truetx.get_intrinsic_gas()==37432# estimate the gas this tx gonna cost.# Sign the transaction with a private key.priv_key=bytes.fromhex('7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a')message_hash=tx.get_signing_hash()signature=cry.secp256k1.sign(message_hash,priv_key)# Set the signature on the transaction.tx.set_signature(signature)# Tx origin?print(tx.get_origin())# 0xd989829d88b0ed1b06edf5c50174ecfa64f14a64# Tx id?print(tx.get_id())# 0xda90eaea52980bc4bb8d40cb2ff84d78433b3b4a6e7d50b75736c5e3e77b71ec# Tx encoded into bytes, ready to be sent out.encoded_bytes=tx.encode()# pretty print the encoded bytes.print('0x'+encoded_bytes.hex())# http POST transaction to send the encoded_bytes to VeChain...# See the REST API details:# testnet: https://sync-testnet.vechain.org/doc/swagger-ui/# mainnet: https://sync-mainnet.vechain.org/doc/swagger-ui/Transaction (VIP-191)https://github.com/vechain/VIPs/blob/master/vips/VIP-191.mdfromthor_devkitimportcry,transactiondelegated_body={"chainTag":1,"blockRef":'0x00000000aabbccdd',"expiration":32,"clauses":[{"to":'0x7567d83b7b8d80addcb281a71d54fc7b3364ffed',"value":10000,"data":'0x000000606060'},{"to":'0x7567d83b7b8d80addcb281a71d54fc7b3364ffed',"value":20000,"data":'0x000000606060'}],"gasPriceCoef":128,"gas":21000,"dependsOn":None,"nonce":12345678,"reserved":{"features":1}}delegated_tx=transaction.Transaction(delegated_body)# Indicate it is a delegated Transaction using VIP-191.assertdelegated_tx.is_delegated()==True# Senderaddr_1='0xf9ea4ba688d55cc7f0eae0dd62f8271b744637bf'priv_1=bytes.fromhex('58e444d4fe08b0f4d9d86ec42f26cf15072af3ddc29a78e33b0ceaaa292bcf6b')# Gas Payeraddr_2='0x34b7538c2a7c213dd34c3ecc0098097d03a94dcb'priv_2=bytes.fromhex('0bfd6a863f347f4ef2cf2d09c3db7b343d84bb3e6fc8c201afee62de6381dc65')h=delegated_tx.get_signing_hash()# Sender hash to be signed.dh=delegated_tx.get_signing_hash(addr_1)# Gas Payer hash to be signed.# Sender sign the hash.# Gas payer sign the hash.# Concat two parts to forge a legal signature.sig=cry.secp256k1.sign(h,priv_1)+cry.secp256k1.sign(dh,priv_2)delegated_tx.set_signature(sig)assertdelegated_tx.get_origin()==addr_1assertdelegated_tx.get_delegator()==addr_2Sign/Verify Certificate (VIP-192)https://github.com/vechain/VIPs/blob/master/vips/VIP-192.mdfromthor_devkitimportcryfromthor_devkit.cryimportsecp256k1fromthor_devkitimportcertificate# My address.address='0xd989829d88b0ed1b06edf5c50174ecfa64f14a64'# My corresponding private key.private_key=bytes.fromhex('7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a')# My cert.cert_dict={'purpose':'identification','payload':{'type':'text','content':'fyi'},'domain':'localhost','timestamp':1545035330,'signer':address}# Construct a cert, without signature.cert=certificate.Certificate(**cert_dict)# Sign the cert with my private key.sig_bytes=secp256k1.sign(cry.blake2b256([certificate.encode(cert).encode('utf-8')])[0],private_key)signature='0x'+sig_bytes.hex()# Mount the signature onto the cert.cert_dict['signature']=signature# Construct a cert, with signature.cert2=certificate.Certificate(**cert_dict)# Verify, if verify failed it will throw Exceptions.certificate.verify(cert2)ABIEncode function name and parameters according to ABI.fromthor_devkitimportabiabi_dict={"constant":False,"inputs":[{"name":"a1","type":"uint256"},{"name":"a2","type":"string"}],"name":"f1","outputs":[{"name":"r1","type":"address"},{"name":"r2","type":"bytes"}],"payable":False,"stateMutability":"nonpayable","type":"function"}# Verify if abi_dict is in good shape.f1=abi.FUNCTION(abi_dict)# Get a function instance of the abi.f=abi.Function(f1)# Get function selector:selector=f.selector.hex()selector=='27fcbb2f'# Encode the function input parameters.r=f.encode([1,'foo'],to_hex=True)r=='0x27fcbb2f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'# Decode function return result according to abi.data='000000000000000000000000abc000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'r=f.decode(bytes.fromhex(data))# {#     "0": '0xabc0000000000000000000000000000000000001',#     "1": b'666f6f',#     "r1": '0xabc0000000000000000000000000000000000001',#     "r2": b'666f6f'# }Decode logs according to data and topics.fromthor_devkitimportabie2=abi.EVENT({"anonymous":True,"inputs":[{"indexed":True,"name":"a1","type":"uint256"},{"indexed":False,"name":"a2","type":"string"}],"name":"E2","type":"event"})ee=abi.Event(e2)# data in hex format.r=ee.decode(data=bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'),topics=[bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000001')])# r == { "0": 1, "1": "foo", "a1": 1, "a2": "foo" }Tweak the CodeLayout.
├── LICENSE
├── README.md
├── requirements.txt
└── thor_devkit
    ├── __init__.py
    ├── abi.py
    ├── bloom.py
    ├── certificate.py
    ├── cry
    │   ├── __init__.py
    │   ├── address.py
    │   ├── blake2b.py
    │   ├── hdnode.py
    │   ├── keccak.py
    │   ├── keystore.py
    │   ├── mnemonic.py
    │   ├── secp256k1.py
    │   └── utils.py
    ├── rlp.py
    └── transaction.pyLocal Development# install dependenciesmakeinstall# test codemaketestKnowledgeNameBytesDescriptionprivate key32random numberpublic key65uncompressed, starts with "04"address20derived from public keykeccak25632hashblake2b25632hashmessage hash32hash of a messagesignature65signing result, last bit as recovery parameterseed64used to derive bip32 master key