Java (8+) library to assist a smoother development process on VeChainThor for all 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.
- Checkout the JAR file on the "release" page.
- Or build locally with
gradle build(higher security).
This SDK is build on top of gson, guava and other libraries. See "build.gradle" to include those dependencies manually in your project.
import org.vechain.devkit.cry.Utils;
import org.vechain.devkit.cry.Secp256k1;
byte[] priv = Secp256k1.newPrivateKey(); // byte[32].
byte[] pub = Secp256k1.derivePublicKey(priv, false); // byte[65].
byte[] addr = Address.publicKeyToAddressBytes(pub); // byte[20].
String address = "0x" + Utils.bytesToHex(addr);
System.out.println(address);
// 0x63ad8a6d015ae579ad128e0c63040bb860cc5d34
String checksumAddress = Address.toChecksumAddress(address); // String.
System.out.println(checksumAddress);
// 0x63ad8A6D015aE579ad128e0c63040bB860Cc5D34import java.util.Arrays;
import org.vechain.devkit.cry.Keccak;
import org.vechain.devkit.cry.Secp256k1;
import org.vechain.devkit.cry.Signature;
import org.vechain.devkit.cry.Utils;
byte[] priv = Utils.hexToBytes(
"7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a"
); // byte[32].
byte[] msgHash = Keccak.keccak256(
Utils.AsciiToBytes("hello world")
); // byte[32].
// Sign the message hash.
byte[] sigBytes = Secp256k1.sign(msgHash, priv);// byte[65].
// Recover public key from given message hash and signature.
byte[] pub = Secp256k1.recover(
msgHash,
new Signature(sigBytes).getECDSASignature(),
new Signature(sigBytes).getV()
); // byte[65].
// Verify if the public key matches.
Arrays.equals(pub, Secp256k1.derivePublicKey(priv, false));// true.import org.vechain.devkit.cry.Mnemonic;
List<String> words = Mnemonic.generate(128);
System.out.println(words);
// [carry, slow, attack, december, number, film, scale, faith, can, old, cage, expose]
boolean flag = Mnemonic.validate(words);
System.out.println(flag); // true.
// Quickly get a Bip32 master seed for HD wallets.
// How to use the seed? See "HD wallet" below.
byte[] seed = Mnemonic.derive_seed(words);
// Quickly get a private key at index 0.
// Need to generate more? See "HD wallet" below.
byte[] priv = Mnemonic.derive_private_key(words, 0);Hierarchical Deterministic Wallets. See bip-32 and bip-44.
import java.util.List;
import com.google.common.base.Splitter;
import org.vechain.devkit.cry.Address;
import org.vechain.devkit.cry.HDNode;
import org.vechain.devkit.cry.Utils;
String sentence = "ignore empty bird silly journey junior ripple have guard waste between tenant";
List<String> words = Splitter.on(" ").splitToList(sentence);
// Construct an HD node from words. (Recommended)
HDNode topMostNode = HDNode.fromMnemonic(words);
// Or, construct from seed. (Advanced)
String seed_hex = "28bc19620b4fbb1f8892b9607f6e406fcd8226a0d6dc167ff677d122a1a64ef936101a644e6b447fd495677f68215d8522c893100d9010668614a68b3c7bb49f";
HDNode topMostNode2 = HDNode.fromSeed(
Utils.hexToBytes(seed_hex)
);
// Access the HD node's properties.
byte[] priv = topMostNode.getPrivateKey();
byte[] pub = topMostNode.getPublicKey();
byte[] cc = topMostNode.getChainCode();
// Or, construct from a private key. (Advanced)
HDNode topMostNode3 = HDNode.fromPrivateKey(priv, cc);
// Or, construct from a public key. (Advanced)
// Notice: This HD node CANNOT derive child HD node contains "private key".
HDNode topMostNode4 = HDNode.fromPublicKey(pub, cc);
// Let it derive further child HD nodes.
for (int i = 0; i < 3; i++) {
HDNode child = topMostNode.derive(i);
System.out.println(
"addr: " + Address.publicKeyToAddressString(child.getPublicKey())
);
System.out.println(
"priv: " + Utils.bytesToHex(child.getPrivateKey())
);
}
// addr: 0x339fb3c438606519e2c75bbf531fb43a0f449a70
// priv: 27196338e7d0b5e7bf1be1c0327c53a244a18ef0b102976980e341500f492425
// addr: 0x5677099d06bc72f9da1113afa5e022feec424c8e
// priv: cf44074ec3bf912d2a46b7c84fa6eb745652c9c74e674c3760dc7af07fc98b62
// addr: 0x86231b5cdcbfe751b9ddcd4bd981fc0a48afe921
// priv: 2ca054a50b53299ea3949f5362ee1d1cfe6252fbe30bea3651774790983e9348import org.vechain.devkit.cry.Keystore;
// You need Java (15+) and up to use text blocks.
// Otherwise just use a StringBuilder.
String 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""
}
}
""";
// Must be UTF_8 string.
String password = "123456";
// Decrypt from keystore to a private key.
byte[] priv = Keystore.decrypt(ks, password);
// Encrypt from a private key to a keystore.
String ks = Keystore.encrypt(priv, password, true);import org.vechain.devkit.cry.Keccak;
import org.vechain.devkit.cry.Blake2b;
import org.vechain.devkit.cry.Utils;
String input = "hello world";
String[] inputs = {"hello", " ", "world"};
byte[] output1 = Keccak.keccak256(Utils.AsciiToBytes(input));
byte[] output2 = Keccak.keccak256(
Utils.AsciiToBytes(inputs[0]),
Utils.AsciiToBytes(inputs[1]),
Utils.AsciiToBytes(inputs[2])
); // output1 == outpu2
byte[] output3 = Blake2b.blake2b256(Utils.AsciiToBytes(input));
byte[] output4 = Blake2b.blake2b256(
Utils.AsciiToBytes(inputs[0]),
Utils.AsciiToBytes(inputs[1]),
Utils.AsciiToBytes(inputs[2])
); // output3 == outpu4import org.vechain.devkit.Bloom;
import org.vechain.devkit.cry.Utils;
// Create a bloom filter that stores 100 items.
int k = Bloom.estimateK(100);
Bloom b = new Bloom(k);
// Add to it.
b.add(Utils.UTF8ToBytes("hello world"));
// Test if exists.
b.test(Utils.UTF8ToBytes("hello world")); // true.
b.test(Utils.UTF8ToBytes("bye bye blue bird")); // false.import org.vechain.devkit.cry.Utils;
// hex -> byte[]
assert new byte[]{15,15} == Utils.hexToBytes("0F0F") // true
// byte[] -> hex
assert Utils.bytesToHex(new byte[]{15,15}) == "0f0f"; // true
// ascii -> byte[]
assert new byte[]{49,50,51} == Utils.AsciiToBytes("123") // trueimport org.vechain.devkit.Function;
import org.vechain.devkit.cry.Utils;
// You need Java (15+) and up to use text blocks.
// Otherwise just use a StringBuilder.
String f1 = """
{
"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"
}
"""; // Function definition.
Function f = new Function(f1);
// Calculate the selector of the function.
assert f.selector() == Utils.hexToBytes("27fcbb2f");
// Encode a function call with params (1, "foo").
assert f.encodeToHex(true, BigInteger.valueOf(1), "foo") == "0x27fcbb2f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000";
// Alternatively,
// f.encodeToBytes() -> to byte[]
// f.encode() -> to ByteBufferimport org.vechain.devkit.Function;
import org.vechain.devkit.cry.Utils;
// You need Java (15+) and up to use text blocks.
// Otherwise just use a StringBuilder.
String f1 = """
{
"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"
}
"""; // Function definition.
Function f = new Function(f1);
// The function call return value.
final byte[] data = Utils.hexToBytes("000000000000000000000000abc000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000");
// Decode above data to JSON String. (ABI v1)
String decoded = f.decodeReturnV1Json(data, true, true);
String expected = """
[
{
"index": 0,
"name": "r1",
"canonicalType": "address",
"value": "0xabc0000000000000000000000000000000000001"
},
{
"index": 1,
"name": "r2",
"canonicalType": "bytes",
"value": "0x666f6f"
}
]
""" // decoded == expected
// Alternatively, f.decodeReturnV1() -> Get raw Java types.
List<V1ParamWrapper> result = f.decodeReturnV1(data, true);
result.get(0).name; // "r1"
result.get(0).value; // "0xabc0000000000000000000000000000000000001"
result.get(1).name; // "r2"
result.get(1).value; // "0x666f6f"import org.vechain.devkit.Function;
import org.vechain.devkit.cry.Utils;
String f2 = """
{
"inputs": [],
"name": "getBigNumbers",
"outputs": [
{
"internalType": "uint256",
"name": "a",
"type": "uint256"
},
{
"internalType": "int256",
"name": "b",
"type": "int256"
}
],
"stateMutability": "pure",
"type": "function"
}
""";
Function f = new Function(f2);
// The function call return value in bytes.
byte[] data = Utils.hexToBytes("000000000000000000000000000000000000000000000000000000000001e240fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1dc0");
// For unit64 and larger number types (eg. int72, unit256, address),
// decode to BigInteger or to human-readable String.
// human=false
List<V1ParamWrapper> result = f.decodeReturnV1(data, false);
result.get(0).value; // BigInteger("123456");
result.get(1).value; // BigInteger("-123456");
// For bytes1 ~ bytes32 and bytes[],
// decode to byte[] or human-readable hex String.
// human=true
List<V1ParamWrapper> result = f.decodeReturnV1(data, true);
result.get(0).value;// "123456"
result.get(1).value;// "-123456"import org.vechain.devkit.Event;
import org.vechain.devkit.cry.Utils;
String e1 = """
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "a1",
"type": "uint256"
},
{
"indexed": false,
"name": "a2",
"type": "string"
}
],
"name": "E1",
"type": "event"
}
""";
Event e = new Event(e1);
// Calculate Signature
byte[] expected = Utils.hexToBytes("47b78f0ec63d97830ace2babb45e6271b15a678528e901a9651e45b65105e6c2");
assert e.calcEventSignature() == expected;
// Suppose we have topics coming from servers (indexed params)
List<byte[]> topics = new ArrayList<byte[]>();
topics.add(Utils.hexToBytes("47b78f0ec63d97830ace2babb45e6271b15a678528e901a9651e45b65105e6c2"));
topics.add(Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000001"));
// Decode the topics.
List<V1ParamWrapper> indexedParams = e.decodeTopics(topics, false);
indexedParams.size(); // 1
indexedParams.get(0).canonicalType; // "uint256"
indexedParams.get(0).name; // "a1"
indexedParams.get(0).value; // BigInteger("1")
// Alternatively,
// e.decodeTopicsJson() -> Decode to JSON String.
// Data (non-indexed params)
byte[] data = Utils.hexToBytes("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000");
// Deocde the data.
List<V1ParamWrapper> nonIndexedParams = e.decodeDataV1(data, true);
nonIndexedParams.size(); // 1
nonIndexedParams.get(0).name; // "a2"
nonIndexedParams.get(0).canonicalType; // "string"
nonIndexedParams.get(0).value; // "foo"
// Alternatively,
// e.decodeDataV1Json() -> Decode to JSON String.// Transaction Structure:
// See: https://docs.vechain.org/thor/learn/transaction-model.html#model
import org.vechain.devkit.types.Clause;
import org.vechain.devkit.Transaction;
import org.vechain.devkit.cry.Blake2b;
import org.vechain.devkit.cry.Secp256k1;
import org.vechain.devkit.cry.Utils;
// Set up clauses.
Clause[] clauses = new Clause[]{
new Clause(
"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed", // to
"10000", // value
"0x000000606060" // data
),
new Clause(
"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed",
"20000",
"0x000000606060"
)
};
// Create a tx.
Transaction tx = new Transaction(
"1", // chainTag
"0x00000000aabbccdd", // blockRef
"32", // expiration
clauses, // clauses
"128", // gasPriceCoef
"21000", // gas
null, // dependsOn
"12345678", // nonce
null // reserved
);
// Sign the tx.
byte[] privateKey = Utils.hexToBytes("7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a");
byte[] h = Blake2b.blake2b256(
tx.encode() // unsigned tx encoded.
);
byte[] sig = Secp256k1.sign(h, privateKey);
// Set signature on tx.
tx.setSignature(sig);
// Properties.
tx.getId();
tx.getSignature();
tx.getIntrinsicGas(); // 37432
tx.getOriginAsAddressBytes();
tx.getOriginAsAddressString();
// Signed tx encoded.
byte[] encodedTx = tx.encode();
// Then you can HTTP POST to send the encodedTx 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/https://github.com/vechain/VIPs/blob/master/vips/VIP-191.md
import com.google.common.primitives.Bytes;
import org.vechain.devkit.types.Clause;
import org.vechain.devkit.Transaction;
import org.vechain.devkit.cry.Address;
import org.vechain.devkit.cry.Blake2b;
import org.vechain.devkit.cry.Secp256k1;
import org.vechain.devkit.cry.Utils;
// Set up clauses.
Clause[] clauses = new Clause[]{
new Clause(
"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed", // to
"10000", // value
"0x000000606060" // data
),
new Clause(
"0x7567d83b7b8d80addcb281a71d54fc7b3364ffed",
"20000",
"0x000000606060"
)
};
// Reserved: VIP-191
Reserved reserved = new Reserved(1, null);
// Create a tx.
Transaction tx = new Transaction(
"1", // chainTag
"0x00000000aabbccdd", // blockRef
"32", // expiration
clauses, // clauses
"128", // gasPriceCoef
"21000", // gas
null, // dependsOn
"12345678", // nonce
reserved // <--- reserved (VIP-191)
);
// Sender
byte[] priv_1 = Utils.hexToBytes("58e444d4fe08b0f4d9d86ec42f26cf15072af3ddc29a78e33b0ceaaa292bcf6b");
byte[] addr_1 = Address.publicKeyToAddressBytes(Secp256k1.derivePublicKey(priv_1, false));
// Gas Payer
byte[] priv_2 = Utils.hexToBytes("0bfd6a863f347f4ef2cf2d09c3db7b343d84bb3e6fc8c201afee62de6381dc65");
byte[] addr_2 = Address.publicKeyToAddressBytes(Secp256k1.derivePublicKey(priv_2, false));
// Sender sign the message himself.
byte[] h = tx.getSigningHash(null);
byte[] senderHash = Secp256k1.sign(h, priv_1);
// Gas payer sign the hash for the sender.
byte[] dh = tx.getSigningHash("0x" + Utils.bytesToHex(addr_1));
byte[] payerHash = Secp256k1.sign(dh, priv_2);
// Assemble signature
byte[] sig = Bytes.concat(senderHash, payerHash); // 130 bytes
// Set the signature onto the tx.
tx.setSignature(sig);
tx.getOriginAsAddressBytes(); // Sender: addr_1
tx.getDeleagtorAsAddressBytes(); // Gas Payer: addr_2
// Signed tx encoded.
byte[] encodedTx = tx.encode();
// Then you can HTTP POST to send the encodedTx 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/https://github.com/vechain/VIPs/blob/master/vips/VIP-192.md
import org.vechain.devkit.Certificate;
import org.vechain.devkit.cry.Address;
import org.vechain.devkit.cry.Blake2b;
import org.vechain.devkit.cry.Secp256k1;
import org.vechain.devkit.cry.Utils;
/* For a Certificate looks like this:
{
"purpose": "identification",
"payload": {
"type": "text",
"content": "fyi"
},
"domain": "localhost",
"timestamp": 1545035330,
"signer": "0xd989829d88b0ed1b06edf5c50174ecfa64f14a64"
}
*/
byte[] priv = Utils.hexToBytes("7582be841ca040aa940fff6c05773129e135623e41acce3e0b8ba520dc1ae26a");
byte[] addr = Address.publicKeyToAddressBytes(Secp256k1.derivePublicKey(priv, false));
// Create a Certificate.
Map<String, String> payload = new TreeMap<String, String>();
payload.put("type", "text");
payload.put("content", "fyi");
Certificate c = new Certificate(
"identification", // purpose
payload, // payload
"localhost", // domian
1545035330, // timestamp
"0x" + Utils.bytesToHex(addr), // signer
null // signature
);
// Or create from some external json string.
Certificate c2 = Certificate.fromJsonString(...);
// Or create from some external Map<String, Object>.
Certificate c3 = Certificate.fromMap(...);
// Sign the cert.
// 1) Calculate signature.
String j = c.toJsonString();
byte[] signingHash = Blake2b.blake2b256(Utils.UTF8ToBytes(j));
byte[] sig = Secp256k1.sign(signingHash, priv);
// 2) Set signature on cert.
c.setSignature("0x" + Utils.bytesToHex(sig));
// 3) Verify. If signature matches this cert.
Certificate.verify(c);── devkit
├── Bloom.java
├── Certificate.java
├── Event.java
├── Function.java
├── Transaction.java
├── cry
│ ├── Address.java
│ ├── Blake2b.java
│ ├── HDNode.java
│ ├── Keccak.java
│ ├── Keystore.java
│ ├── Mnemonic.java
│ ├── Secp256k1.java
│ ├── Signature.java
│ └── Utils.java
└── types
├── BlobKind.java
├── Clause.java
├── CompactFixedBlobKind.java
├── FixedBlobKind.java
├── NullableFixedBlobKind.java
├── NumericKind.java
├── Reserved.java
├── ScalarKind.java
└── V1ParamWrapper.java
gradle test| 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 |