Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests(core): add tests for Ed25519 Evolution Node key derivation #221

Merged
merged 4 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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 @@ -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 @@
.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;

Check warning on line 215 in core/src/main/java/org/bitcoinj/wallet/DerivationPathFactory.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/bitcoinj/wallet/DerivationPathFactory.java#L215

Added line #L215 was not covered by tests
}
}
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));
Comment on lines +127 to +131
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here derivation is done manually.



// 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
package org.bitcoinj.wallet.authentication;

import org.bitcoinj.core.AbstractBlockChain;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Context;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.IDeterministicKey;
import org.bitcoinj.crypto.IKey;
import org.bitcoinj.crypto.ed25519.Ed25519DeterministicKey;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.wallet.AuthenticationKeyChain;
import org.bitcoinj.wallet.DerivationPathFactory;
Expand All @@ -35,6 +39,10 @@
import org.junit.Before;
import org.junit.Test;

import java.util.EnumSet;

import static org.bitcoinj.core.Utils.HEX;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
Expand All @@ -44,12 +52,13 @@ public class AuthenticationGroupExtensionTest {
AuthenticationGroupExtension mnext;
UnitTestParams UNITTEST = UnitTestParams.get();
Context context = new Context(UNITTEST);
String seedCode = "enemy check owner stumble unaware debris suffer peanut good fabric bleak outside";
String passphrase = "";
long creationtime = 1547463771L;

@Before
public void setUp() throws UnreadableWalletException {
String seedCode = "enemy check owner stumble unaware debris suffer peanut good fabric bleak outside";
String passphrase = "";
long creationtime = 1547463771L;//1409478661L;


DeterministicSeed seed = new DeterministicSeed(seedCode, null, passphrase, creationtime);
DerivationPathFactory factory = DerivationPathFactory.get(UNITTEST);
Expand All @@ -76,7 +85,7 @@ public void setUp() throws UnreadableWalletException {

@Test
public void processTransactionsTest() {
byte[] providerRegTxData = Utils.HEX.decode("0300010001ca9a43051750da7c5f858008f2ff7732d15691e48eb7f845c791e5dca78bab58010000006b483045022100fe8fec0b3880bcac29614348887769b0b589908e3f5ec55a6cf478a6652e736502202f30430806a6690524e4dd599ba498e5ff100dea6a872ebb89c2fd651caa71ed012103d85b25d6886f0b3b8ce1eef63b720b518fad0b8e103eba4e85b6980bfdda2dfdffffffff018e37807e090000001976a9144ee1d4e5d61ac40a13b357ac6e368997079678c888ac00000000fd1201010000000000ca9a43051750da7c5f858008f2ff7732d15691e48eb7f845c791e5dca78bab580000000000000000000000000000ffff010205064e1f3dd03f9ec192b5f275a433bfc90f468ee1a3eb4c157b10706659e25eb362b5d902d809f9160b1688e201ee6e94b40f9b5062d7074683ef05a2d5efb7793c47059c878dfad38a30fafe61575db40f05ab0a08d55119b0aad300001976a9144fbc8fb6e11e253d77e5a9c987418e89cf4a63d288ac3477990b757387cb0406168c2720acf55f83603736a314a37d01b135b873a27b411fb37e49c1ff2b8057713939a5513e6e711a71cff2e517e6224df724ed750aef1b7f9ad9ec612b4a7250232e1e400da718a9501e1d9a5565526e4b1ff68c028763");
byte[] providerRegTxData = HEX.decode("0300010001ca9a43051750da7c5f858008f2ff7732d15691e48eb7f845c791e5dca78bab58010000006b483045022100fe8fec0b3880bcac29614348887769b0b589908e3f5ec55a6cf478a6652e736502202f30430806a6690524e4dd599ba498e5ff100dea6a872ebb89c2fd651caa71ed012103d85b25d6886f0b3b8ce1eef63b720b518fad0b8e103eba4e85b6980bfdda2dfdffffffff018e37807e090000001976a9144ee1d4e5d61ac40a13b357ac6e368997079678c888ac00000000fd1201010000000000ca9a43051750da7c5f858008f2ff7732d15691e48eb7f845c791e5dca78bab580000000000000000000000000000ffff010205064e1f3dd03f9ec192b5f275a433bfc90f468ee1a3eb4c157b10706659e25eb362b5d902d809f9160b1688e201ee6e94b40f9b5062d7074683ef05a2d5efb7793c47059c878dfad38a30fafe61575db40f05ab0a08d55119b0aad300001976a9144fbc8fb6e11e253d77e5a9c987418e89cf4a63d288ac3477990b757387cb0406168c2720acf55f83603736a314a37d01b135b873a27b411fb37e49c1ff2b8057713939a5513e6e711a71cff2e517e6224df724ed750aef1b7f9ad9ec612b4a7250232e1e400da718a9501e1d9a5565526e4b1ff68c028763");
Transaction proRegTx = new Transaction(UNITTEST, providerRegTxData);

mnext.processTransaction(proRegTx, null, AbstractBlockChain.NewBlockType.BEST_CHAIN);
Expand Down Expand Up @@ -110,7 +119,7 @@ public void processTransactionsTest() {
@Test
public void roundTrip() throws UnreadableWalletException {
// process the ProRegTx
byte[] providerRegTxData = Utils.HEX.decode("0300010001ca9a43051750da7c5f858008f2ff7732d15691e48eb7f845c791e5dca78bab58010000006b483045022100fe8fec0b3880bcac29614348887769b0b589908e3f5ec55a6cf478a6652e736502202f30430806a6690524e4dd599ba498e5ff100dea6a872ebb89c2fd651caa71ed012103d85b25d6886f0b3b8ce1eef63b720b518fad0b8e103eba4e85b6980bfdda2dfdffffffff018e37807e090000001976a9144ee1d4e5d61ac40a13b357ac6e368997079678c888ac00000000fd1201010000000000ca9a43051750da7c5f858008f2ff7732d15691e48eb7f845c791e5dca78bab580000000000000000000000000000ffff010205064e1f3dd03f9ec192b5f275a433bfc90f468ee1a3eb4c157b10706659e25eb362b5d902d809f9160b1688e201ee6e94b40f9b5062d7074683ef05a2d5efb7793c47059c878dfad38a30fafe61575db40f05ab0a08d55119b0aad300001976a9144fbc8fb6e11e253d77e5a9c987418e89cf4a63d288ac3477990b757387cb0406168c2720acf55f83603736a314a37d01b135b873a27b411fb37e49c1ff2b8057713939a5513e6e711a71cff2e517e6224df724ed750aef1b7f9ad9ec612b4a7250232e1e400da718a9501e1d9a5565526e4b1ff68c028763");
byte[] providerRegTxData = HEX.decode("0300010001ca9a43051750da7c5f858008f2ff7732d15691e48eb7f845c791e5dca78bab58010000006b483045022100fe8fec0b3880bcac29614348887769b0b589908e3f5ec55a6cf478a6652e736502202f30430806a6690524e4dd599ba498e5ff100dea6a872ebb89c2fd651caa71ed012103d85b25d6886f0b3b8ce1eef63b720b518fad0b8e103eba4e85b6980bfdda2dfdffffffff018e37807e090000001976a9144ee1d4e5d61ac40a13b357ac6e368997079678c888ac00000000fd1201010000000000ca9a43051750da7c5f858008f2ff7732d15691e48eb7f845c791e5dca78bab580000000000000000000000000000ffff010205064e1f3dd03f9ec192b5f275a433bfc90f468ee1a3eb4c157b10706659e25eb362b5d902d809f9160b1688e201ee6e94b40f9b5062d7074683ef05a2d5efb7793c47059c878dfad38a30fafe61575db40f05ab0a08d55119b0aad300001976a9144fbc8fb6e11e253d77e5a9c987418e89cf4a63d288ac3477990b757387cb0406168c2720acf55f83603736a314a37d01b135b873a27b411fb37e49c1ff2b8057713939a5513e6e711a71cff2e517e6224df724ed750aef1b7f9ad9ec612b4a7250232e1e400da718a9501e1d9a5565526e4b1ff68c028763");
Transaction proRegTx = new Transaction(UNITTEST, providerRegTxData);
mnext.processTransaction(proRegTx, null, AbstractBlockChain.NewBlockType.BEST_CHAIN);

Expand Down Expand Up @@ -148,4 +157,54 @@ public void roundTrip() throws UnreadableWalletException {
AuthenticationKeyUsage platformUsage = mnext.getKeyUsage().get(platformKey);
assertNull(null, platformUsage);
}

@Test
public void keyTest() throws UnreadableWalletException {
DeterministicSeed seed = new DeterministicSeed(seedCode, null, passphrase, creationtime);
AuthenticationGroupExtension mnext = new AuthenticationGroupExtension(MainNetParams.get());
mnext.addKeyChains(MainNetParams.get(), seed, EnumSet.of(
AuthenticationKeyChain.KeyChainType.MASTERNODE_PLATFORM_OPERATOR,
AuthenticationKeyChain.KeyChainType.MASTERNODE_OWNER,
AuthenticationKeyChain.KeyChainType.MASTERNODE_VOTING,
AuthenticationKeyChain.KeyChainType.MASTERNODE_OPERATOR));
IDeterministicKey votingKey = mnext.getKeyChain(AuthenticationKeyChain.KeyChainType.MASTERNODE_VOTING).getKey(0);
IDeterministicKey ownerKey = mnext.getKeyChain(AuthenticationKeyChain.KeyChainType.MASTERNODE_OWNER).getKey(0);
IDeterministicKey operatorKey = mnext.getKeyChain(AuthenticationKeyChain.KeyChainType.MASTERNODE_OPERATOR).getKey(0);
IDeterministicKey platformKey = mnext.getKeyChain(AuthenticationKeyChain.KeyChainType.MASTERNODE_PLATFORM_OPERATOR).getKey(0, true);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, the key derivation is handled by the AuthenticationGroupExtension object.


// owner keys
assertArrayEquals(Utils.HEX.decode("9e2dd89b24a63cc1e8b6d63d651c4bf5f90a4acb83cd5b2028f042c5a8653d05"), ownerKey.getChainCode());
assertArrayEquals(Utils.HEX.decode("bfefac8ea784dc6261348efd847c99863c4707427bf02e3ca97eb356b7e3a3ad"), ownerKey.getPrivKeyBytes());
assertArrayEquals(Utils.HEX.decode("03877e1b244bea3d8428bb1d295b1a17a7970ed0c11b735b07536ef2e48b77c0fc"), ownerKey.getPubKey());
assertArrayEquals(Utils.HEX.decode("9660052877ef6f3b5d1ed740b594a67f351352f3"), ownerKey.getPubKeyHash());
assertEquals("XpPxDdCV5is57YvZyDejDchDH4GjUHrBc8", Address.fromKey(MainNetParams.get(), ownerKey).toBase58());

// voting keys
assertArrayEquals(Utils.HEX.decode("b41896d14cd59908b08f45c544ac9b565c7a225f3ba14178e2f89abe959f9c93"), votingKey.getChainCode());
assertArrayEquals(Utils.HEX.decode("7d236be7e7cdbd44a679ff39028521fda0e7e026cf553fdb0eb355fe861c09b1"), votingKey.getPrivKeyBytes());
assertArrayEquals(Utils.HEX.decode("0231687b08194c86deab2092959165935be25cf9f45563c80d62662f0ce3f61d0a"), votingKey.getPubKey());
assertArrayEquals(Utils.HEX.decode("0a2efcd4f98231bef5176d112c8db9315360f5ad"), votingKey.getPubKeyHash());
assertEquals("XbcgsvqyH7FGYCcQ7wTL9MXTgUpADsrq6J", Address.fromKey(MainNetParams.get(), votingKey).toBase58());


// operator keys
assertArrayEquals(Utils.HEX.decode("ee116f2addea1d05dafb6ee24d81da8f2fd8d74ae94ae809493a3e3f8499a207"), operatorKey.getChainCode());
assertArrayEquals(Utils.HEX.decode("0461b8370ac28e7d7c453ab769d6edc97e9a79e400ae100bfbbf7ea9d834fd2c"), operatorKey.getPrivKeyBytes());
assertArrayEquals(Utils.HEX.decode("8efa464e7d58cfd90f3bb1bac18598928ffc7c112d4c64455c0ec72915fcab770a402c439ccc728288f3369ebbd530a5"), operatorKey.getPubKey());

// platform keys
// 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"), platformKey.getChainCode());
assertArrayEquals(Utils.HEX.decode("7898dbaa7ab9b550e3befcd53dc276777ffc8a27124f830c04e17fcf74b9e071"), platformKey.getPrivKeyBytes());
assertArrayEquals(Utils.HEX.decode("0008e2698fdcaa0af8416966ba9349b0c8dfaa80ed7f4094e032958a343e45f4b6"), platformKey.getPubKey());
assertArrayEquals(Utils.HEX.decode("c9bbba6a3ad5e87fb11af4f10458a52d3160259c"), platformKey.getPubKeyHash());
String privatePublicBase64 = ((Ed25519DeterministicKey)platformKey).getPrivatePublicBase64();
assertEquals("eJjbqnq5tVDjvvzVPcJ2d3/8iicST4MMBOF/z3S54HEI4mmP3KoK+EFpZrqTSbDI36qA7X9AlOAylYo0PkX0tg==", privatePublicBase64);
}
}
Loading