diff --git a/convex-cli/src/main/java/convex/cli/key/KeyGenerate.java b/convex-cli/src/main/java/convex/cli/key/KeyGenerate.java index e20c0bea9..b57be5674 100644 --- a/convex-cli/src/main/java/convex/cli/key/KeyGenerate.java +++ b/convex-cli/src/main/java/convex/cli/key/KeyGenerate.java @@ -9,6 +9,7 @@ import convex.cli.ExitCodes; import convex.core.crypto.AKeyPair; import convex.core.crypto.BIP39; +import convex.core.crypto.SLIP10; import convex.core.data.ABlob; import convex.core.data.Blob; import convex.core.data.Blobs; @@ -47,6 +48,12 @@ public class KeyGenerate extends AKeyCommand { description="Type of key generation. Supports random, bip39, entropy") private String type; + @Option(names={"--path"}, + defaultValue=convex.core.Constants.DEFAULT_BIP39_PATH, + description="Derivation path for SLIP-0010 when using BIP39. Default: ${DEFAULT-VALUE}") + private String path; + + @Option(names="--passphrase", description="BIP39 passphrase. If not provided, will be requested from user (or assumed blank in non-interactive mode).") private String passphrase; @@ -78,7 +85,7 @@ private AKeyPair generateKeyPair() { paranoia("Cannot use an empty BIP39 passphrase for key generation with strict security"); } Blob bipseed = BIP39.getSeed(mnemonic, passphrase); - AKeyPair result= BIP39.seedToKeyPair(bipseed); + AKeyPair result=SLIP10.deriveKeyPair(bipseed, path); return result; } else if ("random".equals(type)) { return AKeyPair.generate(); diff --git a/convex-core/src/main/java/convex/core/Constants.java b/convex-core/src/main/java/convex/core/Constants.java index d8542b398..9ba050ae9 100644 --- a/convex-core/src/main/java/convex/core/Constants.java +++ b/convex-core/src/main/java/convex/core/Constants.java @@ -179,5 +179,10 @@ public class Constants { */ public static final int CHAIN_CODE = 864; + /** + * Default derivation path for Convex keys + */ + public static final String DEFAULT_BIP39_PATH = "m/44/"+CHAIN_CODE+"/0/0/0"; + } diff --git a/convex-core/src/main/java/convex/core/crypto/BIP39.java b/convex-core/src/main/java/convex/core/crypto/BIP39.java index 925ccefdd..825ff6cc1 100644 --- a/convex-core/src/main/java/convex/core/crypto/BIP39.java +++ b/convex-core/src/main/java/convex/core/crypto/BIP39.java @@ -515,5 +515,26 @@ public static String checkWords(List words) { public static String extendWord(String abbr) { return ABBR.get(abbr.trim().toLowerCase()); } + + public static int[] parsePath(String path) { + try { + String[] es=path.split("/"); + if (!"m".equals(es[0])) throw new Exception(""); + + int n=es.length-1; + int[] proposedPath=new int[n]; + for (int i=0; i"); + } + } + return proposedPath; + } catch (Exception ex) { + return null; + } + } } diff --git a/convex-core/src/main/java/convex/core/crypto/SLIP10.java b/convex-core/src/main/java/convex/core/crypto/SLIP10.java index 11e5f2e88..42dab537a 100644 --- a/convex-core/src/main/java/convex/core/crypto/SLIP10.java +++ b/convex-core/src/main/java/convex/core/crypto/SLIP10.java @@ -86,5 +86,10 @@ public static AKeyPair deriveKeyPair(Blob bip39seed, int... ixs) { return kp; } + public static AKeyPair deriveKeyPair(Blob bip39seed, String path) { + return deriveKeyPair(bip39seed,BIP39.parsePath(path)); + } + + } diff --git a/convex-core/src/test/java/convex/core/crypto/BIP39Test.java b/convex-core/src/test/java/convex/core/crypto/BIP39Test.java index 6a863cb8a..2ea8ce14d 100644 --- a/convex-core/src/test/java/convex/core/crypto/BIP39Test.java +++ b/convex-core/src/test/java/convex/core/crypto/BIP39Test.java @@ -118,6 +118,28 @@ public void doMnemonicTest(String m) { assertEquals(newGen,BIP39.normaliseFormat(newGen)); } + @Test + public void testDerivePath() { + // Ed25199 Test vector 2 from : https://github.com/satoshilabs/slips/blob/master/slip-0010.md + Blob seed = Blob.fromHex("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"); + { + Blob priv=SLIP10.deriveKeyPair(seed, "m").getSeed(); + assertEquals(priv,SLIP10.deriveKeyPair(seed, new int[0]).getSeed()); + assertEquals("171cb88b1b3c1db25add599712e36245d75bc65a1a5c9e18d76f9f2b1eab4012",priv.toHexString()); + } + + { + Blob priv=SLIP10.deriveKeyPair(seed, "m/0").getSeed(); + assertEquals("1559eb2bbec5790b0c65d8693e4d0875b1747f4970ae8b650486ed7470845635",priv.toHexString()); + } + + { + Blob priv=SLIP10.deriveKeyPair(seed, "m/0/2147483647/1/2147483646/2").getSeed(); + assertEquals("551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d",priv.toHexString()); + } + + + } @Test public void testValidStrings() { diff --git a/convex-gui/src/main/java/convex/gui/keys/KeyGenPanel.java b/convex-gui/src/main/java/convex/gui/keys/KeyGenPanel.java index e06f2ffaa..385a6e9bb 100644 --- a/convex-gui/src/main/java/convex/gui/keys/KeyGenPanel.java +++ b/convex-gui/src/main/java/convex/gui/keys/KeyGenPanel.java @@ -158,7 +158,7 @@ private String checkWarnings(String s, String p) { private void updatePath() { try { String path=derivationArea.getText(); - this.derivationPath=parsePath(path); + this.derivationPath=BIP39.parsePath(path); deriveSeed(); } catch (Exception ex) { privateKeyArea.setText(ex.getMessage()); @@ -168,27 +168,6 @@ private void updatePath() { } } - private static int[] parsePath(String path) { - try { - String[] es=path.split("/"); - if (!"m".equals(es[0])) throw new Exception(""); - - int n=es.length-1; - int[] proposedPath=new int[n]; - for (int i=0; i"); - } - } - return proposedPath; - } catch (Exception ex) { - return null; - } - } - private void updateSeed() { mnemonicArea.setText(""); deriveSeed(); @@ -202,7 +181,7 @@ private void deriveSeed() { Blob mb=SLIP10.getMaster(b); masterKeyArea.setText(mb.toHexString()); Blob db; - this.derivationPath=parsePath(derivationArea.getText()); + this.derivationPath=BIP39.parsePath(derivationArea.getText()); if (derivationPath==null) { db=mb; } else {