# VeChain Thor Devkit (SDK) in Python 3
Python 3 (Python 3.6+) library to assist smooth development on VeChain for developers and hobbyists.
| Content |
| --------------------------------------------------------- |
| Public 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 the **newest** features on VeChain.
# Install
```bash
pip3 install thor-devkit -U
```
***Caveat: Bip32 depends on the `ripemd160` hash library, which should be present on your system within OpenSSL.***
Type these in your terminal to see if they are available
```python
> python3
> import hashlib
> print('ripemd160' in hashlib.algorithms_available)
```
# Tutorials
### Private/Public Keys
```python
from thor_devkit import cry
from thor_devkit.cry import secp256k1
private_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 )
# 0x86d8cd908e43bc0076bc99e19e1a3c6221436ad0
print('is address?', cry.is_address(address))
# is address? True
print( cry.to_checksum_address(address) )
# 0x86d8CD908e43BC0076Bc99e19E1a3c6221436aD0
```
### Sign & Verify Signature
```python
from thor_devkit import cry
from thor_devkit.cry import secp256k1
# bytes
private_key = bytes.fromhex('7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a')
# bytes
msg_hash, _ = cry.keccak256([b'hello world'])
# Sign the message hash.
# bytes
signature = secp256k1.sign(msg_hash, private_key)
# Recover public key from given message hash and signature.
# bytes
public_key = secp256k1.recover(msg_hash, signature)
```
### Mnemonic Wallet
```python
from thor_devkit.cry import mnemonic
words = 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 Wallet
Hierarchical Deterministic Wallets. See [bip-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and [bip-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki).
```python
from thor_devkit import cry
from thor_devkit.cry import hdnode
# 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.
for i in range(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: 2ca054a50b53299ea3949f5362ee1d1cfe6252fbe30bea3651774790983e9348
```
### Keystore
```python
from thor_devkit.cry import keystore
ks = {
"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'
# Decrypt
private_key = keystore.decrypt(ks, password)
# Encrypt
ks_backup = keystore.encrypt(private_key, password)
```
### Hash the Messages
```python
from thor_devkit import cry
result, length = cry.blake2b256([b'hello world'])
result2, length = cry.blake2b256([b'hello', b' world'])
# result == result2
result, length = cry.keccak256([b'hello world'])
result2, length = cry.keccak256([b'hello', b' world'])
# result == result2
```
### Bloom Filter
```python
from thor_devkit import Bloom
# 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'))
# Verify
b.test(bytes('hello world', 'UTF-8'))
# True
b.test(bytes('bye bye blue bird', 'UTF-8'))
# False
```
### Transaction
```python
from thor_devkit import cry, transaction
# See: https://docs.vechain.org/thor/learn/transaction-model.html#model
body = {
"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] # True
tx.get_signature() == None # True
tx.get_origin() == None # True
tx.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.md](https://github.com/vechain/VIPs/blob/master/vips/VIP-191.md)
```python
from thor_devkit import cry, transaction
delegated_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.
assert delegated_tx.is_delegated() == True
# Sender
addr_1 = '0xf9ea4ba688d55cc7f0eae0dd62f8271b744637bf'
priv_1 = bytes.fromhex('58e444d4fe08b0f4d9d86ec42f26cf15072af3ddc29a78e33b0ceaaa292bcf6b')
# Gas Payer
addr_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)
assert delegated_tx.get_origin() == addr_1
assert delegated_tx.get_delegator() == addr_2
```
### Sign/Verify Certificate (VIP-192)
[https://github.com/vechain/VIPs/blob/master/vips/VIP-192.md](https://github.com/vechain/VIPs/blob/master/vips/VIP-192.md)
```python
from thor_devkit import cry
from thor_devkit.cry import secp256k1
from thor_devkit import certificate
# 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)
```
### ABI
Encode function name and parameters according to ABI.
```python
from thor_devkit import abi
abi_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.
```python
from thor_devkit import abi
e2 = 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 Code
## Layout
```
.
├── 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.py
```
## Local Development
```bash
# install dependencies
make install
# test code
make test
```
## Knowledge
| Name | Bytes | Description |
| ------------ | ----- | ---------------------------------------------- |
| private key | 32 | random number |
| public key | 65 | uncompressed, starts with "04" |
| address | 20 | derived from public key |
| keccak256 | 32 | hash |
| blake2b256 | 32 | hash |
| message hash | 32 | hash of a message |
| signature | 65 | signing result, last bit as recovery parameter |
| seed | 64 | used to derive bip32 master key |
Raw data
{
"_id": null,
"home_page": "https://github.com/laalaguer/thor-devkit.py",
"name": "cneal-thor-devkit",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": null,
"keywords": "vechain thor blockchain sdk",
"author": "cneal",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/57/8b/46eebb09e03c97589d36b569a642ca5f454bfcb1f361f1a2221ef56385a8/cneal_thor_devkit-1.0.0.tar.gz",
"platform": null,
"description": "# VeChain Thor Devkit (SDK) in Python 3\n\nPython 3 (Python 3.6+) library to assist smooth development on VeChain for developers and hobbyists.\n\n| Content |\n| --------------------------------------------------------- |\n| Public key, private key, address conversion. |\n| Mnemonic Wallets. |\n| HD Wallet. |\n| Keystore. |\n| Various Hashing functions. |\n| Signing messages. |\n| Verify signature of messages. |\n| Bloom filter. |\n| Transaction Assembling (**Multi-task Transaction, MTT**). |\n| Fee Delegation Transaction (**VIP-191**). |\n| Self-signed Certificate (**VIP-192**). |\n| ABI decoding of \"functions\" and \"events\" in logs. |\n\n... and will always be updated with the **newest** features on VeChain.\n\n# Install\n```bash\npip3 install thor-devkit -U\n```\n\n***Caveat: Bip32 depends on the `ripemd160` hash library, which should be present on your system within OpenSSL.***\n\nType these in your terminal to see if they are available\n```python\n> python3\n> import hashlib\n> print('ripemd160' in hashlib.algorithms_available)\n```\n\n# Tutorials\n\n### Private/Public Keys\n```python\nfrom thor_devkit import cry\nfrom thor_devkit.cry import secp256k1\n\nprivate_key = secp256k1.generate_privateKey()\n\npublic_key = secp256k1.derive_publicKey(private_key)\n\n_address_bytes = cry.public_key_to_address(public_key)\naddress = '0x' + _address_bytes.hex()\n\nprint( address )\n# 0x86d8cd908e43bc0076bc99e19e1a3c6221436ad0\nprint('is address?', cry.is_address(address))\n# is address? True\nprint( cry.to_checksum_address(address) ) \n# 0x86d8CD908e43BC0076Bc99e19E1a3c6221436aD0\n```\n\n### Sign & Verify Signature\n\n```python\nfrom thor_devkit import cry\nfrom thor_devkit.cry import secp256k1\n\n# bytes\nprivate_key = bytes.fromhex('7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a')\n# bytes\nmsg_hash, _ = cry.keccak256([b'hello world'])\n\n# Sign the message hash.\n# bytes\nsignature = secp256k1.sign(msg_hash, private_key)\n\n# Recover public key from given message hash and signature.\n# bytes\npublic_key = secp256k1.recover(msg_hash, signature)\n```\n\n### Mnemonic Wallet\n\n```python\nfrom thor_devkit.cry import mnemonic\n\nwords = mnemonic.generate()\nprint(words)\n# ['fashion', 'reduce', 'resource', 'ordinary', 'seek', 'kite', 'space', 'marriage', 'cube', 'detail', 'bundle', 'latin']\n\nflag = mnemonic.validate(words)\nprint(flag)\n# True\n\n# Quickly get a Bip32 master seed for HD wallets. See below \"HD Wallet\".\nseed = mnemonic.derive_seed(words)\n\n# Quickly get a private key.\nprivate_key = mnemonic.derive_private_key(words, 0)\n```\n\n### HD Wallet\nHierarchical Deterministic Wallets. See [bip-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and [bip-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki).\n\n```python\nfrom thor_devkit import cry\nfrom thor_devkit.cry import hdnode\n\n# Construct an HD node from words. (Recommended)\nwords = 'ignore empty bird silly journey junior ripple have guard waste between tenant'.split(' ')\n\nhd_node = cry.HDNode.from_mnemonic(\n words,\n init_path=hdnode.VET_EXTERNAL_PATH\n) # VET wallet, you can input other string values to generate BTC/ETH/... wallets.\n\n# Or, construct HD node from seed. (Advanced)\nseed = '28bc19620b4fbb1f8892b9607f6e406fcd8226a0d6dc167ff677d122a1a64ef936101a644e6b447fd495677f68215d8522c893100d9010668614a68b3c7bb49f'\n\nhd_node = cry.HDNode.from_seed(\n bytes.fromhex(seed),\n init_path=hdnode.VET_EXTERNAL_PATH\n) # VET wallet, you can input other string values to generate BTC/ETH/... wallets.\n\n# Access the HD node's properties.\npriv = hd_node.private_key()\npub = hd_node.public_key()\naddr = hd_node.address()\ncc = hd_node.chain_code()\n\n# Or, construct HD node from a given public key. (Advanced)\n# Notice: This HD node cannot derive child HD node with \"private key\".\nhd_node = cry.HDNode.from_public_key(pub, cc)\n\n# Or, construct HD node from a given private key. (Advanced)\nhd_node = cry.HDNode.from_private_key(priv, cc)\n\n# Let it derive further child HD nodes.\nfor i in range(0, 3):\n print('addr:', '0x'+hd_node.derive(i).address().hex())\n print('priv:', hd_node.derive(i).private_key().hex())\n\n# addr: 0x339fb3c438606519e2c75bbf531fb43a0f449a70\n# priv: 27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425\n# addr: 0x5677099d06bc72f9da1113afa5e022feec424c8e\n# priv: 0xcf44074ec3bf912d2a46b7c84fa6eb745652c9c74e674c3760dc7af07fc98b62\n# addr: 0x86231b5cdcbfe751b9ddcd4bd981fc0a48afe921\n# priv: 2ca054a50b53299ea3949f5362ee1d1cfe6252fbe30bea3651774790983e9348\n```\n\n### Keystore\n\n```python\nfrom thor_devkit.cry import keystore\n\nks = {\n \"version\": 3,\n \"id\": \"f437ebb1-5b0d-4780-ae9e-8640178ffd77\",\n \"address\": \"dc6fa3ec1f3fde763f4d59230ed303f854968d26\",\n \"crypto\":\n {\n \"kdf\": \"scrypt\",\n \"kdfparams\": {\n \"dklen\": 32,\n \"salt\": \"b57682e5468934be81217ad5b14ca74dab2b42c2476864592c9f3b370c09460a\",\n \"n\": 262144,\n \"r\": 8,\n \"p\": 1\n },\n \"cipher\": \"aes-128-ctr\",\n \"ciphertext\": \"88cb876f9c0355a89cad88ee7a17a2179700bc4306eaf78fa67320efbb4c7e31\",\n \"cipherparams\": {\n \"iv\": \"de5c0c09c882b3f679876b22b6c5af21\"\n },\n \"mac\": \"8426e8a1e151b28f694849cb31f64cbc9ae3e278d02716cf5b61d7ddd3f6e728\"\n }\n}\npassword = b'123456'\n\n# Decrypt\nprivate_key = keystore.decrypt(ks, password)\n\n# Encrypt\nks_backup = keystore.encrypt(private_key, password)\n```\n\n### Hash the Messages\n```python\nfrom thor_devkit import cry\n\nresult, length = cry.blake2b256([b'hello world'])\nresult2, length = cry.blake2b256([b'hello', b' world'])\n# result == result2\n\nresult, length = cry.keccak256([b'hello world'])\nresult2, length = cry.keccak256([b'hello', b' world'])\n# result == result2\n```\n\n\n### Bloom Filter\n```python\nfrom thor_devkit import Bloom\n\n# Create a bloom filter that can store 100 items.\n_k = Bloom.estimate_k(100)\nb = Bloom(_k)\n\n# Add an item to the bloom filter.\nb.add(bytes('hello world', 'UTF-8'))\n\n# Verify\nb.test(bytes('hello world', 'UTF-8'))\n# True\nb.test(bytes('bye bye blue bird', 'UTF-8'))\n# False\n```\n\n### Transaction\n\n```python\nfrom thor_devkit import cry, transaction\n\n# See: https://docs.vechain.org/thor/learn/transaction-model.html#model\nbody = {\n \"chainTag\": int('0x4a', 16), # 0x4a/0x27/0xa4 See: https://docs.vechain.org/others/miscellaneous.html#network-identifier\n \"blockRef\": '0x00000000aabbccdd',\n \"expiration\": 32,\n \"clauses\": [\n {\n \"to\": '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed',\n \"value\": 10000,\n \"data\": '0x000000606060'\n },\n {\n \"to\": '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed',\n \"value\": 20000,\n \"data\": '0x000000606060'\n }\n ],\n \"gasPriceCoef\": 128,\n \"gas\": 21000,\n \"dependsOn\": None,\n \"nonce\": 12345678\n}\n\n# Construct an unsigned transaction.\ntx = transaction.Transaction(body)\n\n# Access its properties.\ntx.get_signing_hash() == cry.blake2b256([tx.encode()])[0] # True\n\ntx.get_signature() == None # True\n\ntx.get_origin() == None # True\n\ntx.get_intrinsic_gas() == 37432 # estimate the gas this tx gonna cost.\n\n# Sign the transaction with a private key.\npriv_key = bytes.fromhex('7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a')\nmessage_hash = tx.get_signing_hash()\nsignature = cry.secp256k1.sign(message_hash, priv_key)\n\n# Set the signature on the transaction.\ntx.set_signature(signature)\n\n# Tx origin?\nprint(tx.get_origin())\n# 0xd989829d88b0ed1b06edf5c50174ecfa64f14a64\n\n# Tx id?\nprint(tx.get_id())\n# 0xda90eaea52980bc4bb8d40cb2ff84d78433b3b4a6e7d50b75736c5e3e77b71ec\n\n# Tx encoded into bytes, ready to be sent out.\nencoded_bytes = tx.encode()\n\n# pretty print the encoded bytes.\nprint('0x' + encoded_bytes.hex())\n\n# http POST transaction to send the encoded_bytes to VeChain...\n# See the REST API details:\n# testnet: https://sync-testnet.vechain.org/doc/swagger-ui/\n# mainnet: https://sync-mainnet.vechain.org/doc/swagger-ui/\n```\n\n### Transaction (VIP-191)\n[https://github.com/vechain/VIPs/blob/master/vips/VIP-191.md](https://github.com/vechain/VIPs/blob/master/vips/VIP-191.md)\n\n```python\nfrom thor_devkit import cry, transaction\n\ndelegated_body = {\n \"chainTag\": 1,\n \"blockRef\": '0x00000000aabbccdd',\n \"expiration\": 32,\n \"clauses\": [\n {\n \"to\": '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed',\n \"value\": 10000,\n \"data\": '0x000000606060'\n },\n {\n \"to\": '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed',\n \"value\": 20000,\n \"data\": '0x000000606060'\n }\n ],\n \"gasPriceCoef\": 128,\n \"gas\": 21000,\n \"dependsOn\": None,\n \"nonce\": 12345678,\n \"reserved\": {\n \"features\": 1\n }\n}\n\ndelegated_tx = transaction.Transaction(delegated_body)\n\n# Indicate it is a delegated Transaction using VIP-191.\nassert delegated_tx.is_delegated() == True\n\n# Sender\naddr_1 = '0xf9ea4ba688d55cc7f0eae0dd62f8271b744637bf'\n\npriv_1 = bytes.fromhex('58e444d4fe08b0f4d9d86ec42f26cf15072af3ddc29a78e33b0ceaaa292bcf6b')\n\n\n# Gas Payer\naddr_2 = '0x34b7538c2a7c213dd34c3ecc0098097d03a94dcb'\n\npriv_2 = bytes.fromhex('0bfd6a863f347f4ef2cf2d09c3db7b343d84bb3e6fc8c201afee62de6381dc65')\n\n\nh = delegated_tx.get_signing_hash() # Sender hash to be signed.\ndh = delegated_tx.get_signing_hash(addr_1) # Gas Payer hash to be signed.\n\n# Sender sign the hash.\n# Gas payer sign the hash.\n# Concat two parts to forge a legal signature.\nsig = cry.secp256k1.sign(h, priv_1) + cry.secp256k1.sign(dh, priv_2)\n\ndelegated_tx.set_signature(sig)\n\nassert delegated_tx.get_origin() == addr_1\nassert delegated_tx.get_delegator() == addr_2\n```\n\n### Sign/Verify Certificate (VIP-192)\n[https://github.com/vechain/VIPs/blob/master/vips/VIP-192.md](https://github.com/vechain/VIPs/blob/master/vips/VIP-192.md)\n\n```python\nfrom thor_devkit import cry\nfrom thor_devkit.cry import secp256k1\nfrom thor_devkit import certificate\n\n# My address.\naddress = '0xd989829d88b0ed1b06edf5c50174ecfa64f14a64'\n# My corresponding private key.\nprivate_key = bytes.fromhex('7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a')\n\n# My cert.\ncert_dict = {\n 'purpose': 'identification',\n 'payload': {\n 'type': 'text',\n 'content': 'fyi'\n },\n 'domain': 'localhost',\n 'timestamp': 1545035330,\n 'signer': address\n}\n\n# Construct a cert, without signature.\ncert = certificate.Certificate(**cert_dict)\n\n# Sign the cert with my private key.\nsig_bytes = secp256k1.sign(\n cry.blake2b256([\n certificate.encode(cert).encode('utf-8')\n ])[0],\n private_key\n)\nsignature = '0x' + sig_bytes.hex()\n\n# Mount the signature onto the cert.\ncert_dict['signature'] = signature\n\n# Construct a cert, with signature.\ncert2 = certificate.Certificate(**cert_dict)\n\n# Verify, if verify failed it will throw Exceptions.\ncertificate.verify(cert2)\n```\n\n### ABI\n\nEncode function name and parameters according to ABI.\n\n```python\nfrom thor_devkit import abi\n\nabi_dict = {\n \"constant\": False,\n \"inputs\": [\n {\n \"name\": \"a1\",\n \"type\": \"uint256\"\n },\n {\n \"name\": \"a2\",\n \"type\": \"string\"\n }\n ],\n \"name\": \"f1\",\n \"outputs\": [\n {\n \"name\": \"r1\",\n \"type\": \"address\"\n },\n {\n \"name\": \"r2\",\n \"type\": \"bytes\"\n }\n ],\n \"payable\": False,\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n}\n\n# Verify if abi_dict is in good shape.\nf1 = abi.FUNCTION(abi_dict)\n\n# Get a function instance of the abi.\nf = abi.Function(f1)\n\n# Get function selector:\nselector = f.selector.hex()\nselector == '27fcbb2f'\n\n# Encode the function input parameters.\nr = f.encode([1, 'foo'], to_hex=True)\nr == '0x27fcbb2f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'\n\n# Decode function return result according to abi.\ndata = '000000000000000000000000abc000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'\n\nr = f.decode(bytes.fromhex(data))\n# {\n# \"0\": '0xabc0000000000000000000000000000000000001',\n# \"1\": b'666f6f',\n# \"r1\": '0xabc0000000000000000000000000000000000001',\n# \"r2\": b'666f6f'\n# }\n```\n\nDecode logs according to data and topics.\n\n```python\nfrom thor_devkit import abi\n\ne2 = abi.EVENT({\n \"anonymous\": True,\n \"inputs\": [\n {\n \"indexed\": True,\n \"name\": \"a1\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": False,\n \"name\": \"a2\",\n \"type\": \"string\"\n }\n ],\n \"name\": \"E2\",\n \"type\": \"event\"\n})\n\nee = abi.Event(e2)\n\n# data in hex format.\nr = ee.decode(\n data=bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'),\n topics=[\n bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000001')\n ]\n)\n\n# r == { \"0\": 1, \"1\": \"foo\", \"a1\": 1, \"a2\": \"foo\" }\n```\n\n# Tweak the Code\n\n## Layout\n```\n.\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 requirements.txt\n\u2514\u2500\u2500 thor_devkit\n \u251c\u2500\u2500 __init__.py\n \u251c\u2500\u2500 abi.py\n \u251c\u2500\u2500 bloom.py\n \u251c\u2500\u2500 certificate.py\n \u251c\u2500\u2500 cry\n \u2502 \u251c\u2500\u2500 __init__.py\n \u2502 \u251c\u2500\u2500 address.py\n \u2502 \u251c\u2500\u2500 blake2b.py\n \u2502 \u251c\u2500\u2500 hdnode.py\n \u2502 \u251c\u2500\u2500 keccak.py\n \u2502 \u251c\u2500\u2500 keystore.py\n \u2502 \u251c\u2500\u2500 mnemonic.py\n \u2502 \u251c\u2500\u2500 secp256k1.py\n \u2502 \u2514\u2500\u2500 utils.py\n \u251c\u2500\u2500 rlp.py\n \u2514\u2500\u2500 transaction.py\n```\n\n## Local Development\n```bash\n# install dependencies\nmake install\n# test code\nmake test\n```\n\n## Knowledge\n\n| Name | Bytes | Description |\n| ------------ | ----- | ---------------------------------------------- |\n| private key | 32 | random number |\n| public key | 65 | uncompressed, starts with \"04\" |\n| address | 20 | derived from public key |\n| keccak256 | 32 | hash |\n| blake2b256 | 32 | hash |\n| message hash | 32 | hash of a message |\n| signature | 65 | signing result, last bit as recovery parameter |\n| seed | 64 | used to derive bip32 master key |\n",
"bugtrack_url": null,
"license": null,
"summary": "SDK to interact with VeChain Thor public blockchain.",
"version": "1.0.0",
"project_urls": {
"Documentation": "https://github.com/laalaguer/thor-devkit.py",
"Homepage": "https://github.com/laalaguer/thor-devkit.py",
"Issue Tracker": "https://github.com/laalaguer/thor-devkit.py/issues",
"Source": "https://github.com/laalaguer/thor-devkit.py"
},
"split_keywords": [
"vechain",
"thor",
"blockchain",
"sdk"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "677422697a3aed1c440d8245aec37e52c532720dd353bca0e754f94d3428eab6",
"md5": "6033c1226ee5a16fdd8c3ac19333f47a",
"sha256": "af0b13e5e124d7c874239b2f399d2b4805c5fa44c7eb7f795b7e30861600333d"
},
"downloads": -1,
"filename": "cneal_thor_devkit-1.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "6033c1226ee5a16fdd8c3ac19333f47a",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 40460,
"upload_time": "2024-07-26T19:33:35",
"upload_time_iso_8601": "2024-07-26T19:33:35.387275Z",
"url": "https://files.pythonhosted.org/packages/67/74/22697a3aed1c440d8245aec37e52c532720dd353bca0e754f94d3428eab6/cneal_thor_devkit-1.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "578b46eebb09e03c97589d36b569a642ca5f454bfcb1f361f1a2221ef56385a8",
"md5": "985bd51c5e32b1f69c942fd45d9404b6",
"sha256": "c67c744291bfc89bf14877b2e6cd8a5a74eaefba44dd4b56877e40a833df84e6"
},
"downloads": -1,
"filename": "cneal_thor_devkit-1.0.0.tar.gz",
"has_sig": false,
"md5_digest": "985bd51c5e32b1f69c942fd45d9404b6",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6",
"size": 44588,
"upload_time": "2024-07-26T19:33:37",
"upload_time_iso_8601": "2024-07-26T19:33:37.295797Z",
"url": "https://files.pythonhosted.org/packages/57/8b/46eebb09e03c97589d36b569a642ca5f454bfcb1f361f1a2221ef56385a8/cneal_thor_devkit-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-07-26 19:33:37",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "laalaguer",
"github_project": "thor-devkit.py",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "cneal-thor-devkit"
}