Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ private AbstractTransaction guessTransactionFromTransactionData(
return new Unvote(transactionData.toHashMap());
} else if (TransactionTypeIdentifier.isValidatorRegistration(payload)) {
return new ValidatorRegistration(transactionData.toHashMap());
} else if (TransactionTypeIdentifier.isUpdateValidator(payload)) {
return new ValidatorUpdate(transactionData.toHashMap());
} else if (TransactionTypeIdentifier.isValidatorResignation(payload)) {
return new ValidatorResignation(transactionData.toHashMap());
} else if (TransactionTypeIdentifier.isUsernameRegistration(payload)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package org.arkecosystem.crypto.transactions.builder;

import org.arkecosystem.crypto.identities.BlsPublicKey;
import org.arkecosystem.crypto.encoding.Hex;
import org.arkecosystem.crypto.transactions.types.AbstractTransaction;
import org.arkecosystem.crypto.transactions.types.ValidatorRegistration;
import org.arkecosystem.crypto.utils.ProofOfPossession;

public class ValidatorRegistrationBuilder
extends AbstractTransactionBuilder<ValidatorRegistrationBuilder> {
public ValidatorRegistrationBuilder validatorPublicKey(String validatorPublicKey) {
if (!BlsPublicKey.validate(validatorPublicKey)) {
throw new IllegalArgumentException("Invalid BLS public key");
}

this.transaction.validatorPublicKey = validatorPublicKey;
public ValidatorRegistrationBuilder validatorPassphrase(String passphrase) {
ProofOfPossession.Result pop = ProofOfPossession.fromMnemonic(passphrase);
this.transaction.validatorPublicKey = Hex.encode(pop.pk);
this.transaction.validatorProof = Hex.encode(pop.pop);

this.transaction.refreshPayloadData();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.arkecosystem.crypto.transactions.builder;

import org.arkecosystem.crypto.encoding.Hex;
import org.arkecosystem.crypto.transactions.types.AbstractTransaction;
import org.arkecosystem.crypto.transactions.types.ValidatorUpdate;
import org.arkecosystem.crypto.utils.ProofOfPossession;

public class ValidatorUpdateBuilder extends AbstractTransactionBuilder<ValidatorUpdateBuilder> {

public ValidatorUpdateBuilder validatorPassphrase(String passphrase) {
ProofOfPossession.Result pop = ProofOfPossession.fromMnemonic(passphrase);
this.transaction.validatorPublicKey = Hex.encode(pop.pk);
this.transaction.validatorProof = Hex.encode(pop.pop);

this.transaction.refreshPayloadData();

return this.instance();
}

@Override
protected AbstractTransaction getTransactionInstance() {
return new ValidatorUpdate();
}

@Override
protected ValidatorUpdateBuilder instance() {
return this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public abstract class AbstractTransaction {
public long gasLimit;
public long gasPrice;
public String validatorPublicKey;
public String validatorProof;
public String vote;
public List<String> multipaymentRecipients;
public List<BigInteger> multipaymentAmounts;
Expand Down Expand Up @@ -75,6 +76,9 @@ public AbstractTransaction(Map<String, Object> data) {
if (data.containsKey("validatorPublicKey")) {
this.validatorPublicKey = (String) data.get("validatorPublicKey");
}
if (data.containsKey("validatorProof")) {
this.validatorProof = (String) data.get("validatorProof");
}
if (data.containsKey("vote")) {
this.vote = (String) data.get("vote");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,31 @@

public class ValidatorRegistration extends AbstractTransaction {
public ValidatorRegistration() {
super(); // Call the default constructor of AbstractTransaction
super();
}

public ValidatorRegistration(Map<String, Object> data) {
super(data);

// Use a local decodePayload method since we can't rely on AbstractTransaction's data field
List<Object> payload = decodePayload(data);
if (payload != null && !payload.isEmpty()) {
Object arg = payload.get(0);
this.validatorPublicKey = arg.toString().replaceFirst("^0x", "");
if (payload != null && payload.size() >= 2) {
this.validatorPublicKey = payload.get(0).toString().replaceFirst("^0x", "");
this.validatorProof = payload.get(1).toString().replaceFirst("^0x", "");
}
}

@Override
public String getPayload() {
if (this.validatorPublicKey == null || this.validatorPublicKey.isEmpty()) {
if (this.validatorPublicKey == null
|| this.validatorPublicKey.isEmpty()
|| this.validatorProof == null
|| this.validatorProof.isEmpty()) {
return "";
}

String validatorPublicKeyHex = "0x" + this.validatorPublicKey;
List<Object> args = new ArrayList<>();
args.add(validatorPublicKeyHex);
args.add("0x" + this.validatorPublicKey);
args.add("0x" + this.validatorProof);

try {
return new AbiEncoder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.arkecosystem.crypto.transactions.types;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.arkecosystem.crypto.enums.AbiFunction;
import org.arkecosystem.crypto.utils.AbiEncoder;

public class ValidatorUpdate extends AbstractTransaction {
public ValidatorUpdate() {
super();
}

public ValidatorUpdate(Map<String, Object> data) {
super(data);

List<Object> payload = decodePayload(data);
if (payload != null && payload.size() >= 2) {
this.validatorPublicKey = payload.get(0).toString().replaceFirst("^0x", "");
this.validatorProof = payload.get(1).toString().replaceFirst("^0x", "");
}
}

@Override
public String getPayload() {
if (this.validatorPublicKey == null
|| this.validatorPublicKey.isEmpty()
|| this.validatorProof == null
|| this.validatorProof.isEmpty()) {
return "";
}

List<Object> args = new ArrayList<>();
args.add("0x" + this.validatorPublicKey);
args.add("0x" + this.validatorProof);

try {
return new AbiEncoder()
.encodeFunctionCall(AbiFunction.UPDATE_VALIDATOR.toString(), args);
} catch (Exception e) {
throw new RuntimeException("Error encoding function call", e);
}
}
}
11 changes: 4 additions & 7 deletions src/main/java/org/arkecosystem/crypto/utils/AbiDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -174,20 +174,17 @@ public static Object[] decodeNumber(byte[] bytes, int offset, int bits, boolean

public static Object[] decodeString(byte[] bytes, int offset) {
int dataOffset = readUInt(bytes, offset).intValue();
int stringOffset = offset + dataOffset;
int length = readUInt(bytes, stringOffset).intValue();
byte[] stringData =
Arrays.copyOfRange(bytes, stringOffset + 32, stringOffset + 32 + length);
int length = readUInt(bytes, dataOffset).intValue();
byte[] stringData = Arrays.copyOfRange(bytes, dataOffset + 32, dataOffset + 32 + length);
String value = new String(stringData);

return new Object[] {value, 32};
}

public static Object[] decodeDynamicBytes(byte[] bytes, int offset) {
int dataOffset = readUInt(bytes, offset).intValue();
int bytesOffset = offset + dataOffset;
int length = readUInt(bytes, bytesOffset).intValue();
byte[] bytesData = Arrays.copyOfRange(bytes, bytesOffset + 32, bytesOffset + 32 + length);
int length = readUInt(bytes, dataOffset).intValue();
byte[] bytesData = Arrays.copyOfRange(bytes, dataOffset + 32, dataOffset + 32 + length);
String value = "0x" + Numeric.toHexStringNoPrefix(bytesData);

return new Object[] {value, 32};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.arkecosystem.crypto.utils;

import java.nio.charset.StandardCharsets;
import java.text.Normalizer;
import org.arkecosystem.crypto.encoding.Hex;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import supranational.blst.P1;
import supranational.blst.P2;
import supranational.blst.SecretKey;

public class ProofOfPossession {

private static final String POP_DST = "BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";

public static final class Result {
public final byte[] pk;
public final byte[] pop;

Result(byte[] pk, byte[] pop) {
this.pk = pk;
this.pop = pop;
}
}

public static byte[] deriveBlsPrivateKey(String passphrase) {
return deriveChildSk(passphrase).to_bendian();
}

public static String deriveBlsPublicKey(String passphrase) {
return Hex.encode(new P1(deriveChildSk(passphrase)).compress());
}

public static Result buildProofOfPossession(byte[] secretKeyBytes) {
SecretKey sk = new SecretKey();
sk.from_bendian(secretKeyBytes);
byte[] pk = new P1(sk).compress();
P2 sig = new P2().hash_to(pk, POP_DST).sign_with(sk);
return new Result(pk, sig.compress());
}

public static Result fromMnemonic(String passphrase) {
return buildProofOfPossession(deriveBlsPrivateKey(passphrase));
}

private static SecretKey deriveChildSk(String passphrase) {
byte[] seed = passphraseToSeed(passphrase);
SecretKey master = new SecretKey();
master.derive_master_eip2333(seed);
SecretKey child = new SecretKey();
child.derive_child_eip2333(master, 0L);
return child;
}

private static byte[] passphraseToSeed(String passphrase) {
byte[] pass =
Normalizer.normalize(passphrase, Normalizer.Form.NFKD)
.getBytes(StandardCharsets.UTF_8);
byte[] salt = "mnemonic".getBytes(StandardCharsets.UTF_8);
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest());
gen.init(pass, salt, 2048);
return ((KeyParameter) gen.generateDerivedParameters(512)).getKey();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.arkecosystem.crypto.encoding.Hex;
import org.arkecosystem.crypto.enums.AbiFunction;
import org.arkecosystem.crypto.enums.ContractAbiType;
import org.arkecosystem.crypto.utils.ProofOfPossession;

public final class TransactionEncoder {

Expand Down Expand Up @@ -39,11 +41,12 @@ public static String usernameResignation() {
Collections.emptyList());
}

public static String validatorRegistration(String validatorPublicKey) {
public static String validatorRegistration(String passphrase) {
ProofOfPossession.Result pop = ProofOfPossession.fromMnemonic(passphrase);
return encode(
ContractAbiType.CONSENSUS,
AbiFunction.VALIDATOR_REGISTRATION,
Collections.singletonList(addHexPrefix(validatorPublicKey)));
Arrays.asList(addHexPrefix(Hex.encode(pop.pk)), addHexPrefix(Hex.encode(pop.pop))));
}

public static String validatorResignation() {
Expand All @@ -53,11 +56,12 @@ public static String validatorResignation() {
Collections.emptyList());
}

public static String updateValidator(String validatorPublicKey) {
public static String validatorUpdate(String passphrase) {
ProofOfPossession.Result pop = ProofOfPossession.fromMnemonic(passphrase);
return encode(
ContractAbiType.CONSENSUS,
AbiFunction.UPDATE_VALIDATOR,
Collections.singletonList(addHexPrefix(validatorPublicKey)));
Arrays.asList(addHexPrefix(Hex.encode(pop.pk)), addHexPrefix(Hex.encode(pop.pop))));
}

public static String vote(String voteAddress) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ private static synchronized Map<String, String> signatures() {
map.put("multiPayment", multipaymentMethods.get("pay(address[],uint256[])"));
map.put("registerUsername", usernamesMethods.get("registerUsername(string)"));
map.put("resignUsername", usernamesMethods.get("resignUsername()"));
map.put("registerValidator", consensusMethods.get("registerValidator(bytes)"));
map.put("registerValidator", consensusMethods.get("registerValidator(bytes,bytes)"));
map.put("resignValidator", consensusMethods.get("resignValidator()"));
map.put("vote", consensusMethods.get("vote(address)"));
map.put("unvote", consensusMethods.get("unvote()"));
map.put("updateValidator", consensusMethods.get("updateValidator(bytes)"));
map.put("updateValidator", consensusMethods.get("updateValidator(bytes,bytes)"));
map.put("transfer", "transfer");

signatures = map;
Expand Down
Loading
Loading