Skip to content

Commit

Permalink
fix(core): add tests for Ed25519 Evolution Node key derivation, impro…
Browse files Browse the repository at this point in the history
…ve masternode key tracking (#221)

* fix(core): add unit tests for Ed25519 Evolution Node key derivation

* fix(core): handle provider update service tx and add reset to AuthenticationGroupExtension

* tests(core): add tests for AuthenticationGroupExtension.reset

* feat(examples): save wallet to file with RestoreFromSeedThenDump

---------

Signed-off-by: HashEngineering <[email protected]>
  • Loading branch information
HashEngineering authored Jul 20, 2023
1 parent 764f50f commit bf38a9e
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.io.BaseEncoding;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Base58;
import org.bitcoinj.core.ECKey;
Expand Down Expand Up @@ -777,4 +778,16 @@ public void formatKeyWithAddress(boolean includePrivateKeys, @Nullable KeyParame
builder.append(" ").append(toStringWithPrivate(aesKey, params)).append("\n");
}
}

public byte[] getPrivatePublic() {
checkNotNull(priv);
byte[] result = new byte[64];
System.arraycopy(priv.getEncoded(), 0, result, 0, 32);
System.arraycopy(pub.getEncoded(), 0, result, 32, 32);
return result;
}

public String getPrivatePublicBase64() {
return BaseEncoding.base64().encode(getPrivatePublic());
}
}
16 changes: 11 additions & 5 deletions core/src/main/java/org/bitcoinj/wallet/DerivationPathFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.crypto.ChildNumber;

import java.util.HashMap;

public class DerivationPathFactory {

private NetworkParameters params;
Expand Down Expand Up @@ -199,13 +201,17 @@ public ImmutableList<ChildNumber> bip32DerivationPath(int account) {
.add(new ChildNumber(account, true))
.build();
}
// keep once instance per network
private static final HashMap<String, DerivationPathFactory> instances = new HashMap<>();

// Do we need this?
private static DerivationPathFactory instance;
public static DerivationPathFactory get(NetworkParameters params) {
if(instance == null) {
instance = new DerivationPathFactory(params);
if(instances.get(params.getId()) == null) {
instances.put(params.getId(), new DerivationPathFactory(params));
}
return instance;
return instances.get(params.getId());
}

public NetworkParameters getParams() {
return params;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.protobuf.ByteString;
import org.bitcoinj.core.AbstractBlockChain;
import org.bitcoinj.core.BlockChain;
import org.bitcoinj.core.BloomFilter;
import org.bitcoinj.core.Context;
Expand Down Expand Up @@ -78,6 +79,8 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;

Expand All @@ -86,6 +89,8 @@
import static org.bitcoinj.evolution.ProviderRegisterTx.LEGACY_BLS_VERSION;

public class AuthenticationGroupExtension extends AbstractKeyChainGroupExtension {
public static String EXTENSION_ID = "org.dashj.wallet.authentication";

private static final Logger log = LoggerFactory.getLogger(AuthenticationGroupExtension.class);
private final AuthenticationKeyChainGroup keyChainGroup;
private final HashMap<IKey, AuthenticationKeyUsage> keyUsage = Maps.newHashMap();
Expand Down Expand Up @@ -213,7 +218,7 @@ public boolean supportsEncryption() {
*/
@Override
public String getWalletExtensionID() {
return "org.dashj.wallet.authentication";
return EXTENSION_ID;
}

/**
Expand Down Expand Up @@ -358,8 +363,7 @@ public void processTransaction(Transaction tx, StoredBlock block, BlockChain.New
processRegistration(tx, (ProviderRegisterTx) tx.getExtraPayloadObject());
break;
case TRANSACTION_PROVIDER_UPDATE_SERVICE:
// no keys are updated in this transaction
// TODO -- add IP address to usage!
processUpdateService((ProviderUpdateServiceTx) tx.getExtraPayloadObject());
break;
case TRANSACTION_PROVIDER_UPDATE_REGISTRAR:
processUpdateRegistrar(tx, (ProviderUpdateRegistarTx) tx.getExtraPayloadObject());
Expand Down Expand Up @@ -392,6 +396,14 @@ private void processRevoke(ProviderUpdateRevocationTx providerUpdateRevocationTx
}
}

private void processUpdateService(ProviderUpdateServiceTx providerUpdateServiceTx) {
for (Map.Entry<IKey, AuthenticationKeyUsage> entry : keyUsage.entrySet()) {
if (entry.getValue().getWhereUsed().equals(providerUpdateServiceTx.getProTxHash())) {
entry.getValue().setAddress(providerUpdateServiceTx.getAddress());
}
}
}

private AuthenticationKeyUsage getCurrentOperatorKeyUsage(Sha256Hash proTxHash) {
for (AuthenticationKeyUsage usage : keyUsage.values()) {
if (usage.getType() == AuthenticationKeyChain.KeyChainType.MASTERNODE_OPERATOR) {
Expand Down Expand Up @@ -871,4 +883,17 @@ public void addAuthenticationKeyUsageEventListener(Executor executor, Authentica
public boolean removeAuthenticationKeyUsageEventListener(AuthenticationKeyUsageEventListener listener) {
return ListenerRegistration.removeFromList(listener, usageListeners);
}

public void reset() {
keyUsage.clear();
if (wallet != null) {
Set<Transaction> transactionSet = wallet.getTransactions(false);
List<Transaction> transactionList = Lists.newArrayList(transactionSet);
transactionList.sort((transaction1, transaction2) -> (int) (transaction1.getUpdateTime().getTime() - transaction2.getUpdateTime().getTime()));

for (Transaction tx : transactionList) {
processTransaction(tx, null, AbstractBlockChain.NewBlockType.BEST_CHAIN);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.ChildNumber;

import org.bitcoinj.crypto.IDeterministicKey;
import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.UnreadableWalletException;
import org.junit.Test;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

public class Ed25519DeterministicKeyTest {
Expand Down Expand Up @@ -112,4 +116,34 @@ public void testVector1() {
assertEquals(ImmutableList.of(ChildNumber.ZERO_HARDENED, ChildNumber.ONE_HARDENED, TWO_HARDENED, TWO_HARDENED, BILLION_HARD), levelFive.getPath());
assertEquals(0xd6322ccd, levelFive.getParentFingerprint());
}

@Test
public void testVector2() throws UnreadableWalletException {
String seedPhrase = "enemy check owner stumble unaware debris suffer peanut good fabric bleak outside";
byte[] seed = new DeterministicSeed(seedPhrase, null, "", 0).getSeedBytes();
Ed25519DeterministicKey masterKey = Ed25519HDKeyDerivation.createMasterPrivateKey(seed);

// Derive
IDeterministicKey first = masterKey.deriveChildKey(ChildNumber.NINE_HARDENED);
IDeterministicKey second = first.deriveChildKey(ChildNumber.FIVE_HARDENED);
IDeterministicKey third = second.deriveChildKey(new ChildNumber(3, true));
IDeterministicKey fourth = third.deriveChildKey(new ChildNumber(4, true));
IDeterministicKey fifth = fourth.deriveChildKey(new ChildNumber(0, true));


// from rust dash-core-shared
// m/9'/5'/3'/4'/0' fingerprint 2497558984
// m/9'/5'/3'/4'/0' chaincode 587dc2c7de6d36e06c6de0a2989cd8cb112c1e41b543002a5ff422f3eb1e8cd6
// m/9'/5'/3'/4'/0' secret_key 7898dbaa7ab9b550e3befcd53dc276777ffc8a27124f830c04e17fcf74b9e071
// m/9'/5'/3'/4'/0' public_key_data 08e2698fdcaa0af8416966ba9349b0c8dfaa80ed7f4094e032958a343e45f4b6
// m/9'/5'/3'/4'/0' key_id c9bbba6a3ad5e87fb11af4f10458a52d3160259c
// m/9'/5'/3'/4'/0' base64_keys eJjbqnq5tVDjvvzVPcJ2d3/8iicST4MMBOF/z3S54HEI4mmP3KoK+EFpZrqTSbDI36qA7X9AlOAylYo0PkX0tg==

assertArrayEquals(Utils.HEX.decode("587dc2c7de6d36e06c6de0a2989cd8cb112c1e41b543002a5ff422f3eb1e8cd6"), fifth.getChainCode());
assertArrayEquals(Utils.HEX.decode("7898dbaa7ab9b550e3befcd53dc276777ffc8a27124f830c04e17fcf74b9e071"), fifth.getPrivKeyBytes());
assertArrayEquals(Utils.HEX.decode("0008e2698fdcaa0af8416966ba9349b0c8dfaa80ed7f4094e032958a343e45f4b6"), fifth.getPubKey());
assertArrayEquals(Utils.HEX.decode("c9bbba6a3ad5e87fb11af4f10458a52d3160259c"), fifth.getPubKeyHash());
String privatePublicBase64 = ((Ed25519DeterministicKey)fifth).getPrivatePublicBase64();
assertEquals("eJjbqnq5tVDjvvzVPcJ2d3/8iicST4MMBOF/z3S54HEI4mmP3KoK+EFpZrqTSbDI36qA7X9AlOAylYo0PkX0tg==", privatePublicBase64);
}
}
Loading

0 comments on commit bf38a9e

Please sign in to comment.