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
pip3 install thor-devkit -U
Caveat: Bip32 depends on the ripemd160 hash library, which should be present on your system.
Tutorials
Private/Public Keys
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 )
print('is address?', cry.is_address(address))
print( cry.to_checksum_address(address) )
Sign & Verify Signature
from thor_devkit import cry
from thor_devkit.cry import secp256k1
private_key = bytes.fromhex('7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a')
msg_hash, _ = cry.keccak256([b'hello world'])
signature = secp256k1.sign(msg_hash, private_key)
public_key = secp256k1.recover(msg_hash, signature)
Mnemonic Wallet
from thor_devkit.cry import mnemonic
words = mnemonic.generate()
print(words)
flag = mnemonic.validate(words)
print(flag)
seed = mnemonic.derive_seed(words)
private_key = mnemonic.derive_private_key(words, 0)
HD Wallet
Hierarchical Deterministic Wallets. See bip-32 and bip-44.
from thor_devkit import cry
from thor_devkit.cry import hdnode
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
)
seed = '28bc19620b4fbb1f8892b9607f6e406fcd8226a0d6dc167ff677d122a1a64ef936101a644e6b447fd495677f68215d8522c893100d9010668614a68b3c7bb49f'
hd_node = cry.HDNode.from_seed(
bytes.fromhex(seed),
init_path=hdnode.VET_EXTERNAL_PATH
)
priv = hd_node.private_key()
pub = hd_node.public_key()
addr = hd_node.address()
cc = hd_node.chain_code()
hd_node = cry.HDNode.from_public_key(pub, cc)
hd_node = cry.HDNode.from_private_key(priv, cc)
for i in range(0, 3):
print('addr:', '0x'+hd_node.derive(i).address().hex())
print('priv:', hd_node.derive(i).private_key().hex())
Keystore
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'
private_key = keystore.decrypt(ks, password)
ks_backup = keystore.encrypt(private_key, password)
Hash the Messages
from thor_devkit import cry
result, length = cry.blake2b256([b'hello world'])
result2, length = cry.blake2b256([b'hello', b' world'])
result, length = cry.keccak256([b'hello world'])
result2, length = cry.keccak256([b'hello', b' world'])
Bloom Filter
from thor_devkit import Bloom
_k = Bloom.estimate_k(100)
b = Bloom(_k)
b.add(bytes('hello world', 'UTF-8'))
b.test(bytes('hello world', 'UTF-8'))
b.test(bytes('bye bye blue bird', 'UTF-8'))
Transaction
from thor_devkit import cry, transaction
body = {
"chainTag": int('0x4a', 16),
"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
}
tx = transaction.Transaction(body)
tx.get_signing_hash() == cry.blake2b256([tx.encode()])[0]
tx.get_signature() == None
tx.get_origin() == None
tx.get_intrinsic_gas() == 37432
priv_key = bytes.fromhex('7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a')
message_hash = tx.get_signing_hash()
signature = cry.secp256k1.sign(message_hash, priv_key)
tx.set_signature(signature)
print(tx.get_origin())
print(tx.get_id())
encoded_bytes = tx.encode()
print('0x' + encoded_bytes.hex())
Transaction (VIP-191)
https://github.com/vechain/VIPs/blob/master/vips/VIP-191.md
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)
assert delegated_tx.is_delegated() == True
addr_1 = '0xf9ea4ba688d55cc7f0eae0dd62f8271b744637bf'
priv_1 = bytes.fromhex('58e444d4fe08b0f4d9d86ec42f26cf15072af3ddc29a78e33b0ceaaa292bcf6b')
addr_2 = '0x34b7538c2a7c213dd34c3ecc0098097d03a94dcb'
priv_2 = bytes.fromhex('0bfd6a863f347f4ef2cf2d09c3db7b343d84bb3e6fc8c201afee62de6381dc65')
h = delegated_tx.get_signing_hash()
dh = delegated_tx.get_signing_hash(addr_1)
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
from thor_devkit import cry
from thor_devkit.cry import secp256k1
from thor_devkit import certificate
address = '0xd989829d88b0ed1b06edf5c50174ecfa64f14a64'
private_key = bytes.fromhex('7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a')
cert_dict = {
'purpose': 'identification',
'payload': {
'type': 'text',
'content': 'fyi'
},
'domain': 'localhost',
'timestamp': 1545035330,
'signer': address
}
cert = certificate.Certificate(**cert_dict)
sig_bytes = secp256k1.sign(
cry.blake2b256([
certificate.encode(cert).encode('utf-8')
])[0],
private_key
)
signature = '0x' + sig_bytes.hex()
cert_dict['signature'] = signature
cert2 = certificate.Certificate(**cert_dict)
certificate.verify(cert2)
ABI
Encode function name and parameters according to ABI.
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"
}
f1 = abi.FUNCTION(abi_dict)
f = abi.Function(f1)
selector = f.selector.hex()
selector == '27fcbb2f'
r = f.encode([1, 'foo'], to_hex=True)
r == '0x27fcbb2f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'
data = '000000000000000000000000abc000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'
r = f.decode(bytes.fromhex(data))
Decode logs according to data and topics.
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)
r = ee.decode(
data=bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'),
topics=[
bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000001')
]
)
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
make install
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 |