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 6b069927b..be53184d8 100644 --- a/convex-gui/src/main/java/convex/gui/keys/KeyGenPanel.java +++ b/convex-gui/src/main/java/convex/gui/keys/KeyGenPanel.java @@ -1,6 +1,5 @@ package convex.gui.keys; -import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.util.List; @@ -35,7 +34,7 @@ @SuppressWarnings("serial") public class KeyGenPanel extends JPanel { - private static final String NOTE_CONSTRAINT = "align 50%,span,width 100:600:1000"; + private static final String NOTE_CONSTRAINT = "align 25%,span,width 100:800:1000"; private static final String TEXTAREA_CONSTRAINT = "grow,width 10:500:800"; JTextArea mnemonicArea; JPasswordField passArea; @@ -56,6 +55,9 @@ public class KeyGenPanel extends JPanel { JPanel formPanel; static Font HEX_FONT=Toolkit.MONO_FONT; + + static final int CC = Constants.CHAIN_CODE; + static final String DEAFULT_BIP32_PATH="m/44/"+CC+"/0/0/0"; /** * Format a hex string in blocks for digits @@ -131,7 +133,7 @@ private String checkWarnings(String s, String p) { if (BIP39.extendWord(badWord)!=null) { warn += "Should normalise abbreviated word: "+badWord+". "; } else { - warn +="Not in standard word list: "+badWord+". "; + warn +="Not in standard word list: "+badWord.toUpperCase()+". "; } } else if (!s.equals(BIP39.normaliseFormat(s))) { warn+="Not normalised! "; @@ -242,17 +244,19 @@ private void generatePublicKey() { * @param manager GUI manager root component */ public KeyGenPanel(PeerGUI manager) { - setLayout(new BorderLayout()); + setLayout(new MigLayout()); // Main Key generation form formPanel = new JPanel(); // formPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); formPanel.setLayout(new MigLayout("wrap 3","[][40]10[grow,fill]","")); - add(formPanel, BorderLayout.CENTER); + add(formPanel, "dock center"); + + addNote("Seed Phrase","Press 'Generate' to create a Mnemonic seed phrase (normally 12 words) or enter words you have already created elsewhere. You should also use a good passphrase. Keep a safe backup." );; { // Mnemonic entry box - addLabel("Mnemonic Phrase","BIP39 Mnemonic phrase. These should be 12, 15, 18, 21 or 24 random words from the BIP39 standard word list."); + addLabel("Mnemonic Phrase","BIP39 Mnemonic phrase. These should be 12, 15, 18, 21 or 24 random words from the BIP39 standard word list.\n\nKeep a secure offline backup of this along with the passphrase so that you can re-generate your key pairs if required. If you are serious about your key security, engraving on metal plates and locking in a safe is a reasonable idea."); mnemonicArea = makeTextArea(); mnemonicArea.setWrapStyleWord(true); mnemonicArea.setLineWrap(true); @@ -269,17 +273,32 @@ public KeyGenPanel(PeerGUI manager) { } { // Passphrase entry box - addLabel("Passphrase","BIP39 secret passphrase. This acts as a secret 'extra word' to generate the BIP39 seed alongside the mnemonic. Strong passphrase recommended."); + addLabel("Passphrase","BIP39 secret passphrase. This acts as a secret 'extra word' to generate the BIP39 seed alongside the mnemonic. Strong passphrase recommended.\n\nYou can leave this blank, but then anyone with access to the mnemonic words can easily re-create and use your keys."); passArea = new JPasswordField(); - passArea.setBackground(Color.BLACK); - formPanel.add(passArea,"grow,width 10:300:400"); + passArea.setBackground(Color.BLACK); + formPanel.add(Toolkit.wrapPasswordField(passArea)); passArea.getDocument().addDocumentListener(Toolkit.createDocumentListener(() -> { if (!passArea.isFocusOwner()) return; updatePass(); })); } - { + { + formPanel.add(new JPanel()); // skip first 2 columns + formPanel.add(new JPanel()); // skip first 2 columns + warningArea = makeTextArea(); + + warningArea.setLineWrap(true); + warningArea.setWrapStyleWord(true); + warningArea.setEditable(false); + warningArea.setToolTipText("This is a quick heuristic check of mnemonic and passphrase.\nHeeding any warnings is advised, but you can ignore them if you know what you are doing (or don't care)."); + formPanel.add(warningArea,TEXTAREA_CONSTRAINT); + } + + addNote("Key Derivation","Below are the BIP39 / SLIP-10 key derivation steps. You can usually ignore these, but they are available in case you want to verify or modify the key derivation steps."); + + + { addLabel("BIP39 Seed","This is the BIP39 seed generated from the mnemonic and passphrase. You can also enter this directly."); seedArea = makeTextArea(); seedArea.setRows(2); @@ -294,20 +313,8 @@ public KeyGenPanel(PeerGUI manager) { })); } - { - formPanel.add(new JPanel()); // skip first 2 columns - formPanel.add(new JPanel()); // skip first 2 columns - warningArea = makeTextArea(); - - warningArea.setLineWrap(true); - warningArea.setWrapStyleWord(true); - warningArea.setEditable(false); - warningArea.setToolTipText("This is a quick heuristic check of mnemonic and passphrase.\nHeeding any warnings is advised, but you can ignore them if you know what you are doing (or don't care)."); - formPanel.add(warningArea,TEXTAREA_CONSTRAINT); - } - addNote("Once the BIP39 seed is generated, we use SLIP-10 to create a derivation path to an Ed25519 private key. \n\nInstead of a BIP39 seed, you can also use another good secret source of random entropy, e.g. SLIP-0039."); { addLabel("SLIP-10 Master Key","SLIP-10 creates a Master Key from the BIP39 seed, which acts as the root of key generation for a heirarchical deterministic wallet."); @@ -321,7 +328,6 @@ public KeyGenPanel(PeerGUI manager) { } { - int CC = Constants.CHAIN_CODE; addLabel("BIP32 Path","This is the hierarchical path for key generation as defined in BIP32. \n - 'm' specifies the master key. \n - 44 is the purpose defined as BIP-44. \n - "+CC+" is the Convex SLIP-0044 chain code. \n - The other numbers (account, change, index) may be set at the user's discretion, but are zero by default. \n\nIf you want multiple keys for the same mnemomic, is is recommended to increment the account number. \n\nWARNING: if you change these values, you will need to retain them in order to recover the specified key."); derivationArea = makeTextArea(); @@ -330,7 +336,7 @@ public KeyGenPanel(PeerGUI manager) { derivationArea.setBackground(Color.BLACK); formPanel.add(derivationArea,TEXTAREA_CONSTRAINT); - derivationArea.setText("m/44/"+CC+"/0/0/0"); + derivationArea.setText(DEAFULT_BIP32_PATH); derivationArea.getDocument().addDocumentListener(Toolkit.createDocumentListener(() -> { if (!derivationArea.isFocusOwner()) return; updatePath(); @@ -346,6 +352,9 @@ public KeyGenPanel(PeerGUI manager) { formPanel.add(derivedKeyArea,TEXTAREA_CONSTRAINT); derivedKeyArea.setText("(not ready)"); } + + addNote("Ed5519 Key Pair","Below is the Ed25519 Key Pair that is actually used by Convex. You can safely share the public key and Identicon image. Keep the private seed secret!"); + { addLabel("Private Ed25519 seed","This is the Ed25519 private seed you need to sign transactions in Convex. \nAny 32-byte hex value will work: you can enter this directly if you obtained a good secret random seed from another source.",true); @@ -373,13 +382,13 @@ public KeyGenPanel(PeerGUI manager) { identicon=new Identicon(null,Toolkit.IDENTICON_SIZE_LARGE); // identicon.setBorder(null); addLabel("Identicon","This is a visual representation of the public key. It can be used to visually identify different keys."); - formPanel.add(identicon,"grow 0"); + formPanel.add(identicon, "growx 0"); //////////////////////////////////////////////////////////////// // Action panel with buttons JPanel actionPanel = new ActionPanel(); - add(actionPanel, BorderLayout.SOUTH); + add(actionPanel, "dock south"); JButton btnRecreate = new ActionButton("Generate",0xe5d5,e -> { Integer wc=(Integer) numSpinner.getValue(); @@ -395,10 +404,10 @@ public KeyGenPanel(PeerGUI manager) { numSpinner.setFocusable(false); actionPanel.add(numSpinner); - JButton btnNewButton = new ActionButton("Export...",0xebbe,e->{ - - }); - actionPanel.add(btnNewButton); +// JButton btnNewButton = new ActionButton("Export...",0xebbe,e->{ +// +// }); +// actionPanel.add(btnNewButton); { // Button to Normalise Mnemonic string JButton btnNormalise = new ActionButton("Normalise Mnemonic",0xf0ff,e -> { @@ -407,7 +416,7 @@ public KeyGenPanel(PeerGUI manager) { mnemonicArea.setText(s2); updateMnemonic(); }); - btnNormalise.setToolTipText("Press to normalise mnemonic text according to BIP39. Removes irregular whitespace, sets characters to lowercase."); + btnNormalise.setToolTipText("Press to normalise mnemonic text according to BIP39. Removes irregular whitespace, sets characters to lowercase, highlights faulty words."); actionPanel.add(btnNormalise); } @@ -434,8 +443,8 @@ private void addLabel(String labelText,String helpText, boolean bold) { formPanel.add(Toolkit.makeHelp(helpText)); } - private void addNote(String s) { - JComponent ta = Toolkit.makeNote("NOTE",s); + private void addNote(String title,String s) { + JComponent ta = Toolkit.makeNote(title,s); formPanel.add(ta,NOTE_CONSTRAINT); } diff --git a/convex-gui/src/main/java/convex/gui/keys/KeyRingPanel.java b/convex-gui/src/main/java/convex/gui/keys/KeyRingPanel.java index cf54b9fb9..c2ecabfbf 100644 --- a/convex-gui/src/main/java/convex/gui/keys/KeyRingPanel.java +++ b/convex-gui/src/main/java/convex/gui/keys/KeyRingPanel.java @@ -129,12 +129,12 @@ public KeyRingPanel() { private void loadStore(ActionEvent e) { try { - File f=chooseKeyStore(this); + File f=chooseKeyStore(this,"Load"); if (f==null) return; if (f.exists()) { KeyStore ks=PFXTools.loadStore(f, null); int num=loadKeys(ks,"Loaded from "+f.getCanonicalPath()); - Toolkit.showMessge(this,num+" new keys imported."); + Toolkit.showMessge(this,num+" new keys loaded."); } } catch (IOException | GeneralSecurityException e1) { // TODO Auto-generated catch block @@ -147,15 +147,17 @@ private void loadStore(ActionEvent e) { * @param parent * @return */ - private static File chooseKeyStore(Component parent) { + private static File chooseKeyStore(Component parent,String action) { JFileChooser chooser = new JFileChooser(); FileNameExtensionFilter filter = new FileNameExtensionFilter("PKCS #12 Keystore", "p12", "pfx"); chooser.setFileFilter(filter); File defaultDir=FileUtils.getFile(Constants.DEFAULT_KEYSTORE_FILENAME); if (defaultDir.isDirectory()) { chooser.setCurrentDirectory(defaultDir); + } else { + chooser.setCurrentDirectory(defaultDir.getParentFile()); } - int returnVal = chooser.showOpenDialog(parent); + int returnVal = chooser.showDialog(parent,action); if(returnVal != JFileChooser.APPROVE_OPTION) return null; File f=chooser.getSelectedFile(); return f; @@ -247,7 +249,7 @@ public static AWalletEntry getKeyRingEntry(AccountKey publicKey) { public static boolean saveKey(Component parent, AWalletEntry walletEntry) { boolean locked=walletEntry.isLocked(); try { - File f=chooseKeyStore(parent); + File f=chooseKeyStore(parent,"Save Key"); if (f==null) return false; KeyStore ks; if (f.exists()) { diff --git a/convex-gui/src/main/java/convex/gui/keys/UnlockWalletDialog.java b/convex-gui/src/main/java/convex/gui/keys/UnlockWalletDialog.java index 6f724ac47..ff1fe2ad3 100644 --- a/convex-gui/src/main/java/convex/gui/keys/UnlockWalletDialog.java +++ b/convex-gui/src/main/java/convex/gui/keys/UnlockWalletDialog.java @@ -63,8 +63,8 @@ public UnlockWalletDialog(AWalletEntry walletEntry) { passwordField = new JPasswordField(); passwordField.setFont(new Font("Monospaced", Font.BOLD, 13)); - passwordField.setColumns(20); - passPanel.add(passwordField); + passwordField.setColumns(40); + passPanel.add(Toolkit.wrapPasswordField(passwordField)); mainPanel.add(passPanel); diff --git a/convex-gui/src/main/java/convex/gui/utils/Toolkit.java b/convex-gui/src/main/java/convex/gui/utils/Toolkit.java index dd63274d9..7d66c6d05 100644 --- a/convex-gui/src/main/java/convex/gui/utils/Toolkit.java +++ b/convex-gui/src/main/java/convex/gui/utils/Toolkit.java @@ -32,6 +32,8 @@ import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JTextArea; @@ -52,6 +54,7 @@ import com.formdev.flatlaf.util.SystemInfo; import convex.core.util.Utils; +import net.miginfocom.swing.MigLayout; @SuppressWarnings("serial") public class Toolkit { @@ -339,6 +342,11 @@ public static Border createEmptyBorder(int x) { public static JComponent makeHelp(String helpText) { JLabel help=new JLabel(SymbolIcon.get(0xe887,Toolkit.SMALL_ICON_SIZE)); help.setToolTipText(helpText); + help.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + JOptionPane.showMessageDialog(help, helpText,"Help Tips",JOptionPane.INFORMATION_MESSAGE); + } + }); return help; } @@ -395,4 +403,27 @@ public static void showErrorMessage(Component parent, String attemptFailure,Exce JOptionPane.showMessageDialog(parent, attemptFailure+ "\n"+e.getMessage()); } + public static Component wrapPasswordField(JPasswordField passArea) { + char ec=passArea.getEchoChar(); + JPanel panel=new JPanel(); + JLabel eye=new JLabel(SymbolIcon.get(0xe8f4)); + eye.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + char c=passArea.getEchoChar(); + if (c==0) { + eye.setIcon(SymbolIcon.get(0xe8f4)); + passArea.setEchoChar(ec); + } else { + eye.setIcon(SymbolIcon.get(0xe8f5)); + passArea.setEchoChar((char)0); + } + } + }); + panel.setBorder(null); + panel.setLayout(new MigLayout()); + panel.add(passArea,"dock center"); + panel.add(eye,"dock east"); + return panel; + } + }