├── .gitignore ├── pom.xml ├── readme.md └── src ├── main ├── java │ └── com │ │ └── ldd │ │ ├── App.java │ │ ├── constant │ │ └── NodeConstant.java │ │ ├── foundation │ │ ├── crypto │ │ │ ├── AES.java │ │ │ ├── CipherParams.java │ │ │ ├── Crypto.java │ │ │ ├── EncPair.java │ │ │ ├── Hash.java │ │ │ ├── KDFParams.java │ │ │ ├── Keccak.java │ │ │ ├── Multihash.java │ │ │ ├── PBKDF2Crypto.java │ │ │ ├── PBKDF2Params.java │ │ │ ├── SCryptCrypto.java │ │ │ └── SCryptParams.java │ │ ├── rlp │ │ │ ├── RlpEncoder.java │ │ │ ├── RlpList.java │ │ │ ├── RlpString.java │ │ │ └── RlpType.java │ │ └── utils │ │ │ ├── ByteUtil.java │ │ │ ├── CachedDerivedKey.java │ │ │ ├── DateUtil.java │ │ │ ├── MetaUtil.java │ │ │ ├── MnemonicUtil.java │ │ │ └── NumericUtil.java │ │ └── wallet │ │ ├── Identity.java │ │ ├── KeystoreStorage.java │ │ ├── Wallet.java │ │ ├── WalletManager.java │ │ ├── address │ │ ├── AddressCreator.java │ │ ├── AddressCreatorManager.java │ │ ├── BitcoinAddressCreator.java │ │ ├── EthereumAddressCreator.java │ │ ├── SegWitBitcoinAddressCreator.java │ │ └── TronAddressCreator.java │ │ ├── keystore │ │ ├── EncMnemonicKeystore.java │ │ ├── ExportableKeystore.java │ │ ├── HDMnemonicKeystore.java │ │ ├── IMTKeystore.java │ │ ├── IdentityKeystore.java │ │ ├── Keystore.java │ │ ├── V3Ignore.java │ │ ├── V3Keystore.java │ │ ├── V3MnemonicKeystore.java │ │ └── WalletKeystore.java │ │ ├── model │ │ ├── BIP44Util.java │ │ ├── ChainId.java │ │ ├── ChainType.java │ │ ├── KeyPair.java │ │ ├── Messages.java │ │ ├── Metadata.java │ │ ├── MnemonicAndPath.java │ │ ├── MultiTo.java │ │ ├── Network.java │ │ └── TokenException.java │ │ ├── network │ │ ├── BitcoinCashMainNetParams.java │ │ ├── BitcoinSvMainNetParams.java │ │ ├── DashMainNetParams.java │ │ ├── DogecoinMainNetParams.java │ │ └── LitecoinMainNetParams.java │ │ ├── transaction │ │ ├── BitcoinTransaction.java │ │ ├── EthereumSign.java │ │ ├── EthereumTransaction.java │ │ ├── MyHMacDSAKCalculator.java │ │ ├── SignatureData.java │ │ ├── TransactionSigner.java │ │ ├── TxMultiSignResult.java │ │ └── TxSignResult.java │ │ └── validators │ │ ├── ETHAddressValidator.java │ │ ├── MetadataValidator.java │ │ ├── PrivateKeyValidator.java │ │ ├── Validator.java │ │ └── WIFValidator.java └── resources │ └── logback.xml └── test └── java └── com └── ldd ├── AppTest.java ├── btc └── BtcTest.java ├── eth └── EthTest.java ├── test ├── a.txt └── b.txt └── trx └── TrxTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /target/ -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 4.0.0 7 | 8 | com.ldd 9 | WalletBase 10 | 1.0-SNAPSHOT 11 | 12 | WalletBase 13 | 14 | http://www.example.com 15 | 16 | 17 | UTF-8 18 | 1.7 19 | 1.7 20 | 21 | 22 | 23 | 24 | junit 25 | junit 26 | 4.13.1 27 | test 28 | 29 | 30 | com.alibaba 31 | fastjson 32 | 1.2.74 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | 1.16.20 38 | 39 | 40 | org.bitcoinj 41 | bitcoinj-core 42 | 0.14.7 43 | 44 | 45 | com.google.guava 46 | guava 47 | 48 | 49 | 50 | 51 | com.google.protobuf 52 | protobuf-java 53 | 3.5.1 54 | 55 | 56 | com.fasterxml.jackson.core 57 | jackson-databind 58 | 2.9.10 59 | 60 | 61 | com.google.guava 62 | guava 63 | 30.0-android 64 | 65 | 66 | com.google.guava 67 | guava 68 | 30.0-jre 69 | 70 | 71 | org.json 72 | json 73 | 20170516 74 | 75 | 76 | org.slf4j 77 | slf4j-api 78 | 1.7.25 79 | 80 | 81 | com.squareup.okhttp3 82 | okhttp 83 | 3.12.1 84 | 85 | 86 | org.web3j 87 | core 88 | 4.5.10 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | maven-clean-plugin 98 | 3.1.0 99 | 100 | 101 | 102 | maven-resources-plugin 103 | 3.0.2 104 | 105 | 106 | maven-compiler-plugin 107 | 3.8.0 108 | 109 | 110 | maven-surefire-plugin 111 | 2.22.1 112 | 113 | 114 | maven-jar-plugin 115 | 3.0.2 116 | 117 | 118 | maven-install-plugin 119 | 2.5.2 120 | 121 | 122 | maven-deploy-plugin 123 | 2.8.2 124 | 125 | 126 | 127 | maven-site-plugin 128 | 3.7.1 129 | 130 | 131 | maven-project-info-reports-plugin 132 | 3.0.0 133 | 134 | 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-compiler-plugin 140 | 141 | 8 142 | 8 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## BTC/ETH/TRX钱包 2 | ### BTC 3 | - 助记词导入 4 | - 私钥导入 5 | - 生成普通地址/隔离见证地址/找零地址 6 | - 生成子地址 7 | - 私钥格式转换 8 | - 查询钱包余额(根据UTXO计算) 9 | - 转账签名 10 | ### ETH 11 | - 助记词导入 12 | - 私钥导入 13 | - keystore导入 14 | - 查询ETH余额 15 | - 查询token余额/精度 16 | - eth转账 17 | - eth转账(离线签名) 18 | - ERC20代币转账(离线签名) 19 | ### TRX 20 | - 助记词导入 21 | - 私钥导入 22 | - keystore导入 -------------------------------------------------------------------------------- /src/main/java/com/ldd/App.java: -------------------------------------------------------------------------------- 1 | package com.ldd; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | 8 | public static void main(String[] args) { 9 | System.out.println("Hello World!"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/constant/NodeConstant.java: -------------------------------------------------------------------------------- 1 | package com.ldd.constant; 2 | 3 | public class NodeConstant { 4 | //https://mainnet.infura.io/v3/ea23a1df0c16486ab0dd0f48a6566f4c 5 | //https://ropsten.infura.io/v3/ea23a1df0c16486ab0dd0f48a6566f4c 6 | //1 7 | //2 8 | //34567810 9 | 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/AES.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import javax.crypto.Cipher; 4 | import javax.crypto.spec.IvParameterSpec; 5 | import javax.crypto.spec.SecretKeySpec; 6 | 7 | public class AES { 8 | 9 | public enum AESType { 10 | CTR, CBC 11 | } 12 | 13 | public static byte[] encryptByCTR(byte[] data, byte[] key, byte[] iv) { 14 | return doAES(data, key, iv, Cipher.ENCRYPT_MODE, AESType.CTR, "PKCS5Padding"); 15 | } 16 | 17 | public static byte[] decryptByCTR(byte[] ciphertext, byte[] key, byte[] iv) { 18 | return doAES(ciphertext, key, iv, Cipher.DECRYPT_MODE, AESType.CTR, "PKCS5Padding"); 19 | } 20 | 21 | public static byte[] encryptByCBC(byte[] data, byte[] key, byte[] iv) { 22 | return doAES(data, key, iv, Cipher.ENCRYPT_MODE, AESType.CBC, "PKCS5Padding"); 23 | } 24 | 25 | public static byte[] decryptByCBC(byte[] ciphertext, byte[] key, byte[] iv) { 26 | return doAES(ciphertext, key, iv, Cipher.DECRYPT_MODE, AESType.CBC, "PKCS5Padding"); 27 | } 28 | 29 | public static byte[] encryptByCTRNoPadding(byte[] data, byte[] key, byte[] iv) { 30 | return doAES(data, key, iv, Cipher.ENCRYPT_MODE, AESType.CTR, "NoPadding"); 31 | } 32 | 33 | public static byte[] decryptByCTRNoPadding(byte[] ciphertext, byte[] key, byte[] iv) { 34 | return doAES(ciphertext, key, iv, Cipher.DECRYPT_MODE, AESType.CTR, "NoPadding"); 35 | } 36 | 37 | public static byte[] encryptByCBCNoPadding(byte[] data, byte[] key, byte[] iv) { 38 | return doAES(data, key, iv, Cipher.ENCRYPT_MODE, AESType.CBC, "NoPadding"); 39 | } 40 | 41 | public static byte[] decryptByCBCNoPadding(byte[] ciphertext, byte[] key, byte[] iv) { 42 | return doAES(ciphertext, key, iv, Cipher.DECRYPT_MODE, AESType.CBC, "NoPadding"); 43 | } 44 | 45 | private static byte[] doAES(byte[] data, byte[] key, byte[] iv, int cipherMode, AESType type, String paddingType) { 46 | String aesType; 47 | if (type == AESType.CBC) { 48 | aesType = "CBC"; 49 | } else { 50 | aesType = "CTR"; 51 | } 52 | try { 53 | IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); 54 | SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); 55 | 56 | String algorithmDesc = String.format("AES/%s/%s", aesType, paddingType); 57 | Cipher cipher = Cipher.getInstance(algorithmDesc); 58 | cipher.init(cipherMode, secretKeySpec, ivParameterSpec); 59 | return cipher.doFinal(data); 60 | } catch (Exception ignored) { 61 | } 62 | return new byte[0]; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/CipherParams.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.google.common.base.Strings; 5 | import com.ldd.wallet.model.Messages; 6 | import com.ldd.wallet.model.TokenException; 7 | 8 | /** 9 | * Created by xyz on 2018/2/3. 10 | */ 11 | public class CipherParams { 12 | private String iv; 13 | 14 | CipherParams() { 15 | } 16 | 17 | public String getIv() { 18 | return iv; 19 | } 20 | 21 | public void setIv(String iv) { 22 | this.iv = iv; 23 | } 24 | 25 | @JsonIgnore 26 | public void validate() { 27 | if (Strings.isNullOrEmpty(iv)) { 28 | throw new TokenException(Messages.CIPHER_FAIL); 29 | } 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) { 35 | return true; 36 | } 37 | 38 | if (!(o instanceof CipherParams)) { 39 | return false; 40 | } 41 | 42 | CipherParams that = (CipherParams) o; 43 | 44 | return getIv() != null 45 | ? getIv().equals(that.getIv()) : that.getIv() == null; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return getIv() != null ? getIv().hashCode() : 0; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/Crypto.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonSubTypes; 6 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 7 | import com.google.common.base.Strings; 8 | import java.util.Arrays; 9 | import com.ldd.foundation.utils.CachedDerivedKey; 10 | import com.ldd.foundation.utils.NumericUtil; 11 | import com.ldd.wallet.model.Messages; 12 | import com.ldd.wallet.model.TokenException; 13 | 14 | @JsonTypeInfo( 15 | use = JsonTypeInfo.Id.NAME, 16 | include = JsonTypeInfo.As.EXISTING_PROPERTY, 17 | property = "kdf") 18 | @JsonSubTypes({ 19 | @JsonSubTypes.Type(value = SCryptCrypto.class, name = "scrypt"), 20 | @JsonSubTypes.Type(value = PBKDF2Crypto.class, name = "pbkdf2") 21 | }) 22 | public class Crypto { 23 | static final String CTR = "aes-128-ctr"; 24 | static final String CBC = "aes-128-cbc"; 25 | 26 | static final int IV_LENGTH = 16; 27 | static final int SALT_LENGTH = 32; 28 | 29 | private String ciphertext; 30 | private String mac; 31 | String cipher; 32 | private CipherParams cipherparams; 33 | 34 | /** 35 | * !!! This function is used for testcase, and do not call this in other places; 36 | * 37 | * @return 38 | */ 39 | CachedDerivedKey getCachedDerivedKey() { 40 | return cachedDerivedKey; 41 | } 42 | 43 | @JsonIgnore 44 | private CachedDerivedKey cachedDerivedKey; 45 | 46 | private void setCachedDerivedKey(CachedDerivedKey cachedDerivedKey) { 47 | this.cachedDerivedKey = cachedDerivedKey; 48 | } 49 | 50 | String kdf; 51 | T kdfparams; 52 | 53 | public static Crypto createPBKDF2Crypto(String password, byte[] origin) { 54 | return createCrypto(password, origin, PBKDF2Crypto.PBKDF2, false); 55 | } 56 | 57 | 58 | public static Crypto createPBKDF2CryptoWithKDFCached(String password, byte[] origin) { 59 | return createCrypto(password, origin, PBKDF2Crypto.PBKDF2, true); 60 | } 61 | 62 | 63 | public static Crypto createSCryptCrypto(String password, byte[] origin) { 64 | return createCrypto(password, origin, SCryptCrypto.SCRYPT, false); 65 | } 66 | 67 | 68 | private static Crypto createCrypto(String password, byte[] origin, String kdfType, boolean isCached) { 69 | Crypto crypto = PBKDF2Crypto.PBKDF2.equals(kdfType) ? PBKDF2Crypto.createPBKDF2Crypto() : SCryptCrypto.createSCryptCrypto(); 70 | 71 | crypto.setCipher(CTR); 72 | byte[] iv = NumericUtil.generateRandomBytes(IV_LENGTH); 73 | CipherParams cipherparams = new CipherParams(); 74 | cipherparams.setIv(NumericUtil.bytesToHex(iv)); 75 | crypto.setCipherparams(cipherparams); 76 | 77 | byte[] derivedKey = crypto.getValidDerivedKey(password); 78 | 79 | if (isCached) { 80 | crypto.setCachedDerivedKey(new CachedDerivedKey(password, derivedKey)); 81 | } 82 | 83 | byte[] encrypted = crypto.encrypt(derivedKey, iv, origin); 84 | 85 | crypto.ciphertext = NumericUtil.bytesToHex(encrypted); 86 | 87 | byte[] mac = Hash.generateMac(derivedKey, encrypted); 88 | crypto.mac = NumericUtil.bytesToHex(mac); 89 | 90 | return crypto; 91 | } 92 | 93 | Crypto() { 94 | } 95 | 96 | public boolean verifyPassword(String password) { 97 | try { 98 | getCachedDerivedKey(password); 99 | return true; 100 | } catch (Exception ignored) { 101 | return false; 102 | } 103 | } 104 | 105 | public void cacheDerivedKey(String password) { 106 | byte[] derivedKey = getValidDerivedKey(password); 107 | this.cachedDerivedKey = new CachedDerivedKey(password, derivedKey); 108 | } 109 | 110 | private byte[] getCachedDerivedKey(String password) { 111 | if (cachedDerivedKey != null) { 112 | byte[] derivedKey = cachedDerivedKey.getDerivedKey(password); 113 | if (derivedKey != null) { 114 | return derivedKey; 115 | } 116 | } 117 | 118 | return getValidDerivedKey(password); 119 | } 120 | 121 | public void clearCachedDerivedKey() { 122 | this.cachedDerivedKey = null; 123 | } 124 | 125 | private byte[] getValidDerivedKey(String password) { 126 | byte[] derivedKey = generateDerivedKey(password.getBytes()); 127 | if (this.mac == null) { 128 | return derivedKey; 129 | } 130 | byte[] mac = NumericUtil.hexToBytes(this.mac); 131 | byte[] cipherText = NumericUtil.hexToBytes(getCiphertext()); 132 | 133 | byte[] derivedMac = Hash.generateMac(derivedKey, cipherText); 134 | if (Arrays.equals(derivedMac, mac)) { 135 | return derivedKey; 136 | } else { 137 | throw new TokenException(Messages.WALLET_INVALID_PASSWORD); 138 | } 139 | } 140 | 141 | public void validate() { 142 | if ((!CTR.equals(cipher) && !CBC.equals(cipher)) || cipherparams == null 143 | || Strings.isNullOrEmpty(mac) || Strings.isNullOrEmpty(ciphertext) 144 | || kdfparams == null) { 145 | throw new TokenException(Messages.WALLET_INVALID); 146 | } 147 | 148 | cipherparams.validate(); 149 | kdfparams.validate(); 150 | 151 | } 152 | 153 | byte[] generateDerivedKey(byte[] password) { 154 | throw new UnsupportedOperationException("You invoke the not implement method"); 155 | } 156 | 157 | private byte[] encrypt(byte[] derivedKey, byte[] iv, byte[] text) { 158 | 159 | byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); 160 | 161 | if (CTR.equals(cipher)) { 162 | return AES.encryptByCTRNoPadding(text, encryptKey, iv); 163 | } else { 164 | return AES.encryptByCBCNoPadding(text, encryptKey, iv); 165 | } 166 | } 167 | 168 | public byte[] decrypt(String password) { 169 | byte[] derivedKey = getCachedDerivedKey(password); 170 | byte[] iv = NumericUtil.hexToBytes(this.getCipherparams().getIv()); 171 | byte[] encrypted = NumericUtil.hexToBytes(this.getCiphertext()); 172 | 173 | return decrypt(derivedKey, iv, encrypted); 174 | } 175 | 176 | private byte[] decrypt(byte[] derivedKey, byte[] iv, byte[] text) { 177 | byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); 178 | 179 | if (CTR.equals(cipher)) { 180 | return AES.decryptByCTRNoPadding(text, encryptKey, iv); 181 | } else { 182 | return AES.decryptByCBCNoPadding(text, encryptKey, iv); 183 | } 184 | } 185 | 186 | public EncPair deriveEncPair(String password, byte[] origin) { 187 | byte[] derivedKey = getCachedDerivedKey(password); 188 | EncPair encPair = new EncPair(); 189 | byte[] iv = NumericUtil.generateRandomBytes(16); 190 | byte[] encrypted = this.encrypt(derivedKey, iv, origin); 191 | 192 | encPair.setEncStr(NumericUtil.bytesToHex(encrypted)); 193 | encPair.setNonce(NumericUtil.bytesToHex(iv)); 194 | 195 | return encPair; 196 | } 197 | 198 | public byte[] decryptEncPair(String password, EncPair encPair) { 199 | byte[] derivedKey = getCachedDerivedKey(password); 200 | byte[] iv = NumericUtil.hexToBytes(encPair.getNonce()); 201 | return decrypt(derivedKey, iv, NumericUtil.hexToBytes(encPair.getEncStr())); 202 | } 203 | 204 | @JsonProperty(required = true) 205 | public String getCiphertext() { 206 | return ciphertext; 207 | } 208 | 209 | @JsonProperty(required = true) 210 | public CipherParams getCipherparams() { 211 | return cipherparams; 212 | } 213 | 214 | @JsonProperty(required = true) 215 | public String getMac() { 216 | return mac; 217 | } 218 | 219 | @JsonProperty(required = true) 220 | public String getCipher() { 221 | return cipher; 222 | } 223 | 224 | @JsonProperty(required = true) 225 | public String getKdf() { 226 | return kdf; 227 | } 228 | 229 | @JsonProperty(required = true) 230 | public T getKdfparams() { 231 | return kdfparams; 232 | } 233 | 234 | public void setCiphertext(String ciphertext) { 235 | this.ciphertext = ciphertext; 236 | } 237 | 238 | public void setMac(String mac) { 239 | this.mac = mac; 240 | } 241 | 242 | public void setCipher(String cipher) { 243 | this.cipher = cipher; 244 | } 245 | 246 | public void setCipherparams(CipherParams cipherparams) { 247 | this.cipherparams = cipherparams; 248 | } 249 | 250 | public void setKdf(String kdf) { 251 | this.kdf = kdf; 252 | } 253 | 254 | public void setKdfparams(T kdfparams) { 255 | this.kdfparams = kdfparams; 256 | } 257 | 258 | @Override 259 | public boolean equals(Object o) { 260 | if (this == o) { 261 | return true; 262 | } 263 | if (!(o instanceof Crypto)) { 264 | return false; 265 | } 266 | 267 | Crypto that = (Crypto) o; 268 | 269 | if (cipher != null 270 | ? !cipher.equals(that.cipher) 271 | : that.cipher != null) { 272 | return false; 273 | } 274 | if (getCiphertext() != null 275 | ? !getCiphertext().equals(that.getCiphertext()) 276 | : that.getCiphertext() != null) { 277 | return false; 278 | } 279 | if (getCipherparams() != null 280 | ? !getCipherparams().equals(that.getCipherparams()) 281 | : that.getCipherparams() != null) { 282 | return false; 283 | } 284 | if (kdf != null 285 | ? !kdf.equals(that.kdf) 286 | : that.kdf != null) { 287 | return false; 288 | } 289 | if (this.kdfparams != null 290 | ? !this.kdfparams.equals(that.kdfparams) 291 | : that.kdfparams != null) { 292 | return false; 293 | } 294 | return mac != null 295 | ? mac.equals(that.mac) : that.mac == null; 296 | } 297 | 298 | @Override 299 | public int hashCode() { 300 | int result = cipher != null ? cipher.hashCode() : 0; 301 | result = 31 * result + (getCiphertext() != null ? getCiphertext().hashCode() : 0); 302 | result = 31 * result + (getCipherparams() != null ? getCipherparams().hashCode() : 0); 303 | result = 31 * result + (kdf != null ? kdf.hashCode() : 0); 304 | result = 31 * result + (this.kdfparams != null ? this.kdfparams.hashCode() : 0); 305 | result = 31 * result + (mac != null ? mac.hashCode() : 0); 306 | return result; 307 | } 308 | 309 | } 310 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/EncPair.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | public class EncPair { 4 | private String encStr; 5 | private String nonce; 6 | 7 | public String getNonce() { 8 | return nonce; 9 | } 10 | 11 | public void setNonce(String nonce) { 12 | this.nonce = nonce; 13 | } 14 | 15 | public String getEncStr() { 16 | return encStr; 17 | } 18 | 19 | public void setEncStr(String encStr) { 20 | this.encStr = encStr; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/Hash.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import java.security.InvalidKeyException; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import javax.crypto.Mac; 10 | import javax.crypto.spec.SecretKeySpec; 11 | import org.bitcoinj.core.Sha256Hash; 12 | import com.ldd.foundation.utils.ByteUtil; 13 | import com.ldd.foundation.utils.NumericUtil; 14 | import com.ldd.wallet.model.Messages; 15 | import com.ldd.wallet.model.TokenException; 16 | 17 | public class Hash { 18 | 19 | public static String keccak256(String hex) { 20 | byte[] bytes = NumericUtil.hexToBytes(hex); 21 | byte[] result = keccak256(bytes); 22 | return NumericUtil.bytesToHex(result); 23 | } 24 | 25 | public static byte[] keccak256(byte[] input) { 26 | return keccak256(input, 0, input.length); 27 | } 28 | 29 | public static byte[] generateMac(byte[] derivedKey, byte[] cipherText) { 30 | byte[] result = new byte[16 + cipherText.length]; 31 | 32 | System.arraycopy(derivedKey, 16, result, 0, 16); 33 | System.arraycopy(cipherText, 0, result, 16, cipherText.length); 34 | 35 | return Hash.keccak256(result); 36 | } 37 | 38 | public static String sha256(String hexInput) { 39 | byte[] bytes = NumericUtil.hexToBytes(hexInput); 40 | byte[] result = sha256(bytes); 41 | return NumericUtil.bytesToHex(result); 42 | } 43 | 44 | public static byte[] sha256(byte[] input) { 45 | return sha256(input, 0, input.length); 46 | } 47 | 48 | private static byte[] keccak256(byte[] input, int offset, int length) { 49 | 50 | Keccak keccak = new Keccak(256); 51 | keccak.update(input, offset, length); 52 | 53 | return keccak.digest().array(); 54 | } 55 | 56 | private static byte[] sha256(byte[] input, int offset, int length) { 57 | try { 58 | MessageDigest md = MessageDigest.getInstance("SHA-256"); 59 | md.update(input, offset, length); 60 | return md.digest(); 61 | } catch (Exception ex) { 62 | throw new TokenException(Messages.WALLET_SHA256); 63 | } 64 | } 65 | 66 | public static byte[] hmacSHA256(byte[] key, byte[] data) { 67 | try { 68 | Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); 69 | SecretKeySpec secret_key = new SecretKeySpec(key, "HmacSHA256"); 70 | sha256_HMAC.init(secret_key); 71 | 72 | return sha256_HMAC.doFinal(data); 73 | } catch (NoSuchAlgorithmException | InvalidKeyException ex) { 74 | throw new TokenException(Messages.WALLET_SHA256); 75 | } 76 | } 77 | 78 | public static byte[] merkleHash(byte[] oriData) { 79 | 80 | if (oriData == null || oriData.length == 0) { 81 | throw new IllegalArgumentException("data should not be null"); 82 | } 83 | int chunkSize = 1024; 84 | List hashes = new ArrayList<>(); 85 | for (int pos = 0; pos < oriData.length; pos += chunkSize) { 86 | int end = Math.min(pos + chunkSize, oriData.length); 87 | hashes.add(Sha256Hash.hashTwice(Arrays.copyOfRange(oriData, pos, end))); 88 | } 89 | 90 | int j = 0; 91 | for (int size = hashes.size(); size > 1; size = (size + 1) / 2) { 92 | for (int i = 0; i < size; i += 2) { 93 | int i2 = Math.min(i + 1, size - 1); 94 | hashes.add(Sha256Hash.hashTwice(ByteUtil.concat(hashes.get(j + i), hashes.get(j + i2)))); 95 | } 96 | j += size; 97 | } 98 | 99 | return hashes.get(hashes.size() - 1); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/KDFParams.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | 5 | /** 6 | * Created by xyz on 2018/2/2. 7 | */ 8 | 9 | 10 | interface KDFParams { 11 | int DK_LEN = 32; 12 | 13 | int getDklen(); 14 | 15 | String getSalt(); 16 | 17 | @JsonIgnore 18 | void validate(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/Keccak.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.nio.ByteOrder; 5 | 6 | public class Keccak { 7 | private static final int MAX_STATE_SIZE = 1600; 8 | private static final int MAX_STATE_SIZE_WORDS = MAX_STATE_SIZE / 64; 9 | 10 | private int rateSizeBits, digestSizeBits; 11 | private long[] state = new long[MAX_STATE_SIZE_WORDS]; 12 | private int rateBits; 13 | private boolean padded; 14 | 15 | Keccak(int digestSizeBits) { 16 | reset(digestSizeBits); 17 | } 18 | 19 | public Keccak(Keccak other) { 20 | System.arraycopy(other.state, 0, state, 0, other.state.length); 21 | rateBits = other.rateBits; 22 | rateSizeBits = other.rateSizeBits; 23 | digestSizeBits = other.digestSizeBits; 24 | padded = other.padded; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "Keccak-" + digestSizeBits; 30 | } 31 | 32 | public int rateSize() { 33 | return rateSizeBits >>> 3; 34 | } 35 | 36 | private int digestSize() { 37 | return digestSizeBits >>> 3; 38 | } 39 | 40 | public void reset() { 41 | reset(rateSizeBits, digestSizeBits); 42 | } 43 | 44 | private int rateSizeBitsFor(int digestSizeBits) { 45 | //@formatter:off 46 | switch (digestSizeBits) { 47 | case 288: 48 | return 1024; 49 | case 128: 50 | return 1344; 51 | case 224: 52 | return 1152; 53 | case 256: 54 | return 1088; 55 | case 384: 56 | return 832; 57 | case 512: 58 | return 576; 59 | default: 60 | throw new IllegalArgumentException("Invalid digestSizeBits: " + digestSizeBits + " { 128, 224, 256, 288, 384, 512 }"); 61 | } 62 | //@formatter:on 63 | } 64 | 65 | private void reset(int digestSizeBits) { 66 | reset(rateSizeBitsFor(digestSizeBits), digestSizeBits); 67 | } 68 | 69 | private void reset(int rateSizebits, int digestSizeBits) { 70 | if (rateSizebits + digestSizeBits * 2 != MAX_STATE_SIZE) 71 | throw new IllegalArgumentException("Invalid rateSizebits + digestSizeBits * 2: " + rateSizebits + " + " + digestSizeBits + " * 2 != " + MAX_STATE_SIZE); 72 | if (rateSizebits <= 0 || (rateSizebits & 0x3f) > 0) 73 | throw new IllegalArgumentException("Invalid rateSizebits: " + rateSizebits); 74 | 75 | for (int i = 0; i < MAX_STATE_SIZE_WORDS; ++i) 76 | state[i] = 0; 77 | rateBits = 0; 78 | 79 | rateSizeBits = rateSizebits; 80 | this.digestSizeBits = digestSizeBits; 81 | padded = false; 82 | } 83 | 84 | public void update(byte in) { 85 | updateBits(in & 0xff, 8); 86 | } 87 | 88 | public void update(byte[] in) { 89 | update(ByteBuffer.wrap(in)); 90 | } 91 | 92 | void update(byte[] in, int offset, int length) { 93 | update(ByteBuffer.wrap(in, offset, length)); 94 | } 95 | 96 | private void update(ByteBuffer in) { 97 | int inBytes = in.remaining(); 98 | if (inBytes <= 0) 99 | return; 100 | 101 | if (padded) 102 | throw new IllegalStateException("Cannot update while padded"); 103 | 104 | int rateBits = this.rateBits; 105 | if ((rateBits & 0x7) > 0) //this could be implemented but would introduce considerable performance degradation - also, it's never technically possible. 106 | throw new IllegalStateException("Cannot update while in bit-mode"); 107 | 108 | long[] state = this.state; 109 | int rateBytes = rateBits >>> 3; 110 | 111 | int rateBytesWord = rateBytes & 0x7; 112 | if (rateBytesWord > 0) { 113 | //logically must have space at this point 114 | int c = 8 - rateBytesWord; 115 | if (c > inBytes) 116 | c = inBytes; 117 | int i = rateBytes >>> 3; 118 | long w = state[i]; 119 | rateBytes += c; 120 | inBytes -= c; 121 | rateBytesWord <<= 3; 122 | c = rateBytesWord + (c << 3); 123 | do { 124 | w ^= (long) (in.get() & 0xff) << rateBytesWord; 125 | rateBytesWord += 8; 126 | } while (rateBytesWord < c); 127 | state[i] = w; 128 | 129 | if (inBytes > 0) { 130 | this.rateBits = rateBytes << 3; 131 | return; 132 | } 133 | } 134 | 135 | int rateWords = rateBytes >>> 3; 136 | int rateSizeWords = rateSizeBits >>> 6; 137 | 138 | int inWords = inBytes >>> 3; 139 | if (inWords > 0) { 140 | ByteOrder order = in.order(); 141 | try { 142 | in.order(ByteOrder.LITTLE_ENDIAN); 143 | do { 144 | if (rateWords >= rateSizeWords) { 145 | Keccak.keccak(state); 146 | rateWords = 0; 147 | } 148 | int c = rateSizeWords - rateWords; 149 | if (c > inWords) 150 | c = inWords; 151 | inWords -= c; 152 | c += rateWords; 153 | do { 154 | state[rateWords] ^= in.getLong(); 155 | rateWords++; 156 | } while (rateWords < c); 157 | } while (inWords > 0); 158 | } finally { 159 | in.order(order); 160 | } 161 | inBytes &= 0x7; 162 | if (inBytes <= 0) { 163 | this.rateBits = rateWords << 6; 164 | return; 165 | } 166 | } 167 | 168 | if (rateWords >= rateSizeWords) { 169 | Keccak.keccak(state); 170 | rateWords = 0; 171 | } 172 | long w = state[rateWords]; 173 | inBytes <<= 3; 174 | int i = 0; 175 | do { 176 | w ^= (long) (in.get() & 0xff) << i; 177 | i += 8; 178 | } while (i < inBytes); 179 | state[rateWords] = w; 180 | 181 | this.rateBits = (rateWords << 6) | inBytes; 182 | } 183 | 184 | private void updateBits(long in, int inBits) { 185 | if (inBits < 0 || inBits > 64) 186 | throw new IllegalArgumentException("Invalid valueBits: " + 0 + " < " + inBits + " > " + 64); 187 | 188 | if (inBits <= 0) 189 | return; 190 | 191 | if (padded) 192 | throw new IllegalStateException("Cannot update while padded"); 193 | 194 | long[] state = this.state; 195 | int rateBits = this.rateBits; 196 | int rateBitsWord = rateBits & 0x3f; 197 | if (rateBitsWord > 0) { 198 | //logically must have space at this point 199 | int c = 64 - rateBitsWord; 200 | if (c > inBits) 201 | c = inBits; 202 | state[rateBits >>> 6] ^= (in & (-1L >>> c)) << rateBitsWord; 203 | rateBits += c; 204 | inBits -= c; 205 | if (inBits <= 0) { 206 | this.rateBits = rateBits; 207 | return; 208 | } 209 | in >>>= c; 210 | } 211 | if (rateBits >= rateSizeBits) { 212 | Keccak.keccak(state); 213 | rateBits = 0; 214 | } 215 | state[rateBits >>> 6] ^= in & (-1L >>> inBits); 216 | this.rateBits = rateBits + inBits; 217 | } 218 | 219 | ByteBuffer digest() { 220 | return digest(digestSize()); 221 | } 222 | 223 | private ByteBuffer digest(int outSize) { 224 | return digest(outSize, false); 225 | } 226 | 227 | private ByteBuffer digest(int outSize, boolean direct) { 228 | ByteBuffer buffer = direct ? ByteBuffer.allocateDirect(outSize) : ByteBuffer.allocate(outSize); 229 | digest(buffer); 230 | buffer.flip(); 231 | return buffer; 232 | } 233 | 234 | public byte[] digestArray() { 235 | return digestArray(digestSize()); 236 | } 237 | 238 | private byte[] digestArray(int outSize) { 239 | byte[] array = new byte[outSize]; 240 | digest(array, 0, outSize); 241 | return array; 242 | } 243 | 244 | public void digest(byte[] out) { 245 | digest(ByteBuffer.wrap(out)); 246 | } 247 | 248 | private void digest(byte[] out, int offset, int length) { 249 | digest(ByteBuffer.wrap(out, offset, length)); 250 | } 251 | 252 | private void digest(ByteBuffer out) { 253 | int outBytes = out.remaining(); 254 | if (outBytes <= 0) 255 | return; 256 | 257 | long[] state = this.state; 258 | int rateBits = this.rateBits; 259 | int rateBytes; 260 | if (!padded) { 261 | pad(); 262 | padded = true; 263 | rateBits = 0; 264 | rateBytes = 0; 265 | } else { 266 | if ((rateBits & 0x7) > 0) 267 | throw new IllegalStateException("Cannot digest while in bit-mode"); //this could be implemented but would introduce considerable performance degradation - also, it's never technically possible. 268 | 269 | rateBytes = rateBits >>> 3; 270 | int rateBytesWord = rateBytes & 0x7; 271 | if (rateBytesWord > 0) { 272 | int c = 8 - rateBytesWord; 273 | if (c > outBytes) 274 | c = outBytes; 275 | long w = state[rateBytes >>> 3]; 276 | outBytes -= c; 277 | rateBytes += c; 278 | rateBytesWord <<= 3; 279 | c = (c << 3) + rateBytesWord; 280 | do { 281 | out.put((byte) (w >>> rateBytesWord)); 282 | rateBytesWord += 8; 283 | } while (rateBytesWord < c); 284 | if (outBytes <= 0) { 285 | this.rateBits = rateBytes << 3; 286 | return; 287 | } 288 | } 289 | } 290 | 291 | int rateSizeWords = rateSizeBits >>> 6; 292 | int rateWords = rateBytes >>> 3; 293 | 294 | int outWords = outBytes >>> 3; 295 | if (outWords > 0) { 296 | ByteOrder order = out.order(); 297 | try { 298 | out.order(ByteOrder.LITTLE_ENDIAN); 299 | do { 300 | if (rateWords >= rateSizeWords) { 301 | squeeze(); 302 | rateWords = 0; 303 | } 304 | int c = rateSizeWords - rateWords; 305 | if (c > outWords) 306 | c = outWords; 307 | outWords -= c; 308 | c += rateWords; 309 | do { 310 | out.putLong(state[rateWords]); 311 | rateWords++; 312 | } while (rateWords < c); 313 | } while (outWords > 0); 314 | } finally { 315 | out.order(order); 316 | } 317 | outBytes &= 0x7; 318 | if (outBytes <= 0) { 319 | this.rateBits = rateWords << 6; 320 | return; 321 | } 322 | } 323 | 324 | if (rateWords >= rateSizeWords) { 325 | squeeze(); 326 | rateWords = 0; 327 | } 328 | long w = state[rateWords]; 329 | outBytes <<= 3; 330 | int i = 0; 331 | do { 332 | out.put((byte) (w >>> i)); 333 | i += 8; 334 | } while (i < outBytes); 335 | this.rateBits = (rateWords << 6) | outBytes; 336 | } 337 | 338 | private void squeeze() { 339 | Keccak.keccak(state); 340 | } 341 | 342 | private void pad() { 343 | updateBits(0x1, 1); 344 | if (rateBits >= rateSizeBits) { 345 | Keccak.keccak(state); 346 | rateBits = 0; 347 | } 348 | rateBits = rateSizeBits - 1; 349 | updateBits(0x1, 1); 350 | Keccak.keccak(state); 351 | } 352 | 353 | private static void keccak(long[] a) { 354 | //@formatter:off 355 | int c, i; 356 | long x, a_10_; 357 | long x0, x1, x2, x3, x4; 358 | long t0, t1, t2, t3, t4; 359 | long c0, c1, c2, c3, c4; 360 | 361 | i = 0; 362 | do { 363 | //theta (precalculation part) 364 | c0 = a[0] ^ a[5 + 0] ^ a[10 + 0] ^ a[15 + 0] ^ a[20 + 0]; 365 | c1 = a[1] ^ a[5 + 1] ^ a[10 + 1] ^ a[15 + 1] ^ a[20 + 1]; 366 | c2 = a[2] ^ a[5 + 2] ^ a[10 + 2] ^ a[15 + 2] ^ a[20 + 2]; 367 | c3 = a[3] ^ a[5 + 3] ^ a[10 + 3] ^ a[15 + 3] ^ a[20 + 3]; 368 | c4 = a[4] ^ a[5 + 4] ^ a[10 + 4] ^ a[15 + 4] ^ a[20 + 4]; 369 | 370 | t0 = (c0 << 1) ^ (c0 >>> (64 - 1)) ^ c3; 371 | t1 = (c1 << 1) ^ (c1 >>> (64 - 1)) ^ c4; 372 | t2 = (c2 << 1) ^ (c2 >>> (64 - 1)) ^ c0; 373 | t3 = (c3 << 1) ^ (c3 >>> (64 - 1)) ^ c1; 374 | t4 = (c4 << 1) ^ (c4 >>> (64 - 1)) ^ c2; 375 | 376 | //theta (xorring part) + rho + pi 377 | a[0] ^= t1; 378 | x = a[1] ^ t2; 379 | a_10_ = (x << 1) | (x >>> (64 - 1)); 380 | x = a[6] ^ t2; 381 | a[1] = (x << 44) | (x >>> (64 - 44)); 382 | x = a[9] ^ t0; 383 | a[6] = (x << 20) | (x >>> (64 - 20)); 384 | x = a[22] ^ t3; 385 | a[9] = (x << 61) | (x >>> (64 - 61)); 386 | 387 | x = a[14] ^ t0; 388 | a[22] = (x << 39) | (x >>> (64 - 39)); 389 | x = a[20] ^ t1; 390 | a[14] = (x << 18) | (x >>> (64 - 18)); 391 | x = a[2] ^ t3; 392 | a[20] = (x << 62) | (x >>> (64 - 62)); 393 | x = a[12] ^ t3; 394 | a[2] = (x << 43) | (x >>> (64 - 43)); 395 | x = a[13] ^ t4; 396 | a[12] = (x << 25) | (x >>> (64 - 25)); 397 | 398 | x = a[19] ^ t0; 399 | a[13] = (x << 8) | (x >>> (64 - 8)); 400 | x = a[23] ^ t4; 401 | a[19] = (x << 56) | (x >>> (64 - 56)); 402 | x = a[15] ^ t1; 403 | a[23] = (x << 41) | (x >>> (64 - 41)); 404 | x = a[4] ^ t0; 405 | a[15] = (x << 27) | (x >>> (64 - 27)); 406 | x = a[24] ^ t0; 407 | a[4] = (x << 14) | (x >>> (64 - 14)); 408 | 409 | x = a[21] ^ t2; 410 | a[24] = (x << 2) | (x >>> (64 - 2)); 411 | x = a[8] ^ t4; 412 | a[21] = (x << 55) | (x >>> (64 - 55)); 413 | x = a[16] ^ t2; 414 | a[8] = (x << 45) | (x >>> (64 - 45)); 415 | x = a[5] ^ t1; 416 | a[16] = (x << 36) | (x >>> (64 - 36)); 417 | x = a[3] ^ t4; 418 | a[5] = (x << 28) | (x >>> (64 - 28)); 419 | 420 | x = a[18] ^ t4; 421 | a[3] = (x << 21) | (x >>> (64 - 21)); 422 | x = a[17] ^ t3; 423 | a[18] = (x << 15) | (x >>> (64 - 15)); 424 | x = a[11] ^ t2; 425 | a[17] = (x << 10) | (x >>> (64 - 10)); 426 | x = a[7] ^ t3; 427 | a[11] = (x << 6) | (x >>> (64 - 6)); 428 | x = a[10] ^ t1; 429 | a[7] = (x << 3) | (x >>> (64 - 3)); 430 | a[10] = a_10_; 431 | 432 | //chi 433 | c = 0; 434 | do { 435 | x0 = a[c + 0]; 436 | x1 = a[c + 1]; 437 | x2 = a[c + 2]; 438 | x3 = a[c + 3]; 439 | x4 = a[c + 4]; 440 | a[c + 0] = x0 ^ ((~x1) & x2); 441 | a[c + 1] = x1 ^ ((~x2) & x3); 442 | a[c + 2] = x2 ^ ((~x3) & x4); 443 | a[c + 3] = x3 ^ ((~x4) & x0); 444 | a[c + 4] = x4 ^ ((~x0) & x1); 445 | 446 | c += 5; 447 | } while (c < 25); 448 | 449 | //iota 450 | a[0] ^= RC[i]; 451 | 452 | i++; 453 | } while (i < 24); 454 | //@formatter:on 455 | } 456 | 457 | private static final long[] RC = {0x0000000000000001L, 0x0000000000008082L, 0x800000000000808AL, 0x8000000080008000L, 0x000000000000808BL, 0x0000000080000001L, 0x8000000080008081L, 458 | 0x8000000000008009L, 0x000000000000008AL, 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000AL, 0x000000008000808BL, 0x800000000000008BL, 459 | 0x8000000000008089L, 0x8000000000008003L, 0x8000000000008002L, 0x8000000000000080L, 0x000000000000800AL, 0x800000008000000AL, 0x8000000080008081L, 460 | 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L}; 461 | } 462 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/Multihash.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.DataInput; 5 | import java.io.DataOutput; 6 | import java.io.IOException; 7 | import java.util.Arrays; 8 | import java.util.Map; 9 | import java.util.TreeMap; 10 | import org.bitcoinj.core.Base58; 11 | 12 | public class Multihash { 13 | public enum Type { 14 | md5(0xd5, 16), 15 | sha1(0x11, 20), 16 | sha2_256(0x12, 32), 17 | sha2_512(0x13, 64), 18 | sha3_512(0x14, 64), 19 | blake2b(0x40, 64), 20 | blake2s(0x41, 32); 21 | 22 | public int index, length; 23 | 24 | Type(int index, int length) { 25 | this.index = index; 26 | this.length = length; 27 | } 28 | 29 | private static Map lookup = new TreeMap<>(); 30 | static { 31 | for (Type t: Type.values()) 32 | lookup.put(t.index, t); 33 | } 34 | 35 | public static Type lookup(int t) { 36 | if (!lookup.containsKey(t)) 37 | throw new IllegalStateException("Unknown Multihash type: "+t); 38 | return lookup.get(t); 39 | } 40 | } 41 | 42 | public final Type type; 43 | private final byte[] hash; 44 | 45 | public Multihash(Type type, byte[] hash) { 46 | if (hash.length > 127) 47 | throw new IllegalStateException("Unsupported hash size: "+hash.length); 48 | if (hash.length != type.length) 49 | throw new IllegalStateException("Incorrect hash length: " + hash.length + " != "+type.length); 50 | this.type = type; 51 | this.hash = hash; 52 | } 53 | 54 | public Multihash(Multihash toClone) { 55 | this(toClone.type, toClone.hash); // N.B. despite being a byte[], hash is immutable 56 | } 57 | 58 | private Multihash(byte[] multihash) { 59 | this(Type.lookup(multihash[0] & 0xff), Arrays.copyOfRange(multihash, 2, multihash.length)); 60 | } 61 | 62 | private byte[] toBytes() { 63 | byte[] res = new byte[hash.length+2]; 64 | res[0] = (byte)type.index; 65 | res[1] = (byte)hash.length; 66 | System.arraycopy(hash, 0, res, 2, hash.length); 67 | return res; 68 | } 69 | 70 | public void serialize(DataOutput dout) throws IOException { 71 | dout.write(toBytes()); 72 | } 73 | 74 | public static Multihash deserialize(DataInput din) throws IOException { 75 | int type = din.readUnsignedByte(); 76 | int len = din.readUnsignedByte(); 77 | Type t = Type.lookup(type); 78 | byte[] hash = new byte[len]; 79 | din.readFully(hash); 80 | return new Multihash(t, hash); 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return toBase58(); 86 | } 87 | 88 | @Override 89 | public boolean equals(Object o) { 90 | if (!(o instanceof Multihash)) 91 | return false; 92 | return type == ((Multihash) o).type && Arrays.equals(hash, ((Multihash) o).hash); 93 | } 94 | 95 | @Override 96 | public int hashCode() { 97 | return Arrays.hashCode(hash) ^ type.hashCode(); 98 | } 99 | 100 | private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); 101 | 102 | public String toHex() { 103 | byte[] bytes = toBytes(); 104 | char[] hexChars = new char[bytes.length * 2]; 105 | for ( int j = 0; j < bytes.length; j++ ) { 106 | int v = bytes[j] & 0xFF; 107 | hexChars[j * 2] = hexArray[v >>> 4]; 108 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 109 | } 110 | return new String(hexChars); 111 | } 112 | 113 | public String toBase58() { 114 | return Base58.encode(toBytes()); 115 | } 116 | 117 | public static Multihash fromHex(String hex) { 118 | if (hex.length() % 2 != 0) 119 | throw new IllegalStateException("Uneven number of hex digits!"); 120 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 121 | for (int i=0; i < hex.length()-1; i+= 2) 122 | bout.write(Integer.valueOf(hex.substring(i, i+2), 16)); 123 | return new Multihash(bout.toByteArray()); 124 | } 125 | 126 | public static Multihash fromBase58(String base58) { 127 | return new Multihash(Base58.decode(base58)); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/PBKDF2Crypto.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import com.ldd.foundation.utils.NumericUtil; 4 | import com.ldd.wallet.model.Messages; 5 | import com.ldd.wallet.model.TokenException; 6 | import org.spongycastle.crypto.digests.SHA256Digest; 7 | import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator; 8 | import org.spongycastle.crypto.params.KeyParameter; 9 | 10 | /** 11 | * Created by xyz on 2018/2/3. 12 | */ 13 | 14 | public final class PBKDF2Crypto extends Crypto { 15 | static final String PBKDF2 = "pbkdf2"; 16 | 17 | PBKDF2Crypto() { 18 | super(); 19 | this.kdf = PBKDF2; 20 | } 21 | 22 | public static PBKDF2Crypto createPBKDF2Crypto() { 23 | PBKDF2Crypto crypto = new PBKDF2Crypto(); 24 | byte[] salt = NumericUtil.generateRandomBytes(SALT_LENGTH); 25 | PBKDF2Params pbkdf2Params = PBKDF2Params.createPBKDF2Params(); 26 | pbkdf2Params.setSalt(NumericUtil.bytesToHex(salt)); 27 | crypto.kdfparams = pbkdf2Params; 28 | return crypto; 29 | } 30 | 31 | @Override 32 | public byte[] generateDerivedKey(byte[] password) { 33 | PBKDF2Params params = this.kdfparams; 34 | if (!PBKDF2Params.PRF.equals(params.getPrf())) { 35 | throw new TokenException(Messages.PRF_UNSUPPORTED); 36 | } 37 | 38 | PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest()); 39 | generator.init(password, NumericUtil.hexToBytes(params.getSalt()), params.getC()); 40 | return ((KeyParameter) generator.generateDerivedParameters(256)).getKey(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/PBKDF2Params.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import com.google.common.base.Strings; 4 | import com.ldd.wallet.model.Messages; 5 | import com.ldd.wallet.model.TokenException; 6 | 7 | /** 8 | * Created by xyz on 2018/2/2. 9 | */ 10 | public class PBKDF2Params implements KDFParams { 11 | static final String PRF = "hmac-sha256"; 12 | static final int C_LIGHT = 10240; 13 | 14 | private int dklen = 0; 15 | private int c = 0; 16 | private String prf = ""; 17 | private String salt; 18 | 19 | public PBKDF2Params() { 20 | } 21 | 22 | public static PBKDF2Params createPBKDF2Params() { 23 | PBKDF2Params params = new PBKDF2Params(); 24 | params.dklen = DK_LEN; 25 | params.c = C_LIGHT; 26 | params.prf = PRF; 27 | return params; 28 | } 29 | 30 | public int getDklen() { 31 | return dklen; 32 | } 33 | 34 | public void setDklen(int dklen) { 35 | this.dklen = dklen; 36 | } 37 | 38 | public int getC() { 39 | return c; 40 | } 41 | 42 | public void setC(int c) { 43 | this.c = c; 44 | } 45 | 46 | public String getPrf() { 47 | return prf; 48 | } 49 | 50 | public void setPrf(String prf) { 51 | this.prf = prf; 52 | } 53 | 54 | public String getSalt() { 55 | return salt; 56 | } 57 | 58 | @Override 59 | public void validate() { 60 | if (dklen == 0 || c == 0 || Strings.isNullOrEmpty(salt) || Strings.isNullOrEmpty(prf)) { 61 | throw new TokenException(Messages.KDF_PARAMS_INVALID); 62 | } 63 | } 64 | 65 | public void setSalt(String salt) { 66 | this.salt = salt; 67 | } 68 | 69 | @Override 70 | public boolean equals(Object o) { 71 | if (this == o) { 72 | return true; 73 | } 74 | if (!(o instanceof PBKDF2Params)) { 75 | return false; 76 | } 77 | 78 | PBKDF2Params that = (PBKDF2Params) o; 79 | 80 | if (dklen != that.dklen) { 81 | return false; 82 | } 83 | if (c != that.c) { 84 | return false; 85 | } 86 | if (getPrf() != null 87 | ? !getPrf().equals(that.getPrf()) 88 | : that.getPrf() != null) { 89 | return false; 90 | } 91 | return getSalt() != null 92 | ? getSalt().equals(that.getSalt()) : that.getSalt() == null; 93 | } 94 | 95 | @Override 96 | public int hashCode() { 97 | int result = dklen; 98 | result = 31 * result + c; 99 | result = 31 * result + (getPrf() != null ? getPrf().hashCode() : 0); 100 | result = 31 * result + (getSalt() != null ? getSalt().hashCode() : 0); 101 | return result; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/SCryptCrypto.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import com.lambdaworks.crypto.SCrypt; 4 | import java.security.GeneralSecurityException; 5 | import com.ldd.foundation.utils.NumericUtil; 6 | import com.ldd.wallet.model.Messages; 7 | import com.ldd.wallet.model.TokenException; 8 | 9 | /** 10 | * Created by xyz on 2018/2/3. 11 | */ 12 | 13 | public class SCryptCrypto extends Crypto { 14 | static final String SCRYPT = "scrypt"; 15 | 16 | 17 | public SCryptCrypto() { 18 | super(); 19 | this.kdf = SCRYPT; 20 | } 21 | 22 | public static SCryptCrypto createSCryptCrypto() { 23 | SCryptCrypto crypto = new SCryptCrypto(); 24 | byte[] salt = NumericUtil.generateRandomBytes(SALT_LENGTH); 25 | SCryptParams params = SCryptParams.createSCryptParams(); 26 | params.setSalt(NumericUtil.bytesToHex(salt)); 27 | crypto.kdfparams = params; 28 | return crypto; 29 | } 30 | 31 | @Override 32 | public byte[] generateDerivedKey(byte[] password) { 33 | int dkLen = this.kdfparams.getDklen(); 34 | int n = this.kdfparams.getN(); 35 | int p = this.kdfparams.getP(); 36 | int r = this.kdfparams.getR(); 37 | byte[] salt = NumericUtil.hexToBytes(this.kdfparams.getSalt()); 38 | try { 39 | return SCrypt.scrypt(password, salt, n, r, p, dkLen); 40 | } catch (GeneralSecurityException e) { 41 | throw new TokenException(Messages.SCRYPT_PARAMS_INVALID, e); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/crypto/SCryptParams.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.crypto; 2 | 3 | import com.google.common.base.Strings; 4 | import com.ldd.wallet.model.Messages; 5 | import com.ldd.wallet.model.TokenException; 6 | 7 | /** 8 | * Created by xyz on 2018/2/2. 9 | */ 10 | public class SCryptParams implements KDFParams { 11 | static final int COST_FACTOR = 8192; 12 | static final int BLOCK_SIZE_FACTOR = 8; 13 | static final int PARALLELIZATION_FACTOR = 1; 14 | private int dklen = 0; 15 | private int n = 0; 16 | private int p = 0; 17 | private int r = 0; 18 | private String salt; 19 | 20 | public SCryptParams() { 21 | } 22 | 23 | public static SCryptParams createSCryptParams() { 24 | SCryptParams params = new SCryptParams(); 25 | params.dklen = DK_LEN; 26 | params.n = COST_FACTOR; 27 | params.p = PARALLELIZATION_FACTOR; 28 | params.r = BLOCK_SIZE_FACTOR; 29 | return params; 30 | } 31 | 32 | public int getDklen() { 33 | return dklen; 34 | } 35 | 36 | public void setDklen(int dklen) { 37 | this.dklen = dklen; 38 | } 39 | 40 | public int getN() { 41 | return n; 42 | } 43 | 44 | public void setN(int n) { 45 | this.n = n; 46 | } 47 | 48 | public int getP() { 49 | return p; 50 | } 51 | 52 | public void setP(int p) { 53 | this.p = p; 54 | } 55 | 56 | public int getR() { 57 | return r; 58 | } 59 | 60 | public void setR(int r) { 61 | this.r = r; 62 | } 63 | 64 | public String getSalt() { 65 | return salt; 66 | } 67 | 68 | @Override 69 | public void validate() { 70 | if (n == 0 || dklen == 0 || p == 0 || r == 0 || Strings.isNullOrEmpty(salt)) { 71 | throw new TokenException(Messages.KDF_PARAMS_INVALID); 72 | } 73 | } 74 | 75 | public void setSalt(String salt) { 76 | this.salt = salt; 77 | } 78 | 79 | @Override 80 | public boolean equals(Object o) { 81 | if (this == o) { 82 | return true; 83 | } 84 | if (!(o instanceof SCryptParams)) { 85 | return false; 86 | } 87 | 88 | SCryptParams that = (SCryptParams) o; 89 | 90 | if (dklen != that.dklen) { 91 | return false; 92 | } 93 | if (n != that.n) { 94 | return false; 95 | } 96 | if (p != that.p) { 97 | return false; 98 | } 99 | if (r != that.r) { 100 | return false; 101 | } 102 | return getSalt() != null 103 | ? getSalt().equals(that.getSalt()) : that.getSalt() == null; 104 | } 105 | 106 | @Override 107 | public int hashCode() { 108 | int result = dklen; 109 | result = 31 * result + n; 110 | result = 31 * result + p; 111 | result = 31 * result + r; 112 | result = 31 * result + (getSalt() != null ? getSalt().hashCode() : 0); 113 | return result; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/rlp/RlpEncoder.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.rlp; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import com.ldd.foundation.utils.ByteUtil; 6 | 7 | /** 8 | *

Recursive Length Prefix (RLP) encoder.

9 | * 10 | *

For the specification, refer to p16 of the 11 | * yellow paper and here.

12 | */ 13 | public class RlpEncoder { 14 | 15 | private static final int STRING_OFFSET = 0x80; 16 | private static final int LIST_OFFSET = 0xc0; 17 | 18 | public static byte[] encode(RlpType value) { 19 | if (value instanceof RlpString) { 20 | return encodeString((RlpString) value); 21 | } else { 22 | return encodeList((RlpList) value); 23 | } 24 | } 25 | 26 | private static byte[] encode(byte[] bytesValue, int offset) { 27 | if (bytesValue.length == 1 28 | && offset == STRING_OFFSET 29 | && bytesValue[0] >= (byte) 0x00 30 | && bytesValue[0] <= (byte) 0x7f) { 31 | return bytesValue; 32 | } else if (bytesValue.length <= 55) { 33 | byte[] result = new byte[bytesValue.length + 1]; 34 | result[0] = (byte) (offset + bytesValue.length); 35 | System.arraycopy(bytesValue, 0, result, 1, bytesValue.length); 36 | return result; 37 | } else { 38 | byte[] encodedStringLength = toMinimalByteArray(bytesValue.length); 39 | byte[] result = new byte[bytesValue.length + encodedStringLength.length + 1]; 40 | 41 | result[0] = (byte) ((offset + 0x37) + encodedStringLength.length); 42 | System.arraycopy(encodedStringLength, 0, result, 1, encodedStringLength.length); 43 | System.arraycopy( 44 | bytesValue, 0, result, encodedStringLength.length + 1, bytesValue.length); 45 | return result; 46 | } 47 | } 48 | 49 | private static byte[] encodeString(RlpString value) { 50 | return encode(value.getBytes(), STRING_OFFSET); 51 | } 52 | 53 | private static byte[] toMinimalByteArray(int value) { 54 | byte[] encoded = toByteArray(value); 55 | 56 | for (int i = 0; i < encoded.length; i++) { 57 | if (encoded[i] != 0) { 58 | return Arrays.copyOfRange(encoded, i, encoded.length); 59 | } 60 | } 61 | 62 | return new byte[]{ }; 63 | } 64 | 65 | private static byte[] toByteArray(int value) { 66 | return new byte[] { 67 | (byte) ((value >> 24) & 0xff), 68 | (byte) ((value >> 16) & 0xff), 69 | (byte) ((value >> 8) & 0xff), 70 | (byte) (value & 0xff) 71 | }; 72 | } 73 | 74 | private static byte[] encodeList(RlpList value) { 75 | List values = value.getValues(); 76 | if (values.isEmpty()) { 77 | return encode(new byte[]{ }, LIST_OFFSET); 78 | } else { 79 | byte[] result = new byte[0]; 80 | for (RlpType entry:values) { 81 | result = ByteUtil.concat(result, encode(entry)); 82 | } 83 | return encode(result, LIST_OFFSET); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/rlp/RlpList.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.rlp; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | /** 7 | * RLP list type. 8 | */ 9 | public class RlpList implements RlpType { 10 | private final List values; 11 | 12 | public RlpList(RlpType... values) { 13 | this.values = Arrays.asList(values); 14 | } 15 | 16 | public RlpList(List values) { 17 | this.values = values; 18 | } 19 | 20 | public List getValues() { 21 | return values; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/rlp/RlpString.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.rlp; 2 | 3 | import java.math.BigInteger; 4 | import java.util.Arrays; 5 | 6 | 7 | /** 8 | * RLP string type. 9 | */ 10 | public class RlpString implements RlpType { 11 | private static final byte[] EMPTY = new byte[]{ }; 12 | 13 | private final byte[] value; 14 | 15 | private RlpString(byte[] value) { 16 | this.value = value; 17 | } 18 | 19 | public byte[] getBytes() { 20 | return value; 21 | } 22 | 23 | public static RlpString create(byte[] value) { 24 | return new RlpString(value); 25 | } 26 | 27 | public static RlpString create(byte value) { 28 | return new RlpString(new byte[]{ value }); 29 | } 30 | 31 | public static RlpString create(BigInteger value) { 32 | // RLP encoding only supports positive integer values 33 | if (value.signum() < 1) { 34 | return new RlpString(EMPTY); 35 | } else { 36 | byte[] bytes = value.toByteArray(); 37 | if (bytes[0] == 0) { // remove leading zero 38 | return new RlpString(Arrays.copyOfRange(bytes, 1, bytes.length)); 39 | } else { 40 | return new RlpString(bytes); 41 | } 42 | } 43 | } 44 | 45 | public static RlpString create(long value) { 46 | return create(BigInteger.valueOf(value)); 47 | } 48 | 49 | public static RlpString create(String value) { 50 | return new RlpString(value.getBytes()); 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) { 56 | return true; 57 | } 58 | if (o == null || getClass() != o.getClass()) { 59 | return false; 60 | } 61 | 62 | RlpString rlpString = (RlpString) o; 63 | 64 | return Arrays.equals(value, rlpString.value); 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return Arrays.hashCode(value); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/rlp/RlpType.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.rlp; 2 | 3 | /** 4 | * Base RLP type. 5 | */ 6 | public interface RlpType { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/utils/ByteUtil.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.utils; 2 | 3 | import java.util.Arrays; 4 | 5 | public class ByteUtil { 6 | private static byte[] trimLeadingBytes(byte[] bytes, byte b) { 7 | int offset = 0; 8 | for (; offset < bytes.length - 1; offset++) { 9 | if (bytes[offset] != b) { 10 | break; 11 | } 12 | } 13 | return Arrays.copyOfRange(bytes, offset, bytes.length); 14 | } 15 | 16 | public static byte[] trimLeadingZeroes(byte[] bytes) { 17 | return trimLeadingBytes(bytes, (byte) 0); 18 | } 19 | 20 | public static byte[] concat(byte[] b1, byte[] b2) { 21 | byte[] result = Arrays.copyOf(b1, b1.length + b2.length); 22 | System.arraycopy(b2, 0, result, b1.length, b2.length); 23 | return result; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/utils/CachedDerivedKey.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.utils; 2 | 3 | import com.ldd.foundation.crypto.Hash; 4 | 5 | public class CachedDerivedKey { 6 | private String hashedPassword; 7 | private byte[] derivedKey; 8 | 9 | 10 | public CachedDerivedKey(String password, byte[] derivedKey) { 11 | this.hashedPassword = hash(password); 12 | this.derivedKey = derivedKey; 13 | } 14 | 15 | private String hash(String password) { 16 | return NumericUtil.bytesToHex(Hash.sha256(Hash.sha256(password.getBytes()))); 17 | } 18 | 19 | public byte[] getDerivedKey(String password) { 20 | if (hashedPassword != null && hashedPassword.equals(hash(password))) { 21 | return derivedKey; 22 | } 23 | return null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/utils/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.utils; 2 | 3 | import java.util.Calendar; 4 | import java.util.TimeZone; 5 | 6 | public class DateUtil { 7 | public static long getUTCTime() { 8 | Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 9 | return cal.getTimeInMillis()/1000; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/utils/MetaUtil.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.utils; 2 | 3 | import com.ldd.wallet.model.ChainType; 4 | import com.ldd.wallet.model.Metadata; 5 | import org.bitcoinj.core.NetworkParameters; 6 | import org.bitcoinj.params.MainNetParams; 7 | import org.bitcoinj.params.TestNet3Params; 8 | 9 | /** 10 | * Created by pie on 2018/12/5 15: 32. 11 | */ 12 | public class MetaUtil { 13 | 14 | public static NetworkParameters getNetWork(Metadata metadata) { 15 | NetworkParameters network = null; 16 | if (metadata.getChainType() == null) { 17 | return MainNetParams.get(); 18 | } 19 | switch (metadata.getChainType()) { 20 | case ChainType.BITCOIN: 21 | network = metadata.isMainNet() ? MainNetParams.get() : TestNet3Params.get(); 22 | break; 23 | default: 24 | break; 25 | } 26 | return network; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/utils/MnemonicUtil.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.utils; 2 | 3 | import com.google.common.base.Joiner; 4 | import java.util.List; 5 | import org.bitcoinj.crypto.MnemonicCode; 6 | import com.ldd.wallet.model.Messages; 7 | import com.ldd.wallet.model.TokenException; 8 | 9 | public class MnemonicUtil { 10 | public static void validateMnemonics(List mnemonicCodes) { 11 | try { 12 | MnemonicCode.INSTANCE.check(mnemonicCodes); 13 | } catch (org.bitcoinj.crypto.MnemonicException.MnemonicLengthException e) { 14 | throw new TokenException(Messages.MNEMONIC_INVALID_LENGTH); 15 | } catch (org.bitcoinj.crypto.MnemonicException.MnemonicWordException e) { 16 | throw new TokenException(Messages.MNEMONIC_BAD_WORD); 17 | } catch (Exception e) { 18 | throw new TokenException(Messages.MNEMONIC_CHECKSUM); 19 | } 20 | } 21 | 22 | public static List randomMnemonicCodes() { 23 | return toMnemonicCodes(NumericUtil.generateRandomBytes(16)); 24 | } 25 | 26 | public static String randomMnemonicStr() { 27 | List mnemonicCodes=randomMnemonicCodes(); 28 | return Joiner.on(" ").join(mnemonicCodes); 29 | } 30 | 31 | 32 | public static List toMnemonicCodes(byte[] entropy) { 33 | try { 34 | return MnemonicCode.INSTANCE.toMnemonic(entropy); 35 | } catch (org.bitcoinj.crypto.MnemonicException.MnemonicLengthException e) { 36 | throw new TokenException(Messages.MNEMONIC_INVALID_LENGTH); 37 | } catch (Exception e) { 38 | throw new TokenException(Messages.MNEMONIC_CHECKSUM); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/foundation/utils/NumericUtil.java: -------------------------------------------------------------------------------- 1 | package com.ldd.foundation.utils; 2 | 3 | 4 | import com.google.common.base.Strings; 5 | import java.math.BigInteger; 6 | import java.nio.ByteBuffer; 7 | import java.nio.ByteOrder; 8 | import java.security.SecureRandom; 9 | import java.util.Arrays; 10 | import java.util.regex.Pattern; 11 | 12 | public class NumericUtil { 13 | private final static SecureRandom SECURE_RANDOM = new SecureRandom(); 14 | private static final String HEX_PREFIX = "0x"; 15 | 16 | public static byte[] generateRandomBytes(int size) { 17 | byte[] bytes = new byte[size]; 18 | SECURE_RANDOM.nextBytes(bytes); 19 | return bytes; 20 | } 21 | 22 | public static boolean isValidHex(String value) { 23 | if (value == null) { 24 | return false; 25 | } 26 | if (value.startsWith("0x") || value.startsWith("0X")) { 27 | value = value.substring(2, value.length()); 28 | } 29 | 30 | if (value.length() == 0 || value.length() % 2 != 0) { 31 | return false; 32 | } 33 | 34 | String pattern = "[0-9a-fA-F]+"; 35 | return Pattern.matches(pattern, value); 36 | // If TestRpc resolves the following issue, we can reinstate this code 37 | // https://github.com/ethereumjs/testrpc/issues/220 38 | // if (value.length() > 3 && value.charAt(2) == '0') { 39 | // return false; 40 | // } 41 | } 42 | 43 | public static String cleanHexPrefix(String input) { 44 | if (hasHexPrefix(input)) { 45 | return input.substring(2); 46 | } else { 47 | return input; 48 | } 49 | } 50 | 51 | public static String prependHexPrefix(String input) { 52 | if (input.length() > 1 && !hasHexPrefix(input)) { 53 | return HEX_PREFIX + input; 54 | } else { 55 | return input; 56 | } 57 | } 58 | 59 | private static boolean hasHexPrefix(String input) { 60 | return input.length() > 1 && input.charAt(0) == '0' && input.charAt(1) == 'x'; 61 | } 62 | 63 | public static BigInteger bytesToBigInteger(byte[] value, int offset, int length) { 64 | return bytesToBigInteger((Arrays.copyOfRange(value, offset, offset + length))); 65 | } 66 | 67 | public static BigInteger bytesToBigInteger(byte[] value) { 68 | return new BigInteger(1, value); 69 | } 70 | 71 | public static BigInteger hexToBigInteger(String hexValue) { 72 | String cleanValue = cleanHexPrefix(hexValue); 73 | return new BigInteger(cleanValue, 16); 74 | } 75 | 76 | public static String bigIntegerToHex(BigInteger value) { 77 | return value.toString(16); 78 | } 79 | 80 | public static String bigIntegerToHexWithZeroPadded(BigInteger value, int size) { 81 | String result = bigIntegerToHex(value); 82 | 83 | int length = result.length(); 84 | if (length > size) { 85 | throw new UnsupportedOperationException( 86 | "Value " + result + "is larger then length " + size); 87 | } else if (value.signum() < 0) { 88 | throw new UnsupportedOperationException("Value cannot be negative"); 89 | } 90 | 91 | if (length < size) { 92 | result = Strings.repeat("0", size - length) + result; 93 | } 94 | return result; 95 | } 96 | 97 | public static byte[] bigIntegerToBytesWithZeroPadded(BigInteger value, int length) { 98 | byte[] result = new byte[length]; 99 | byte[] bytes = value.toByteArray(); 100 | 101 | int bytesLength; 102 | int srcOffset; 103 | if (bytes[0] == 0) { 104 | bytesLength = bytes.length - 1; 105 | srcOffset = 1; 106 | } else { 107 | bytesLength = bytes.length; 108 | srcOffset = 0; 109 | } 110 | 111 | if (bytesLength > length) { 112 | throw new RuntimeException("Input is too large to put in byte array of size " + length); 113 | } 114 | 115 | int destOffset = length - bytesLength; 116 | System.arraycopy(bytes, srcOffset, result, destOffset, bytesLength); 117 | return result; 118 | } 119 | 120 | public static byte[] hexToBytes(String input) { 121 | String cleanInput = cleanHexPrefix(input); 122 | 123 | int len = cleanInput.length(); 124 | 125 | if (len == 0) { 126 | return new byte[]{}; 127 | } 128 | 129 | byte[] data; 130 | int startIdx; 131 | if (len % 2 != 0) { 132 | data = new byte[(len / 2) + 1]; 133 | data[0] = (byte) Character.digit(cleanInput.charAt(0), 16); 134 | startIdx = 1; 135 | } else { 136 | data = new byte[len / 2]; 137 | startIdx = 0; 138 | } 139 | 140 | for (int i = startIdx; i < len; i += 2) { 141 | data[(i + 1) / 2] = (byte) ((Character.digit(cleanInput.charAt(i), 16) << 4) 142 | + Character.digit(cleanInput.charAt(i + 1), 16)); 143 | } 144 | return data; 145 | } 146 | 147 | public static byte[] hexToBytesLittleEndian(String input) { 148 | byte[] bytes = hexToBytes(input); 149 | if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { 150 | return bytes; 151 | } 152 | int middle = bytes.length / 2; 153 | for (int i = 0; i < middle; i++) { 154 | byte b = bytes[i]; 155 | bytes[i] = bytes[bytes.length - 1 - i]; 156 | bytes[bytes.length - 1 - i] = b; 157 | } 158 | return bytes; 159 | } 160 | 161 | public static byte[] reverseBytes(byte[] bytes) { 162 | int middle = bytes.length / 2; 163 | for (int i = 0; i < middle; i++) { 164 | byte b = bytes[i]; 165 | bytes[i] = bytes[bytes.length - 1 - i]; 166 | bytes[bytes.length - 1 - i] = b; 167 | } 168 | return bytes; 169 | } 170 | 171 | public static String bytesToHex(byte[] input) { 172 | StringBuilder stringBuilder = new StringBuilder(); 173 | if (input.length == 0) { 174 | return ""; 175 | } 176 | 177 | for (byte anInput : input) { 178 | stringBuilder.append(String.format("%02x", anInput)); 179 | } 180 | 181 | return stringBuilder.toString(); 182 | } 183 | 184 | 185 | public static String beBigEndianHex(String hex) { 186 | if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { 187 | return hex; 188 | } 189 | 190 | return reverseHex(hex); 191 | } 192 | 193 | public static String beLittleEndianHex(String hex) { 194 | if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { 195 | return hex; 196 | } 197 | 198 | return reverseHex(hex); 199 | } 200 | 201 | private static String reverseHex(String hex) { 202 | byte[] bytes = hexToBytes(hex); 203 | bytes = reverseBytes(bytes); 204 | return bytesToHex(bytes); 205 | } 206 | 207 | public static int bytesToInt(byte[] bytes) { 208 | return ByteBuffer.wrap(bytes).getInt(); 209 | } 210 | 211 | public static byte[] intToBytes(int intValue) { 212 | 213 | byte[] intBytes = ByteBuffer.allocate(4).putInt(intValue).array(); 214 | int zeroLen = 0; 215 | for (byte b : intBytes) { 216 | if (b != 0) { 217 | break; 218 | } 219 | zeroLen++; 220 | } 221 | if (zeroLen == 4) { 222 | zeroLen = 3; 223 | } 224 | return Arrays.copyOfRange(intBytes, zeroLen, intBytes.length); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/KeystoreStorage.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Created by xyz on 2018/4/8. 7 | */ 8 | 9 | public interface KeystoreStorage { 10 | File getKeystoreDir(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/Wallet.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import org.bitcoinj.crypto.ChildNumber; 8 | import org.bitcoinj.crypto.DeterministicKey; 9 | import org.bitcoinj.crypto.HDKeyDerivation; 10 | import com.ldd.foundation.utils.MetaUtil; 11 | import com.ldd.foundation.utils.NumericUtil; 12 | import com.ldd.wallet.keystore.EncMnemonicKeystore; 13 | import com.ldd.wallet.keystore.ExportableKeystore; 14 | import com.ldd.wallet.keystore.HDMnemonicKeystore; 15 | import com.ldd.wallet.keystore.IMTKeystore; 16 | import com.ldd.wallet.keystore.V3Ignore; 17 | import com.ldd.wallet.keystore.V3Keystore; 18 | import com.ldd.wallet.keystore.V3MnemonicKeystore; 19 | import com.ldd.wallet.model.Messages; 20 | import com.ldd.wallet.model.Metadata; 21 | import com.ldd.wallet.model.MnemonicAndPath; 22 | import com.ldd.wallet.model.TokenException; 23 | 24 | 25 | public class Wallet { 26 | 27 | private IMTKeystore keystore; 28 | 29 | public IMTKeystore getKeystore() { 30 | return keystore; 31 | } 32 | 33 | public Wallet(IMTKeystore keystore) { 34 | this.keystore = keystore; 35 | } 36 | 37 | public String getId() { 38 | return this.keystore.getId(); 39 | } 40 | 41 | public String getAddress() { 42 | return this.keystore.getAddress(); 43 | } 44 | 45 | public Metadata getMetadata() { 46 | return keystore.getMetadata(); 47 | } 48 | 49 | public String getEncXPub() { 50 | if (keystore instanceof HDMnemonicKeystore) { 51 | return ((HDMnemonicKeystore) keystore).getEncryptXPub(); 52 | } 53 | return null; 54 | } 55 | 56 | public byte[] decryptMainKey(String password) { 57 | return keystore.decryptCiphertext(password); 58 | } 59 | 60 | MnemonicAndPath exportMnemonic(String password) { 61 | if (keystore instanceof EncMnemonicKeystore) { 62 | EncMnemonicKeystore encMnemonicKeystore = (EncMnemonicKeystore) keystore; 63 | String mnemonic = encMnemonicKeystore.decryptMnemonic(password); 64 | String path = encMnemonicKeystore.getMnemonicPath(); 65 | return new MnemonicAndPath(mnemonic, path); 66 | } 67 | return null; 68 | } 69 | 70 | String exportKeystore(String password) { 71 | if (keystore instanceof ExportableKeystore) { 72 | if (!keystore.verifyPassword(password)) { 73 | throw new TokenException(Messages.WALLET_INVALID_PASSWORD); 74 | } 75 | 76 | try { 77 | ObjectMapper mapper = new ObjectMapper(); 78 | mapper.addMixIn(IMTKeystore.class, V3Ignore.class); 79 | return mapper.writeValueAsString(keystore); 80 | } catch (Exception ex) { 81 | throw new TokenException(Messages.WALLET_INVALID, ex); 82 | } 83 | } else { 84 | throw new TokenException(Messages.CAN_NOT_EXPORT_MNEMONIC); 85 | } 86 | } 87 | 88 | public String exportPrivateKey(String password) { 89 | if (keystore instanceof V3Keystore || keystore instanceof V3MnemonicKeystore) { 90 | byte[] decrypted = keystore.decryptCiphertext(password); 91 | if (keystore.getMetadata().getSource().equals(Metadata.FROM_WIF)) { 92 | return new String(decrypted); 93 | } else { 94 | return NumericUtil.bytesToHex(decrypted); 95 | } 96 | } else if (keystore instanceof HDMnemonicKeystore) { 97 | String xprv = new String(decryptMainKey(password), StandardCharsets.UTF_8); 98 | DeterministicKey xprvKey = DeterministicKey 99 | .deserializeB58(xprv, MetaUtil.getNetWork(keystore.getMetadata())); 100 | DeterministicKey accountKey = HDKeyDerivation 101 | .deriveChildKey(xprvKey, new ChildNumber(0, false)); 102 | DeterministicKey externalChangeKey = HDKeyDerivation 103 | .deriveChildKey(accountKey, new ChildNumber(0, false)); 104 | return NumericUtil.bigIntegerToHex(externalChangeKey.getPrivKey()); 105 | } 106 | throw new TokenException(Messages.ILLEGAL_OPERATION); 107 | } 108 | 109 | public String exportPrivateKey(String password, int childNumber) { 110 | if (keystore instanceof V3Keystore || keystore instanceof V3MnemonicKeystore) { 111 | byte[] decrypted = keystore.decryptCiphertext(password); 112 | if (keystore.getMetadata().getSource().equals(Metadata.FROM_WIF)) { 113 | return new String(decrypted); 114 | } else { 115 | return NumericUtil.bytesToHex(decrypted); 116 | } 117 | } else if (keystore instanceof HDMnemonicKeystore) { 118 | String xprv = new String(decryptMainKey(password), StandardCharsets.UTF_8); 119 | DeterministicKey xprvKey = DeterministicKey 120 | .deserializeB58(xprv, MetaUtil.getNetWork(keystore.getMetadata())); 121 | DeterministicKey accountKey = HDKeyDerivation 122 | .deriveChildKey(xprvKey, new ChildNumber(0, false)); 123 | DeterministicKey externalChangeKey = HDKeyDerivation 124 | .deriveChildKey(accountKey, new ChildNumber(childNumber, false)); 125 | return NumericUtil.bigIntegerToHex(externalChangeKey.getPrivKey()); 126 | } 127 | throw new TokenException(Messages.ILLEGAL_OPERATION); 128 | } 129 | 130 | public String exportPrivateKey(String password, int childNumber,boolean isChange) { 131 | if (keystore instanceof V3Keystore || keystore instanceof V3MnemonicKeystore) { 132 | byte[] decrypted = keystore.decryptCiphertext(password); 133 | if (keystore.getMetadata().getSource().equals(Metadata.FROM_WIF)) { 134 | return new String(decrypted); 135 | } else { 136 | return NumericUtil.bytesToHex(decrypted); 137 | } 138 | } else if (keystore instanceof HDMnemonicKeystore) { 139 | String xprv = new String(decryptMainKey(password), StandardCharsets.UTF_8); 140 | DeterministicKey xprvKey = DeterministicKey 141 | .deserializeB58(xprv, MetaUtil.getNetWork(keystore.getMetadata())); 142 | DeterministicKey changeKey ; 143 | if (isChange) { 144 | changeKey = HDKeyDerivation.deriveChildKey(xprvKey, ChildNumber.ONE); 145 | }else { 146 | changeKey = HDKeyDerivation.deriveChildKey(xprvKey, ChildNumber.ZERO); 147 | } 148 | DeterministicKey externalChangeKey = HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber(childNumber)); 149 | return NumericUtil.bigIntegerToHex(externalChangeKey.getPrivKey()); 150 | } 151 | throw new TokenException(Messages.ILLEGAL_OPERATION); 152 | } 153 | 154 | boolean verifyPassword(String password) { 155 | return keystore.verifyPassword(password); 156 | } 157 | 158 | public String newReceiveAddress(int nextRecvIdx) { 159 | if (keystore instanceof HDMnemonicKeystore) { 160 | return ((HDMnemonicKeystore) keystore).newReceiveAddress(nextRecvIdx); 161 | } else { 162 | return keystore.getAddress(); 163 | } 164 | } 165 | 166 | public String newReceiveAddress(int nextRecvIdx, boolean isChange) { 167 | if (keystore instanceof HDMnemonicKeystore) { 168 | return ((HDMnemonicKeystore) keystore).newReceiveAddress(nextRecvIdx, isChange); 169 | } else { 170 | return keystore.getAddress(); 171 | } 172 | } 173 | 174 | public long getCreatedAt() { 175 | return this.keystore.getMetadata().getTimestamp(); 176 | } 177 | 178 | boolean hasMnemonic() { 179 | return this.keystore instanceof EncMnemonicKeystore; 180 | } 181 | 182 | 183 | boolean delete(String password) { 184 | return keystore.verifyPassword(password) && WalletManager.generateWalletFile(keystore.getId()) 185 | .delete(); 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/WalletManager.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.MapperFeature; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.google.common.base.Strings; 8 | import com.google.common.io.CharSource; 9 | import com.google.common.io.Files; 10 | import com.ldd.foundation.utils.MnemonicUtil; 11 | import com.ldd.foundation.utils.NumericUtil; 12 | import com.ldd.wallet.address.AddressCreatorManager; 13 | import com.ldd.wallet.address.EthereumAddressCreator; 14 | import com.ldd.wallet.address.TronAddressCreator; 15 | import com.ldd.wallet.keystore.HDMnemonicKeystore; 16 | import com.ldd.wallet.keystore.IMTKeystore; 17 | import com.ldd.wallet.keystore.Keystore; 18 | import com.ldd.wallet.keystore.V3Keystore; 19 | import com.ldd.wallet.keystore.V3MnemonicKeystore; 20 | import com.ldd.wallet.keystore.WalletKeystore; 21 | import com.ldd.wallet.model.BIP44Util; 22 | import com.ldd.wallet.model.ChainType; 23 | import com.ldd.wallet.model.Messages; 24 | import com.ldd.wallet.model.Metadata; 25 | import com.ldd.wallet.model.MnemonicAndPath; 26 | import com.ldd.wallet.model.Network; 27 | import com.ldd.wallet.model.TokenException; 28 | import com.ldd.wallet.validators.PrivateKeyValidator; 29 | import java.io.File; 30 | import java.io.IOException; 31 | import java.nio.charset.Charset; 32 | import java.util.Arrays; 33 | import java.util.Hashtable; 34 | import java.util.List; 35 | import lombok.extern.slf4j.Slf4j; 36 | import org.bitcoinj.core.Base58; 37 | import org.bitcoinj.core.Sha256Hash; 38 | import org.bitcoinj.crypto.DeterministicKey; 39 | import org.bitcoinj.wallet.DeterministicKeyChain; 40 | import org.bitcoinj.wallet.DeterministicSeed; 41 | import org.json.JSONObject; 42 | import org.spongycastle.util.encoders.Hex; 43 | 44 | @Slf4j 45 | public class WalletManager { 46 | 47 | private static Hashtable keystoreMap = new Hashtable<>(); 48 | private static final String LOG_TAG = WalletManager.class.getSimpleName(); 49 | 50 | public static KeystoreStorage storage; 51 | // 52 | // static { 53 | // try {ø 54 | // scanWallets(); 55 | // } catch (IOException ignored) { 56 | // } 57 | // } 58 | 59 | static Wallet createWallet(IMTKeystore keystore) { 60 | File file = generateWalletFile(keystore.getId()); 61 | writeToFile(keystore, file); 62 | keystoreMap.put(keystore.getId(), keystore); 63 | return new Wallet(keystore); 64 | } 65 | 66 | public static Hashtable getKeyMap() { 67 | return keystoreMap; 68 | } 69 | 70 | public static void changePassword(String id, String oldPassword, String newPassword) { 71 | IMTKeystore keystore = mustFindKeystoreById(id); 72 | IMTKeystore newKeystore = (IMTKeystore) keystore.changePassword(oldPassword, newPassword); 73 | flushWallet(newKeystore, true); 74 | } 75 | 76 | public static String exportPrivateKey(String id, String password) { 77 | Wallet wallet = mustFindWalletById(id); 78 | return wallet.exportPrivateKey(password); 79 | } 80 | 81 | public static MnemonicAndPath exportMnemonic(String id, String password) { 82 | Wallet wallet = mustFindWalletById(id); 83 | return wallet.exportMnemonic(password); 84 | } 85 | 86 | public static String exportKeystore(String id, String password) { 87 | Wallet wallet = mustFindWalletById(id); 88 | return wallet.exportKeystore(password); 89 | } 90 | 91 | public static void removeWallet(String id, String password) { 92 | Wallet wallet = mustFindWalletById(id); 93 | if (!wallet.verifyPassword(password)) { 94 | throw new TokenException(Messages.WALLET_INVALID_PASSWORD); 95 | } 96 | if (wallet.delete(password)) { 97 | Identity.getCurrentIdentity().removeWallet(id); 98 | keystoreMap.remove(id); 99 | } 100 | } 101 | 102 | public static void clearKeystoreMap() { 103 | keystoreMap.clear(); 104 | } 105 | 106 | public static Wallet importWalletFromKeystore(Metadata metadata, String keystoreContent, 107 | String password, boolean overwrite) throws Exception { 108 | WalletKeystore importedKeystore = validateKeystore(keystoreContent, password, 109 | metadata.getChainType()); 110 | if (metadata.getSource() == null) { 111 | metadata.setSource(Metadata.FROM_KEYSTORE); 112 | } 113 | String privateKey = NumericUtil.bytesToHex(importedKeystore.decryptCiphertext(password)); 114 | try { 115 | new PrivateKeyValidator(privateKey).validate(); 116 | } catch (TokenException ex) { 117 | if (Messages.PRIVATE_KEY_INVALID.equals(ex.getMessage())) { 118 | throw new TokenException(Messages.KEYSTORE_CONTAINS_INVALID_PRIVATE_KEY); 119 | } else { 120 | throw ex; 121 | } 122 | } 123 | return importWalletFromPrivateKey(metadata, privateKey, password, overwrite); 124 | } 125 | 126 | public static Wallet importWalletFromPrivateKey(Metadata metadata, String prvKeyHex, 127 | String password, boolean overwrite) { 128 | IMTKeystore keystore = V3Keystore.create(metadata, password, prvKeyHex); 129 | Wallet wallet = flushWallet(keystore, overwrite); 130 | Identity.getCurrentIdentity().addWallet(wallet); 131 | return wallet; 132 | } 133 | 134 | 135 | public static Wallet importWalletFromMnemonic(Metadata metadata, String mnemonic, String path, 136 | String password, boolean overwrite) { 137 | if (metadata.getSource() == null) { 138 | metadata.setSource(Metadata.FROM_MNEMONIC); 139 | } 140 | IMTKeystore keystore = null; 141 | List mnemonicCodes = Arrays.asList(mnemonic.split(" ")); 142 | MnemonicUtil.validateMnemonics(mnemonicCodes); 143 | switch (metadata.getChainType()) { 144 | case ChainType.ETHEREUM: 145 | keystore = V3MnemonicKeystore.create(metadata, password, mnemonicCodes, path); 146 | break; 147 | case ChainType.BITCOIN: 148 | keystore = HDMnemonicKeystore.create(metadata, password, mnemonicCodes, path); 149 | break; 150 | case ChainType.LITECOIN: 151 | keystore = HDMnemonicKeystore.create(metadata, password, mnemonicCodes, path); 152 | break; 153 | case ChainType.TRON: 154 | keystore = V3MnemonicKeystore.create(metadata, password, mnemonicCodes, path); 155 | break; 156 | default: 157 | break; 158 | } 159 | return persistWallet(keystore, overwrite); 160 | } 161 | 162 | public static Wallet findWalletByPrivateKey(String chainType, String network, String privateKey, 163 | String segWit) { 164 | if (ChainType.ETHEREUM.equals(chainType)) { 165 | new PrivateKeyValidator(privateKey).validate(); 166 | } 167 | Network net = new Network(network); 168 | String address = AddressCreatorManager.getInstance(chainType, net.isMainnet(), segWit) 169 | .fromPrivateKey(privateKey); 170 | return findWalletByAddress(chainType, address); 171 | } 172 | 173 | public static Wallet findWalletByKeystore(String chainType, String keystoreContent, 174 | String password) throws Exception { 175 | WalletKeystore walletKeystore = validateKeystore(keystoreContent, password, chainType); 176 | 177 | byte[] prvKeyBytes = walletKeystore.decryptCiphertext(password); 178 | String address = new EthereumAddressCreator().fromPrivateKey(prvKeyBytes); 179 | return findWalletByAddress(chainType, address); 180 | } 181 | 182 | public static Wallet findWalletByMnemonic(String chainType, String network, String mnemonic, 183 | String path, String segWit) { 184 | List mnemonicCodes = Arrays.asList(mnemonic.split(" ")); 185 | MnemonicUtil.validateMnemonics(mnemonicCodes); 186 | DeterministicSeed seed = new DeterministicSeed(mnemonicCodes, null, "", 0L); 187 | DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build(); 188 | if (Strings.isNullOrEmpty(path)) { 189 | throw new TokenException(Messages.INVALID_MNEMONIC_PATH); 190 | } 191 | 192 | if (ChainType.BITCOIN.equalsIgnoreCase(chainType) || ChainType.LITECOIN 193 | .equalsIgnoreCase(chainType)) { 194 | path += "/0/0"; 195 | } 196 | 197 | DeterministicKey key = keyChain.getKeyByPath(BIP44Util.generatePath(path), true); 198 | Network net = new Network(network); 199 | String address = AddressCreatorManager.getInstance(chainType, net.isMainnet(), segWit) 200 | .fromPrivateKey(key.getPrivateKeyAsHex()); 201 | return findWalletByAddress(chainType, address); 202 | } 203 | 204 | public static Wallet switchBTCWalletMode(String id, String password, String model) { 205 | Wallet wallet = mustFindWalletById(id); 206 | // !!! Warning !!! You must verify password before you write content to keystore 207 | if (!wallet.getMetadata().getChainType().equalsIgnoreCase(ChainType.BITCOIN)) { 208 | throw new TokenException("Ethereum wallet can't switch mode"); 209 | } 210 | Metadata metadata = wallet.getMetadata().clone(); 211 | if (metadata.getSegWit().equalsIgnoreCase(model)) { 212 | return wallet; 213 | } 214 | 215 | metadata.setSegWit(model); 216 | IMTKeystore keystore; 217 | if (wallet.hasMnemonic()) { 218 | 219 | MnemonicAndPath mnemonicAndPath = wallet.exportMnemonic(password); 220 | String path = BIP44Util.getBTCMnemonicPath(model, metadata.isMainNet()); 221 | List mnemonicCodes = Arrays.asList(mnemonicAndPath.getMnemonic().split(" ")); 222 | keystore = new HDMnemonicKeystore(metadata, password, mnemonicCodes, path, wallet.getId()); 223 | } else { 224 | String prvKey = wallet.exportPrivateKey(password); 225 | keystore = new V3Keystore(metadata, password, prvKey, wallet.getId()); 226 | 227 | } 228 | flushWallet(keystore, false); 229 | keystoreMap.put(wallet.getId(), keystore); 230 | return new Wallet(keystore); 231 | } 232 | 233 | static Wallet findWalletById(String id) { 234 | IMTKeystore keystore = keystoreMap.get(id); 235 | if (keystore != null) { 236 | return new Wallet(keystore); 237 | } else { 238 | return null; 239 | } 240 | } 241 | 242 | public static Wallet mustFindWalletById(String id) { 243 | IMTKeystore keystore = keystoreMap.get(id); 244 | if (keystore == null) { 245 | throw new TokenException(Messages.WALLET_NOT_FOUND); 246 | } 247 | return new Wallet(keystore); 248 | } 249 | 250 | 251 | static File generateWalletFile(String walletID) { 252 | return new File(getDefaultKeyDirectory(), walletID + ".json"); 253 | } 254 | 255 | 256 | static File getDefaultKeyDirectory() { 257 | File directory = new File(storage.getKeystoreDir(), "wallets"); 258 | if (!directory.exists()) { 259 | directory.mkdirs(); 260 | } 261 | return directory; 262 | } 263 | 264 | static boolean cleanKeystoreDirectory() { 265 | return deleteDir(getDefaultKeyDirectory()); 266 | } 267 | 268 | private static Wallet persistWallet(IMTKeystore keystore, boolean overwrite) { 269 | Wallet wallet = flushWallet(keystore, overwrite); 270 | Identity.getCurrentIdentity().addWallet(wallet); 271 | return wallet; 272 | } 273 | 274 | private static IMTKeystore findKeystoreByAddress(String type, String address) { 275 | if (Strings.isNullOrEmpty(address)) { 276 | return null; 277 | } 278 | for (IMTKeystore keystore : keystoreMap.values()) { 279 | if (Strings.isNullOrEmpty(keystore.getAddress())) { 280 | continue; 281 | } 282 | if (keystore.getMetadata().getChainType().equals(type) && keystore.getAddress() 283 | .equals(address)) { 284 | return keystore; 285 | } 286 | } 287 | return null; 288 | } 289 | 290 | public static Wallet findWalletByAddress(String type, String address) { 291 | IMTKeystore keystore = findKeystoreByAddress(type, address); 292 | if (keystore != null) { 293 | return new Wallet(keystore); 294 | } 295 | return null; 296 | } 297 | 298 | 299 | private static Wallet flushWallet(IMTKeystore keystore, boolean overwrite) { 300 | 301 | IMTKeystore existsKeystore = findKeystoreByAddress(keystore.getMetadata().getChainType(), 302 | keystore.getAddress()); 303 | if (existsKeystore != null) { 304 | if (!overwrite) { 305 | throw new TokenException(Messages.WALLET_EXISTS); 306 | } else { 307 | keystore.setId(existsKeystore.getId()); 308 | } 309 | } 310 | 311 | File file = generateWalletFile(keystore.getId()); 312 | writeToFile(keystore, file); 313 | keystoreMap.put(keystore.getId(), keystore); 314 | return new Wallet(keystore); 315 | } 316 | 317 | private static void writeToFile(Keystore keyStore, File destination) { 318 | try { 319 | ObjectMapper objectMapper = new ObjectMapper(); 320 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 321 | objectMapper.writeValue(destination, keyStore); 322 | } catch (IOException ex) { 323 | throw new TokenException(Messages.WALLET_STORE_FAIL, ex); 324 | } 325 | } 326 | 327 | private static boolean deleteDir(File dir) { 328 | if (dir.isDirectory()) { 329 | String[] children = dir.list(); 330 | for (String child : children) { 331 | boolean success = deleteDir(new File(dir, child)); 332 | if (!success) { 333 | return false; 334 | } 335 | } 336 | } 337 | return dir.delete(); 338 | } 339 | 340 | private static V3Keystore validateKeystore(String keystoreContent, String password, 341 | String chainType) throws Exception { 342 | V3Keystore importedKeystore = unmarshalKeystore(keystoreContent, V3Keystore.class); 343 | if (Strings.isNullOrEmpty(importedKeystore.getAddress()) 344 | || importedKeystore.getCrypto() == null) { 345 | throw new TokenException(Messages.WALLET_INVALID_KEYSTORE); 346 | } 347 | 348 | importedKeystore.getCrypto().validate(); 349 | 350 | if (!importedKeystore.verifyPassword(password)) { 351 | throw new TokenException(Messages.MAC_UNMATCH); 352 | } 353 | 354 | byte[] prvKey = importedKeystore.decryptCiphertext(password); 355 | String address = ""; 356 | if (ChainType.ETHEREUM.equals(chainType)) { 357 | address = new EthereumAddressCreator().fromPrivateKey(prvKey); 358 | } else if (ChainType.TRON.equals(chainType)) { 359 | address = new TronAddressCreator().fromPrivateKey(prvKey); 360 | } else { 361 | throw new Exception("ChainType not found"); 362 | } 363 | if (Strings.isNullOrEmpty(address) || !address 364 | .equalsIgnoreCase(importedKeystore.getAddress())) { 365 | throw new TokenException(Messages.PRIVATE_KEY_ADDRESS_NOT_MATCH); 366 | } 367 | return importedKeystore; 368 | } 369 | 370 | private static IMTKeystore mustFindKeystoreById(String id) { 371 | IMTKeystore keystore = keystoreMap.get(id); 372 | if (keystore == null) { 373 | throw new TokenException(Messages.WALLET_NOT_FOUND); 374 | } 375 | 376 | return keystore; 377 | } 378 | 379 | private static T unmarshalKeystore(String keystoreContent, 380 | Class clazz) { 381 | T importedKeystore; 382 | try { 383 | ObjectMapper mapper = new ObjectMapper(); 384 | mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); 385 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 386 | mapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true); 387 | importedKeystore = mapper.readValue(keystoreContent, clazz); 388 | } catch (IOException ex) { 389 | throw new TokenException(Messages.WALLET_INVALID_KEYSTORE, ex); 390 | } 391 | return importedKeystore; 392 | } 393 | 394 | public static void scanWallets() { 395 | File directory = getDefaultKeyDirectory(); 396 | keystoreMap.clear(); 397 | for (File file : directory.listFiles()) { 398 | if (!file.getName().startsWith("identity")) { 399 | try { 400 | IMTKeystore keystore = null; 401 | CharSource charSource = Files.asCharSource(file, Charset.forName("UTF-8")); 402 | String jsonContent = charSource.read(); 403 | JSONObject jsonObject = new JSONObject(jsonContent); 404 | int version = jsonObject.getInt("version"); 405 | if (version == 3) { 406 | if (jsonContent.contains("encMnemonic")) { 407 | keystore = unmarshalKeystore(jsonContent, V3MnemonicKeystore.class); 408 | } else { 409 | keystore = unmarshalKeystore(jsonContent, V3Keystore.class); 410 | } 411 | } else if (version == 1) { 412 | keystore = unmarshalKeystore(jsonContent, V3Keystore.class); 413 | } else if (version == 44) { 414 | keystore = unmarshalKeystore(jsonContent, HDMnemonicKeystore.class); 415 | } 416 | if (keystore != null) { 417 | keystoreMap.put(keystore.getId(), keystore); 418 | } 419 | } catch (Exception ex) { 420 | log.info(LOG_TAG, "Can't loaded " + file.getName() + " file", ex); 421 | } 422 | } 423 | } 424 | } 425 | 426 | private WalletManager() { 427 | } 428 | 429 | /** 430 | * 十六进制格式转换 431 | * 432 | * @param hex 压缩前私钥 433 | * @return 压缩后私钥 434 | */ 435 | public static String bigIntegerToBase58(String hex) { 436 | if (hex.length() < 64) { 437 | StringBuilder stringBuilder = new StringBuilder(); 438 | for (int i = 0; i < 64 - hex.length(); i++) { 439 | stringBuilder.append("0"); 440 | } 441 | hex = stringBuilder.toString() + hex; 442 | } 443 | hex = "80" + hex + "01"; 444 | byte[] hash1 = Sha256Hash.hash(Hex.decode(hex)); 445 | byte[] hash2 = Sha256Hash.hash(hash1); 446 | String result = hex + Hex.toHexString(hash2).substring(0, 8); 447 | return Base58.encode(Hex.decode(result)); 448 | } 449 | 450 | } 451 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/address/AddressCreator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.address; 2 | 3 | public interface AddressCreator { 4 | String fromPrivateKey(String prvKeyHex); 5 | String fromPrivateKey(byte[] prvKeyBytes); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/address/AddressCreatorManager.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.address; 2 | 3 | import org.bitcoinj.core.NetworkParameters; 4 | import org.bitcoinj.params.MainNetParams; 5 | import org.bitcoinj.params.TestNet3Params; 6 | import com.ldd.wallet.model.ChainType; 7 | import com.ldd.wallet.model.Messages; 8 | import com.ldd.wallet.model.Metadata; 9 | import com.ldd.wallet.model.TokenException; 10 | import com.ldd.wallet.network.*; 11 | 12 | public class AddressCreatorManager { 13 | 14 | public static AddressCreator getInstance(String type, boolean isMainnet, String segWit) { 15 | if (ChainType.ETHEREUM.equals(type)) { 16 | return new EthereumAddressCreator(); 17 | } else if (ChainType.TRON.equals(type)) { 18 | return new TronAddressCreator(); 19 | } else if (ChainType.LITECOIN.equals(type)) { 20 | NetworkParameters network = LitecoinMainNetParams.get(); 21 | return new BitcoinAddressCreator(network); 22 | } else if (ChainType.DASH.equals(type)) { 23 | NetworkParameters network = DashMainNetParams.get(); 24 | return new BitcoinAddressCreator(network); 25 | } else if (ChainType.DOGECOIN.equals(type)) { 26 | NetworkParameters network = DogecoinMainNetParams.get(); 27 | return new BitcoinAddressCreator(network); 28 | } else if (ChainType.BITCOINSV.equals(type)) { 29 | NetworkParameters network = BitcoinSvMainNetParams.get(); 30 | return new BitcoinAddressCreator(network); 31 | } else if (ChainType.BITCOINCASH.equals(type)) { 32 | NetworkParameters network = BitcoinCashMainNetParams.get(); 33 | return new BitcoinAddressCreator(network); 34 | } else if (ChainType.BITCOIN.equals(type)) { 35 | 36 | NetworkParameters network = isMainnet ? MainNetParams.get() : TestNet3Params.get(); 37 | if (Metadata.P2WPKH.equals(segWit)) { 38 | return new SegWitBitcoinAddressCreator(network); 39 | } 40 | return new BitcoinAddressCreator(network); 41 | } else { 42 | throw new TokenException(Messages.WALLET_INVALID_TYPE); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/address/BitcoinAddressCreator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.address; 2 | 3 | import java.util.Arrays; 4 | import org.bitcoinj.core.Address; 5 | import org.bitcoinj.core.DumpedPrivateKey; 6 | import org.bitcoinj.core.ECKey; 7 | import org.bitcoinj.core.NetworkParameters; 8 | import com.ldd.foundation.utils.NumericUtil; 9 | import org.spongycastle.util.encoders.Hex; 10 | 11 | public class BitcoinAddressCreator implements AddressCreator { 12 | private NetworkParameters networkParameters; 13 | 14 | public BitcoinAddressCreator(NetworkParameters networkParameters) { 15 | this.networkParameters = networkParameters; 16 | } 17 | 18 | @Override 19 | public String fromPrivateKey(String prvKeyHex) { 20 | ECKey key; 21 | if (prvKeyHex.length() == 51 || prvKeyHex.length() == 52) { 22 | DumpedPrivateKey dumpedPrivateKey = DumpedPrivateKey.fromBase58(networkParameters, prvKeyHex); 23 | key = dumpedPrivateKey.getKey(); 24 | } else { 25 | key = ECKey.fromPrivate(NumericUtil.hexToBytes(prvKeyHex)); 26 | } 27 | byte[] pubKeyHash = key.getPubKeyHash(); 28 | return new Address(this.networkParameters, pubKeyHash).toBase58(); 29 | // return key.toAddress(this.networkParameters).toBase58(); 30 | } 31 | 32 | @Override 33 | public String fromPrivateKey(byte[] prvKeyBytes) { 34 | ECKey key = ECKey.fromPrivate(prvKeyBytes); 35 | return key.toAddress(this.networkParameters).toBase58(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/address/EthereumAddressCreator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.address; 2 | 3 | import java.math.BigInteger; 4 | import java.util.Arrays; 5 | import org.bitcoinj.core.ECKey; 6 | import com.ldd.foundation.crypto.Hash; 7 | import com.ldd.foundation.utils.NumericUtil; 8 | 9 | 10 | public class EthereumAddressCreator implements AddressCreator { 11 | 12 | private static final int PUBLIC_KEY_SIZE = 64; 13 | private static final int PUBLIC_KEY_LENGTH_IN_HEX = PUBLIC_KEY_SIZE << 1; 14 | private static final int ADDRESS_LENGTH = 20; 15 | private static final int ADDRESS_LENGTH_IN_HEX = ADDRESS_LENGTH << 1; 16 | 17 | 18 | public String fromPublicKey(BigInteger publicKey) { 19 | byte[] pubKeyBytes = NumericUtil.bigIntegerToBytesWithZeroPadded(publicKey, PUBLIC_KEY_SIZE); 20 | return publicKeyToAddress(pubKeyBytes); 21 | } 22 | 23 | private String publicKeyToAddress(byte[] pubKeyBytes) { 24 | byte[] hashedBytes = Hash.keccak256(pubKeyBytes); 25 | byte[] addrBytes = Arrays.copyOfRange(hashedBytes, hashedBytes.length - 20, hashedBytes.length); 26 | return NumericUtil.bytesToHex(addrBytes); 27 | } 28 | 29 | @Override 30 | public String fromPrivateKey(String prvKeyHex) { 31 | ECKey key = ECKey.fromPrivate(NumericUtil.hexToBytes(prvKeyHex), false); 32 | return fromECKey(key); 33 | } 34 | 35 | @Override 36 | public String fromPrivateKey(byte[] prvKeyBytes) { 37 | ECKey key = ECKey.fromPrivate(prvKeyBytes, false); 38 | return fromECKey(key); 39 | } 40 | 41 | private String fromECKey(ECKey key) { 42 | byte[] pubKeyBytes = key.getPubKey(); 43 | return publicKeyToAddress(Arrays.copyOfRange(pubKeyBytes, 1, pubKeyBytes.length)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/address/SegWitBitcoinAddressCreator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.address; 2 | 3 | import java.util.Arrays; 4 | import org.bitcoinj.core.Address; 5 | import org.bitcoinj.core.DumpedPrivateKey; 6 | import org.bitcoinj.core.ECKey; 7 | import org.bitcoinj.core.NetworkParameters; 8 | import org.bitcoinj.core.Utils; 9 | import com.ldd.foundation.utils.NumericUtil; 10 | import com.ldd.wallet.model.Messages; 11 | import com.ldd.wallet.model.TokenException; 12 | import org.spongycastle.util.encoders.Hex; 13 | 14 | public class SegWitBitcoinAddressCreator implements AddressCreator { 15 | private NetworkParameters networkParameters; 16 | 17 | public SegWitBitcoinAddressCreator(NetworkParameters networkParameters) { 18 | this.networkParameters = networkParameters; 19 | } 20 | 21 | @Override 22 | public String fromPrivateKey(String prvKeyHex) { 23 | ECKey key; 24 | if (prvKeyHex.length() == 51 || prvKeyHex.length() == 52) { 25 | DumpedPrivateKey dumpedPrivateKey = DumpedPrivateKey.fromBase58(networkParameters, prvKeyHex); 26 | key = dumpedPrivateKey.getKey(); 27 | if (!key.isCompressed()) { 28 | throw new TokenException(Messages.SEGWIT_NEEDS_COMPRESS_PUBLIC_KEY); 29 | } 30 | } else { 31 | key = ECKey.fromPrivate(NumericUtil.hexToBytes(prvKeyHex), true); 32 | } 33 | return calcSegWitAddress(key.getPubKeyHash()); 34 | } 35 | 36 | @Override 37 | public String fromPrivateKey(byte[] prvKeyBytes) { 38 | ECKey key = ECKey.fromPrivate(prvKeyBytes, true); 39 | return calcSegWitAddress(key.getPubKeyHash()); 40 | } 41 | 42 | private String calcSegWitAddress(byte[] pubKeyHash) { 43 | String redeemScript = String.format("0x0014%s", NumericUtil.bytesToHex(pubKeyHash)); 44 | return Address.fromP2SHHash(networkParameters, Utils.sha256hash160(NumericUtil.hexToBytes(redeemScript))).toBase58(); 45 | } 46 | 47 | public Address fromPrivateKey(ECKey ecKey) { 48 | String redeemScript = String.format("0x0014%s", NumericUtil.bytesToHex(ecKey.getPubKeyHash())); 49 | return Address.fromP2SHHash(networkParameters, Utils.sha256hash160(NumericUtil.hexToBytes(redeemScript))); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/address/TronAddressCreator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.address; 2 | 3 | import static java.util.Arrays.copyOfRange; 4 | 5 | import java.math.BigInteger; 6 | import java.util.Arrays; 7 | import org.bitcoinj.core.Base58; 8 | import org.bitcoinj.core.ECKey; 9 | import com.ldd.foundation.crypto.Hash; 10 | import com.ldd.foundation.utils.NumericUtil; 11 | 12 | /** 13 | * Created by pie on 2020/9/6 22: 28. 14 | */ 15 | public class TronAddressCreator implements AddressCreator { 16 | 17 | private static final byte ADD_PRE_FIX_BYTE_MAINNET = (byte) 0x41; 18 | 19 | private static final int PUBLIC_KEY_SIZE = 64; 20 | 21 | private String encode58Check(byte[] input) { 22 | byte[] hash0 = Hash.sha256(input); 23 | byte[] hash1 = Hash.sha256(hash0); 24 | byte[] inputCheck = new byte[input.length + 4]; 25 | System.arraycopy(input, 0, inputCheck, 0, input.length); 26 | System.arraycopy(hash1, 0, inputCheck, input.length, 4); 27 | return Base58.encode(inputCheck); 28 | } 29 | 30 | public String fromPublicKey(BigInteger publicKey) { 31 | byte[] pubKeyBytes = NumericUtil.bigIntegerToBytesWithZeroPadded(publicKey, PUBLIC_KEY_SIZE); 32 | return publicKeyToAddress(pubKeyBytes); 33 | } 34 | 35 | private String publicKeyToAddress(byte[] pubKeyBytes) { 36 | byte[] hashedBytes = Hash.keccak256(pubKeyBytes); 37 | byte[] address = copyOfRange(hashedBytes, 11, hashedBytes.length); 38 | address[0] = ADD_PRE_FIX_BYTE_MAINNET; 39 | return encode58Check(address); 40 | } 41 | 42 | @Override 43 | public String fromPrivateKey(String prvKeyHex) { 44 | ECKey key = ECKey.fromPrivate(NumericUtil.hexToBytes(prvKeyHex), false); 45 | return fromECKey(key); 46 | } 47 | 48 | @Override 49 | public String fromPrivateKey(byte[] prvKeyBytes) { 50 | ECKey key = ECKey.fromPrivate(prvKeyBytes, false); 51 | return fromECKey(key); 52 | } 53 | 54 | private String fromECKey(ECKey key) { 55 | byte[] pubKeyBytes = key.getPubKey(); 56 | return publicKeyToAddress(Arrays.copyOfRange(pubKeyBytes, 1, pubKeyBytes.length)); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/keystore/EncMnemonicKeystore.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.keystore; 2 | 3 | import com.ldd.foundation.crypto.Crypto; 4 | import com.ldd.foundation.crypto.EncPair; 5 | import java.nio.charset.Charset; 6 | 7 | public interface EncMnemonicKeystore { 8 | 9 | EncPair getEncMnemonic(); 10 | 11 | void setEncMnemonic(EncPair encMnemonic); 12 | 13 | String getMnemonicPath(); 14 | 15 | Crypto getCrypto(); 16 | 17 | default void createEncMnemonic(String password, String mnemonic) { 18 | EncPair encMnemonic = getCrypto() 19 | .deriveEncPair(password, mnemonic.getBytes(Charset.forName("UTF-8"))); 20 | this.setEncMnemonic(encMnemonic); 21 | } 22 | 23 | default String decryptMnemonic(String password) { 24 | return new String(getCrypto().decryptEncPair(password, getEncMnemonic())); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/keystore/ExportableKeystore.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.keystore; 2 | 3 | public interface ExportableKeystore { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/keystore/HDMnemonicKeystore.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.keystore; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.google.common.base.Joiner; 5 | import com.google.common.base.Strings; 6 | import com.google.common.io.BaseEncoding; 7 | import com.subgraph.orchid.encoders.Hex; 8 | import java.nio.charset.Charset; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.UUID; 12 | import org.bitcoinj.core.NetworkParameters; 13 | import org.bitcoinj.crypto.ChildNumber; 14 | import org.bitcoinj.crypto.DeterministicKey; 15 | import org.bitcoinj.crypto.HDKeyDerivation; 16 | import org.bitcoinj.wallet.DeterministicKeyChain; 17 | import org.bitcoinj.wallet.DeterministicSeed; 18 | import com.ldd.foundation.crypto.AES; 19 | import com.ldd.foundation.crypto.Crypto; 20 | import com.ldd.foundation.crypto.EncPair; 21 | import com.ldd.foundation.utils.DateUtil; 22 | import com.ldd.foundation.utils.MetaUtil; 23 | import com.ldd.foundation.utils.MnemonicUtil; 24 | import com.ldd.wallet.address.SegWitBitcoinAddressCreator; 25 | import com.ldd.wallet.model.BIP44Util; 26 | import com.ldd.wallet.model.Messages; 27 | import com.ldd.wallet.model.Metadata; 28 | import com.ldd.wallet.model.TokenException; 29 | 30 | /** 31 | * Created by xyz on 2018/2/5. 32 | */ 33 | 34 | public final class HDMnemonicKeystore extends IMTKeystore implements EncMnemonicKeystore { 35 | 36 | // !!! Don't use this key in production !!! 37 | public static String XPubCommonKey128 = "B888D25EC8C12BD5043777B1AC49F872"; 38 | public static String XPubCommonIv = "9C0C30889CBCC5E01AB5B2BB88715799"; 39 | 40 | static int VERSION = 44; 41 | private EncPair encMnemonic; 42 | private String mnemonicPath; 43 | private String xpub; 44 | 45 | public Info getInfo() { 46 | return info; 47 | } 48 | 49 | public void setInfo(Info info) { 50 | this.info = info; 51 | } 52 | 53 | private Info info; 54 | 55 | @Override 56 | public EncPair getEncMnemonic() { 57 | return encMnemonic; 58 | } 59 | 60 | @Override 61 | public void setEncMnemonic(EncPair encMnemonic) { 62 | this.encMnemonic = encMnemonic; 63 | } 64 | 65 | @Override 66 | public String getMnemonicPath() { 67 | return mnemonicPath; 68 | } 69 | 70 | public void setMnemonicPath(String mnemonicPath) { 71 | this.mnemonicPath = mnemonicPath; 72 | } 73 | 74 | public String getXpub() { 75 | return this.xpub; 76 | } 77 | 78 | public void setXpub(String xpub) { 79 | this.xpub = xpub; 80 | } 81 | 82 | public HDMnemonicKeystore() { 83 | super(); 84 | } 85 | 86 | public static HDMnemonicKeystore create(Metadata metadata, String password, List mnemonics, String path) { 87 | return new HDMnemonicKeystore(metadata, password, mnemonics, path, ""); 88 | } 89 | 90 | public HDMnemonicKeystore(Metadata metadata, String password, List mnemonics, String path, String id) { 91 | MnemonicUtil.validateMnemonics(mnemonics); 92 | DeterministicSeed seed = new DeterministicSeed(mnemonics, null, "", 0L); 93 | DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build(); 94 | this.mnemonicPath = path; 95 | 96 | DeterministicKey parent = keyChain.getKeyByPath(BIP44Util.generatePath(path), true); 97 | NetworkParameters networkParameters = MetaUtil.getNetWork(metadata); 98 | this.xpub = parent.serializePubB58(networkParameters); 99 | String xprv = parent.serializePrivB58(networkParameters); 100 | DeterministicKey mainAddressKey = keyChain.getKeyByPath(BIP44Util.generatePath(path + "/0/0"), true); 101 | if (Metadata.P2WPKH.equals(metadata.getSegWit())) { 102 | this.address = new SegWitBitcoinAddressCreator(networkParameters).fromPrivateKey(mainAddressKey.getPrivateKeyAsHex()); 103 | } else { 104 | this.address = mainAddressKey.toAddress(networkParameters).toBase58(); 105 | } 106 | if (metadata.getTimestamp() == 0) { 107 | metadata.setTimestamp(DateUtil.getUTCTime()); 108 | } 109 | metadata.setWalletType(Metadata.HD); 110 | 111 | this.crypto = Crypto.createPBKDF2CryptoWithKDFCached(password, xprv.getBytes(Charset.forName("UTF-8"))); 112 | this.metadata = metadata; 113 | this.encMnemonic = crypto.deriveEncPair(password, Joiner.on(" ").join(mnemonics).getBytes()); 114 | this.crypto.clearCachedDerivedKey(); 115 | 116 | this.version = VERSION; 117 | this.info = new Info(); 118 | this.id = Strings.isNullOrEmpty(id) ? UUID.randomUUID().toString() : id; 119 | } 120 | 121 | 122 | @Override 123 | public Keystore changePassword(String oldPassword, String newPassword) { 124 | String mnemonic = new String(getCrypto().decryptEncPair(oldPassword, encMnemonic)); 125 | List mnemonicCodes = Arrays.asList(mnemonic.split(" ")); 126 | return new HDMnemonicKeystore(metadata, newPassword, mnemonicCodes, this.mnemonicPath, this.id); 127 | } 128 | 129 | @JsonIgnore 130 | public String getEncryptXPub() { 131 | String plainText = this.xpub; 132 | try { 133 | 134 | byte[] commonKey128 = Hex.decode(XPubCommonKey128); 135 | byte[] clean = plainText.getBytes(); 136 | byte[] commonIv = Hex.decode(XPubCommonIv); 137 | byte[] encrypted = AES.encryptByCBC(clean, commonKey128, commonIv); 138 | return BaseEncoding.base64().encode(encrypted); 139 | } catch (Exception ex) { 140 | throw new TokenException(Messages.ENCRYPT_XPUB_ERROR); 141 | } 142 | } 143 | 144 | public String newReceiveAddress(int nextIdx) { 145 | NetworkParameters networkParameters = MetaUtil.getNetWork(this.metadata); 146 | DeterministicKey key = DeterministicKey.deserializeB58(this.xpub, networkParameters); 147 | DeterministicKey changeKey = HDKeyDerivation.deriveChildKey(key, ChildNumber.ZERO); 148 | DeterministicKey indexKey = HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber(nextIdx)); 149 | if (Metadata.P2WPKH.equals(metadata.getSegWit())) { 150 | return new SegWitBitcoinAddressCreator(networkParameters).fromPrivateKey(indexKey).toBase58(); 151 | } else { 152 | return indexKey.toAddress(networkParameters).toBase58(); 153 | } 154 | } 155 | 156 | public String newReceiveAddress(int nextIdx,boolean isChange) { 157 | NetworkParameters networkParameters = MetaUtil.getNetWork(this.metadata); 158 | DeterministicKey key = DeterministicKey.deserializeB58(this.xpub, networkParameters); 159 | DeterministicKey changeKey ; 160 | if (isChange) { 161 | changeKey = HDKeyDerivation.deriveChildKey(key, ChildNumber.ONE); 162 | }else { 163 | changeKey = HDKeyDerivation.deriveChildKey(key, ChildNumber.ZERO); 164 | } 165 | DeterministicKey indexKey = HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber(nextIdx)); 166 | if (Metadata.P2WPKH.equals(metadata.getSegWit())) { 167 | return new SegWitBitcoinAddressCreator(networkParameters).fromPrivateKey(indexKey).toBase58(); 168 | } else { 169 | return indexKey.toAddress(networkParameters).toBase58(); 170 | } 171 | } 172 | 173 | 174 | public static class Info { 175 | private String curve = "spec256k1"; 176 | private String purpuse = "sign"; 177 | 178 | public Info() { 179 | 180 | } 181 | 182 | public String getCurve() { 183 | return curve; 184 | } 185 | 186 | public void setCurve(String curve) { 187 | this.curve = curve; 188 | } 189 | 190 | public String getPurpuse() { 191 | return purpuse; 192 | } 193 | 194 | public void setPurpuse(String purpuse) { 195 | this.purpuse = purpuse; 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/keystore/IMTKeystore.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.keystore; 2 | 3 | import com.fasterxml.jackson.annotation.JsonGetter; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.ldd.wallet.model.Metadata; 6 | 7 | public abstract class IMTKeystore extends WalletKeystore { 8 | @JsonIgnore 9 | Metadata metadata; 10 | 11 | @JsonGetter(value = "imTokenMeta") 12 | public Metadata getMetadata() { 13 | return metadata; 14 | } 15 | 16 | public void setMetadata(Metadata metadata) { 17 | this.metadata = metadata; 18 | } 19 | 20 | public IMTKeystore() { 21 | super(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/keystore/IdentityKeystore.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.keystore; 2 | 3 | import com.fasterxml.jackson.annotation.JsonGetter; 4 | import com.google.common.base.Joiner; 5 | import java.nio.charset.Charset; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import org.bitcoinj.core.Base58; 10 | import org.bitcoinj.core.ECKey; 11 | import org.bitcoinj.core.NetworkParameters; 12 | import org.bitcoinj.core.Sha256Hash; 13 | import org.bitcoinj.crypto.DeterministicKey; 14 | import org.bitcoinj.crypto.HDKeyDerivation; 15 | import org.bitcoinj.wallet.DeterministicSeed; 16 | import com.ldd.foundation.crypto.Crypto; 17 | import com.ldd.foundation.crypto.EncPair; 18 | import com.ldd.foundation.crypto.Hash; 19 | import com.ldd.foundation.crypto.Multihash; 20 | import com.ldd.foundation.utils.*; 21 | import com.ldd.wallet.model.Metadata; 22 | 23 | /** 24 | * Created by xyz on 2017/12/11. 25 | */ 26 | 27 | public class IdentityKeystore extends Keystore implements EncMnemonicKeystore { 28 | private static final int VERSION = 10000; 29 | 30 | public void setIdentifier(String identifier) { 31 | this.identifier = identifier; 32 | } 33 | 34 | public void setWalletIDs(List walletIDs) { 35 | this.walletIDs = walletIDs; 36 | } 37 | 38 | public void setIpfsId(String ipfsId) { 39 | this.ipfsId = ipfsId; 40 | 41 | } 42 | 43 | 44 | public void setEncKey(String encKey) { 45 | this.encKey = encKey; 46 | } 47 | 48 | public void setEncAuthKey(EncPair encAuthKey) { 49 | this.encAuthKey = encAuthKey; 50 | } 51 | 52 | public void setMnemonicPath(String mnemonicPath) { 53 | this.mnemonicPath = mnemonicPath; 54 | } 55 | 56 | private String identifier; 57 | private String ipfsId; 58 | private String encKey; 59 | private EncPair encAuthKey; 60 | private EncPair encMnemonic; 61 | private String mnemonicPath; 62 | 63 | public void setMetadata(Metadata metadata) { 64 | this.metadata = metadata; 65 | } 66 | 67 | private Metadata metadata; 68 | private List walletIDs = new ArrayList<>(); 69 | 70 | public IdentityKeystore() { 71 | 72 | } 73 | 74 | 75 | public IdentityKeystore(Metadata metadata, List mnemonicCodes, String password) { 76 | MnemonicUtil.validateMnemonics(mnemonicCodes); 77 | 78 | DeterministicSeed seed = new DeterministicSeed(mnemonicCodes, null, "", 0L); 79 | 80 | DeterministicKey masterPrivateKey = HDKeyDerivation.createMasterPrivateKey(seed.getSeedBytes()); 81 | byte[] masterKey = masterPrivateKey.getPrivKeyBytes(); 82 | 83 | String salt = metadata.isMainNet() ? "Automatic Backup Key Mainnet" : "Automatic Backup Key Testnet"; 84 | byte[] backupKey = Hash.hmacSHA256(masterKey, salt.getBytes(Charset.forName("ASCII"))); 85 | byte[] authenticationKey = Hash.hmacSHA256(backupKey, "Authentication Key".getBytes(Charset.forName("UTF-8"))); 86 | ECKey authKey = ECKey.fromPrivate(authenticationKey); 87 | 88 | NetworkParameters networkParameters = MetaUtil.getNetWork(metadata); 89 | 90 | String aPubHashHex = NumericUtil.bytesToHex(authKey.getPubKeyHash()); 91 | int networkHeader = networkParameters.getAddressHeader(); 92 | int version = 2; 93 | // this magic hex will start with 'im' after base58check 94 | String magicHex = "0fdc0c"; 95 | String fullIdentifier = String.format("%s%02x%02x%s", magicHex, (byte) networkHeader, (byte) version, aPubHashHex); 96 | byte[] fullIdentifierBytes = NumericUtil.hexToBytes(fullIdentifier); 97 | byte[] checksumBytes = Arrays.copyOfRange(Sha256Hash.hashTwice(fullIdentifierBytes), 0, 4); 98 | byte[] identifierWithChecksum = ByteUtil.concat(fullIdentifierBytes, checksumBytes); 99 | this.identifier = Base58.encode(identifierWithChecksum); 100 | 101 | byte[] encKeyFullBytes = Hash.hmacSHA256(backupKey, "Encryption Key".getBytes(Charset.forName("UTF-8"))); 102 | this.encKey = NumericUtil.bytesToHex(encKeyFullBytes); 103 | 104 | ECKey ecKey = ECKey.fromPrivate(encKeyFullBytes, false); 105 | this.ipfsId = new Multihash(Multihash.Type.sha2_256, Hash.sha256(ecKey.getPubKey())).toBase58(); 106 | Crypto crypto = Crypto.createPBKDF2CryptoWithKDFCached(password, masterPrivateKey.serializePrivB58(networkParameters).getBytes(Charset.forName("UTF-8"))); 107 | 108 | this.encAuthKey = crypto.deriveEncPair(password, authenticationKey); 109 | this.encMnemonic = crypto.deriveEncPair(password, Joiner.on(" ").join(mnemonicCodes).getBytes()); 110 | crypto.clearCachedDerivedKey(); 111 | metadata.setTimestamp(DateUtil.getUTCTime()); 112 | 113 | metadata.setSegWit(null); 114 | this.metadata = metadata; 115 | this.crypto = crypto; 116 | 117 | this.version = VERSION; 118 | this.walletIDs = new ArrayList<>(); 119 | } 120 | 121 | @Override 122 | public EncPair getEncMnemonic() { 123 | return this.encMnemonic; 124 | } 125 | 126 | @Override 127 | public void setEncMnemonic(EncPair encMnemonic) { 128 | this.encMnemonic = encMnemonic; 129 | } 130 | 131 | @Override 132 | public String getMnemonicPath() { 133 | return this.mnemonicPath; 134 | } 135 | 136 | public List getWalletIDs() { 137 | return walletIDs; 138 | } 139 | 140 | @JsonGetter(value = "imTokenMeta") 141 | public Metadata getMetadata() { 142 | return metadata; 143 | } 144 | 145 | public String getIdentifier() { 146 | return identifier; 147 | } 148 | 149 | public String getIpfsId() { 150 | return ipfsId; 151 | } 152 | 153 | public String getEncKey() { 154 | return encKey; 155 | } 156 | 157 | public EncPair getEncAuthKey() { 158 | return encAuthKey; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/keystore/Keystore.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.keystore; 2 | 3 | import com.ldd.foundation.crypto.Crypto; 4 | import java.util.UUID; 5 | 6 | /** 7 | * Created by xyz on 2018/2/5. 8 | */ 9 | 10 | public abstract class Keystore { 11 | String id; 12 | int version; 13 | 14 | Crypto crypto; 15 | 16 | public Keystore() { 17 | this.id = UUID.randomUUID().toString(); 18 | } 19 | public String getId() { 20 | return id; 21 | } 22 | 23 | public void setId(String id) { 24 | this.id = id; 25 | } 26 | 27 | public int getVersion() { 28 | return version; 29 | } 30 | 31 | public void setVersion(int version) { 32 | this.version = version; 33 | } 34 | 35 | public Crypto getCrypto() { 36 | return crypto; 37 | } 38 | 39 | public void setCrypto(Crypto crypto) { 40 | this.crypto = crypto; 41 | } 42 | 43 | public boolean verifyPassword(String password) { 44 | return getCrypto().verifyPassword(password); 45 | } 46 | 47 | public byte[] decryptCiphertext(String password) { 48 | return getCrypto().decrypt(password); 49 | } 50 | } 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/keystore/V3Ignore.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.keystore; 2 | 3 | import com.fasterxml.jackson.annotation.JsonGetter; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.ldd.foundation.crypto.EncPair; 6 | import com.ldd.wallet.model.Metadata; 7 | 8 | /** 9 | * Created by xyz on 2018/2/8. 10 | */ 11 | public abstract class V3Ignore { 12 | @JsonIgnore 13 | public abstract EncPair getEncMnemonic(); 14 | 15 | @JsonIgnore 16 | @JsonGetter(value = "imTokenMeta") 17 | public abstract Metadata getMetadata(); 18 | 19 | @JsonIgnore 20 | public abstract String getMnemonicPath(); 21 | 22 | @JsonIgnore 23 | public abstract String getEncXPub(); 24 | 25 | @JsonIgnore 26 | public abstract int getMnemonicIndex(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/keystore/V3Keystore.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.keystore; 2 | 3 | import com.google.common.base.Strings; 4 | import com.ldd.foundation.crypto.Crypto; 5 | import com.ldd.wallet.address.TronAddressCreator; 6 | import java.util.UUID; 7 | import org.bitcoinj.core.NetworkParameters; 8 | import com.ldd.foundation.utils.MetaUtil; 9 | import com.ldd.foundation.utils.NumericUtil; 10 | import com.ldd.wallet.address.BitcoinAddressCreator; 11 | import com.ldd.wallet.address.EthereumAddressCreator; 12 | import com.ldd.wallet.address.SegWitBitcoinAddressCreator; 13 | import com.ldd.wallet.model.ChainType; 14 | import com.ldd.wallet.model.Metadata; 15 | import com.ldd.wallet.model.TokenException; 16 | import com.ldd.wallet.validators.PrivateKeyValidator; 17 | import com.ldd.wallet.validators.WIFValidator; 18 | 19 | /** 20 | * Created by xyz on 2018/2/5. 21 | */ 22 | 23 | public class V3Keystore extends IMTKeystore implements ExportableKeystore { 24 | public static final int VERSION = 3; 25 | 26 | public V3Keystore() { 27 | } 28 | 29 | public static V3Keystore create(Metadata metadata, String password, String prvKeyHex) { 30 | return new V3Keystore(metadata, password, prvKeyHex, ""); 31 | } 32 | 33 | public V3Keystore(Metadata metadata, String password, String prvKeyHex, String id) { 34 | byte[] prvKeyBytes; 35 | if (metadata.getChainType().equals(ChainType.BITCOIN)||metadata.getChainType().equals(ChainType.LITECOIN)) { 36 | NetworkParameters network = MetaUtil.getNetWork(metadata); 37 | prvKeyBytes = prvKeyHex.getBytes(); 38 | new WIFValidator(prvKeyHex, network).validate(); 39 | if (Metadata.P2WPKH.equals(metadata.getSegWit())) { 40 | this.address = new SegWitBitcoinAddressCreator(network).fromPrivateKey(prvKeyHex); 41 | } else { 42 | this.address = new BitcoinAddressCreator(network).fromPrivateKey(prvKeyHex); 43 | } 44 | } else if (metadata.getChainType().equals(ChainType.ETHEREUM)) { 45 | prvKeyBytes = NumericUtil.hexToBytes(prvKeyHex); 46 | new PrivateKeyValidator(prvKeyHex).validate(); 47 | this.address = new EthereumAddressCreator().fromPrivateKey(prvKeyBytes); 48 | }else if (metadata.getChainType().equals(ChainType.TRON)){ 49 | prvKeyBytes = NumericUtil.hexToBytes(prvKeyHex); 50 | new PrivateKeyValidator(prvKeyHex).validate(); 51 | this.address = new TronAddressCreator().fromPrivateKey(prvKeyBytes); 52 | } else { 53 | throw new TokenException("Can't init other keystore in this constructor"); 54 | } 55 | this.crypto = Crypto.createPBKDF2Crypto(password, prvKeyBytes); 56 | metadata.setWalletType(Metadata.V3); 57 | this.metadata = metadata; 58 | this.version = VERSION; 59 | this.id = Strings.isNullOrEmpty(id) ? UUID.randomUUID().toString() : id; 60 | } 61 | 62 | 63 | @Override 64 | public Keystore changePassword(String oldPassword, String newPassword) { 65 | byte[] decrypted = this.crypto.decrypt(oldPassword); 66 | String prvKeyHex; 67 | if (Metadata.FROM_WIF.equals(getMetadata().getSource())) { 68 | prvKeyHex = new String(decrypted); 69 | } else { 70 | prvKeyHex = NumericUtil.bytesToHex(decrypted); 71 | } 72 | return new V3Keystore(metadata, newPassword, prvKeyHex, this.id); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/keystore/V3MnemonicKeystore.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.keystore; 2 | 3 | import com.google.common.base.Joiner; 4 | import com.google.common.base.Strings; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.UUID; 8 | import org.bitcoinj.crypto.ChildNumber; 9 | import org.bitcoinj.wallet.DeterministicKeyChain; 10 | import org.bitcoinj.wallet.DeterministicSeed; 11 | import com.ldd.foundation.crypto.Crypto; 12 | import com.ldd.foundation.crypto.EncPair; 13 | import com.ldd.foundation.utils.DateUtil; 14 | import com.ldd.foundation.utils.MnemonicUtil; 15 | import com.ldd.wallet.address.AddressCreatorManager; 16 | import com.ldd.wallet.model.BIP44Util; 17 | import com.ldd.wallet.model.Metadata; 18 | 19 | /** 20 | * Created by xyz on 2018/2/5. 21 | */ 22 | 23 | public class V3MnemonicKeystore extends IMTKeystore implements EncMnemonicKeystore, ExportableKeystore { 24 | private static final int VERSION = 3; 25 | private EncPair encMnemonic; 26 | private String mnemonicPath; 27 | 28 | @Override 29 | public EncPair getEncMnemonic() { 30 | return encMnemonic; 31 | } 32 | 33 | @Override 34 | public void setEncMnemonic(EncPair encMnemonic) { 35 | this.encMnemonic = encMnemonic; 36 | } 37 | 38 | @Override 39 | public String getMnemonicPath() { 40 | return this.mnemonicPath; 41 | } 42 | 43 | public void setMnemonicPath(String mnemonicPath) { 44 | this.mnemonicPath = mnemonicPath; 45 | } 46 | 47 | public V3MnemonicKeystore() { 48 | super(); 49 | } 50 | 51 | public static V3MnemonicKeystore create(Metadata metadata, String password, List mnemonicCodes, String path) { 52 | return new V3MnemonicKeystore(metadata, password, mnemonicCodes, path, ""); 53 | } 54 | 55 | 56 | private V3MnemonicKeystore(Metadata metadata, String password, List mnemonicCodes, String path, String id) { 57 | MnemonicUtil.validateMnemonics(mnemonicCodes); 58 | DeterministicSeed seed = new DeterministicSeed(mnemonicCodes, null, "", 0L); 59 | DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build(); 60 | 61 | this.mnemonicPath = path; 62 | List zeroPath = BIP44Util.generatePath(path); 63 | 64 | byte[] prvKeyBytes = keyChain.getKeyByPath(zeroPath, true).getPrivKeyBytes(); 65 | this.crypto = Crypto.createPBKDF2CryptoWithKDFCached(password, prvKeyBytes); 66 | this.encMnemonic = crypto.deriveEncPair(password, Joiner.on(" ").join(mnemonicCodes).getBytes()); 67 | this.crypto.clearCachedDerivedKey(); 68 | 69 | this.address = AddressCreatorManager.getInstance(metadata.getChainType(), metadata.isMainNet(), metadata.getSegWit()).fromPrivateKey(prvKeyBytes); 70 | metadata.setTimestamp(DateUtil.getUTCTime()); 71 | metadata.setWalletType(Metadata.V3); 72 | this.metadata = metadata; 73 | this.version = VERSION; 74 | this.id = Strings.isNullOrEmpty(id) ? UUID.randomUUID().toString() : id; 75 | } 76 | 77 | 78 | 79 | @Override 80 | public Keystore changePassword(String oldPassword, String newPassword) { 81 | String mnemonic = new String(getCrypto().decryptEncPair(oldPassword, this.getEncMnemonic())); 82 | List mnemonicCodes = Arrays.asList(mnemonic.split(" ")); 83 | return new V3MnemonicKeystore(this.metadata, newPassword, mnemonicCodes, this.mnemonicPath, this.id); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/keystore/WalletKeystore.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.keystore; 2 | 3 | public abstract class WalletKeystore extends Keystore { 4 | String address; 5 | 6 | public String getAddress() { 7 | return address; 8 | } 9 | 10 | public void setAddress(String address) { 11 | this.address = address; 12 | } 13 | 14 | public abstract Keystore changePassword(String oldPassword, String newPassword); 15 | 16 | public WalletKeystore() { super();} 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/model/BIP44Util.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.model; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import org.bitcoinj.crypto.ChildNumber; 7 | 8 | public class BIP44Util { 9 | public final static String BITCOIN_MAINNET_PATH = "m/44'/0'/0'"; 10 | public final static String BITCOIN_TESTNET_PATH = "m/44'/1'/0'"; 11 | public final static String BITCOIN_SEGWIT_MAIN_PATH = "m/49'/0'/0'"; 12 | public final static String BITCOIN_SEGWIT_TESTNET_PATH = "m/49'/1'/0'"; 13 | public final static String LITECOIN_MAINNET_PATH = "m/44'/2'/0'"; 14 | public final static String DOGECOIN_MAINNET_PATH = "m/44'/3'/0'"; 15 | public final static String DASH_MAINNET_PATH = "m/44'/5'/0'"; 16 | public final static String BITCOINSV_MAINNET_PATH="m/44'/236'/0'"; 17 | public final static String BITCOINCASH_MAINNET_PATH="m/44'/145'/0'"; 18 | public final static String ETHEREUM_PATH = "m/44'/60'/0'/0/0"; 19 | public final static String EOS_PATH = "m/44'/194'"; 20 | public final static String EOS_SLIP48 = "m/48'/4'/0'/0'/0',m/48'/4'/1'/0'/0'"; 21 | public final static String EOS_LEDGER = "m/44'/194'/0'/0/0"; 22 | public final static String TRON_PATH ="m/44'/195'/0'/0/0"; 23 | 24 | 25 | public static ImmutableList generatePath(String path) { 26 | List list = new ArrayList<>(); 27 | for (String p : path.split("/")) { 28 | if ("m".equalsIgnoreCase(p) || "".equals(p.trim())) { 29 | continue; 30 | } else if (p.charAt(p.length() - 1) == '\'') { 31 | list.add(new ChildNumber(Integer.parseInt(p.substring(0, p.length() - 1)), true)); 32 | } else { 33 | list.add(new ChildNumber(Integer.parseInt(p), false)); 34 | } 35 | } 36 | 37 | ImmutableList.Builder builder = ImmutableList.builder(); 38 | return builder.addAll(list).build(); 39 | } 40 | 41 | public static String getBTCMnemonicPath(String segWit, boolean isMainnet) { 42 | if (Metadata.P2WPKH.equalsIgnoreCase(segWit)) { 43 | return isMainnet ? BITCOIN_SEGWIT_MAIN_PATH : BITCOIN_SEGWIT_TESTNET_PATH; 44 | } else { 45 | return isMainnet ? BITCOIN_MAINNET_PATH : BITCOIN_TESTNET_PATH; 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/model/ChainId.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.model; 2 | 3 | public class ChainId { 4 | public static final int BITCOIN_MAINNET = 0; 5 | public static final int BITCOIN_TESTNET = 1; 6 | 7 | // ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md 8 | public static final int ETHEREUM_MAINNET = 1; 9 | public static final int ETHEREUM_ROPSTEN = 3; 10 | public static final int ETHEREUM_KOVAN = 42; 11 | public static final int ETHEREUM_CLASSIC_MAINNET = 61; 12 | public static final int ETHEREUM_CLASSIC_TESTNET = 62; 13 | 14 | public static final int ANY = 999; 15 | 16 | public static final String EOS_MAINNET = "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906"; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/model/ChainType.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.model; 2 | 3 | public class ChainType { 4 | public final static String ETHEREUM = "ETHEREUM"; 5 | public final static String BITCOIN = "BITCOIN"; 6 | public final static String EOS = "EOS"; 7 | public final static String LITECOIN = "LITECOIN"; 8 | public final static String DASH = "DASH"; 9 | public final static String BITCOINCASH = "BITCOINCASH"; 10 | public final static String BITCOINSV = "BITCOINSV"; 11 | public final static String DOGECOIN = "DOGECOIN"; 12 | public final static String TRON = "TRON"; 13 | 14 | 15 | public static void validate(String type) { 16 | if (!ETHEREUM.equals(type) && 17 | !BITCOIN.equals(type) && 18 | !EOS.equals(type) && 19 | !LITECOIN.equals(type) && 20 | !DASH.equals(type) && 21 | !BITCOINSV.equals(type) && 22 | !BITCOINCASH.equals(type) && 23 | !DOGECOIN.equals(type) && 24 | !TRON.equals(type)) { 25 | throw new TokenException(Messages.WALLET_INVALID_TYPE); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/model/KeyPair.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.model; 2 | 3 | import java.util.Objects; 4 | 5 | public class KeyPair { 6 | 7 | String privateKey; 8 | String publicKey; 9 | 10 | public KeyPair() { 11 | } 12 | 13 | public KeyPair(String privateKey, String publicKey) { 14 | this.privateKey = privateKey; 15 | this.publicKey = publicKey; 16 | } 17 | 18 | public String getPrivateKey() { 19 | return privateKey; 20 | } 21 | 22 | public void setPrivateKey(String privateKey) { 23 | this.privateKey = privateKey; 24 | } 25 | 26 | public String getPublicKey() { 27 | return publicKey; 28 | } 29 | 30 | public void setPublicKey(String publicKey) { 31 | this.publicKey = publicKey; 32 | } 33 | 34 | @Override 35 | public boolean equals(Object o) { 36 | if (this == o) return true; 37 | if (o == null || getClass() != o.getClass()) return false; 38 | KeyPair keyPair = (KeyPair) o; 39 | return Objects.equals(privateKey, keyPair.privateKey) && 40 | Objects.equals(publicKey, keyPair.publicKey); 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | 46 | return Objects.hash(privateKey, publicKey); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/model/Messages.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.model; 2 | 3 | public class Messages { 4 | public static final String UNKNOWN = "unknown"; 5 | 6 | public static final String WALLET_INVALID_PASSWORD = "password_incorrect"; 7 | public static final String PASSWORD_BLANK = "password_blank"; 8 | public static final String PASSWORD_WEAK = "password_weak"; 9 | 10 | public static final String MNEMONIC_BAD_WORD = "mnemonic_word_invalid"; 11 | public static final String MNEMONIC_INVALID_LENGTH = "mnemonic_length_invalid"; 12 | public static final String MNEMONIC_CHECKSUM = "mnemonic_checksum_invalid"; 13 | 14 | public static final String INVALID_MNEMONIC_PATH = "invalid_mnemonic_path"; 15 | 16 | public static final String APPLICATION_NOT_READY = "application_not_ready"; 17 | 18 | public static final String WALLET_NOT_FOUND = "wallet_not_found"; 19 | 20 | public static final String WALLET_INVALID_KEYSTORE = "keystore_invalid"; 21 | public static final String WALLET_STORE_FAIL = "store_wallet_failed"; 22 | public static final String WALLET_INVALID = "keystore_invalid"; 23 | public static final String KDF_UNSUPPORTED = "kdf_unsupported"; 24 | 25 | public static final String WALLET_INVALID_TYPE = "unsupported_chain"; 26 | public static final String WALLET_INVALID_ADDRESS = "address_invalid"; 27 | 28 | public static final String INVALID_TRANSACTION_DATA = "transaction_data_invalid"; 29 | public static final String WALLET_EXISTS = "address_already_exist"; 30 | public static final String INVALID_WALLET_VERSION = "keystore_version_invalid"; 31 | public static final String WALLET_HD_NOT_SUPPORT_PRIVATE = "hd_not_support_private"; 32 | public static final String WALLET_SHA256 = "sha256"; 33 | 34 | public static final String IPFS_CHECK_SIGNATURE = "check_signature"; 35 | public static final String NOT_UTF8 = "not_utf8"; 36 | 37 | public static final String MAC_UNMATCH = "mac_unmatch"; 38 | public static final String PRIVATE_KEY_ADDRESS_NOT_MATCH = "private_key_address_not_match"; 39 | 40 | 41 | public static final String CAN_NOT_TO_JSON = "can_not_to_json" ; 42 | public static final String CAN_NOT_FROM_JSON = "can_not_from_json" ; 43 | 44 | public static final String SCRYPT_PARAMS_INVALID = "scrypt_params_invalid"; 45 | public static final String PRF_UNSUPPORTED = "prf_unsupported"; 46 | public static final String KDF_PARAMS_INVALID = "kdf_params_invalid"; 47 | public static final String CIPHER_FAIL = "cipher_unsupported"; 48 | 49 | 50 | 51 | public static final String INVALID_BIG_NUMBER = "big_number_invalid"; 52 | public static final String INVALID_NEGATIVE = "negative_invalid"; 53 | public static final String INVALID_HEX = "hex_invalid"; 54 | 55 | public static final String INSUFFICIENT_FUNDS = "insufficient_funds"; 56 | public static final String CAN_NOT_FOUND_PRIVATE_KEY = "can_not_found_private_key"; 57 | public static final String WIF_WRONG_NETWORK = "wif_wrong_network"; 58 | public static final String WIF_INVALID = "wif_invalid"; 59 | public static final String PRIVATE_KEY_INVALID = "privatekey_invalid"; 60 | public static final String CAN_NOT_EXPORT_MNEMONIC = "not_support_export_keystore"; 61 | 62 | public static final String INVALID_IDENTITY = "invalid_identity"; 63 | 64 | public static final String UNSUPPORT_SEND_TARGET = "not_support_send_target"; 65 | public static final String ILLEGAL_OPERATION = "illegal_operation"; 66 | public static final String UNSUPPORT_ENCRYPTION_DATA_VERSION = "unsupport_encryption_data_version"; 67 | public static final String INVALID_ENCRYPTION_DATA_SIGNATURE = "invalid_encryption_data_signature"; 68 | 69 | public static final String ENCRYPT_XPUB_ERROR = "encrypt_xpub_error"; 70 | 71 | public static final String SEGWIT_NEEDS_COMPRESS_PUBLIC_KEY = "segwit_needs_compress_public_key"; 72 | public static final String EOS_PRIVATE_PUBLIC_NOT_MATCH = "eos_private_public_not_match"; 73 | public static final String EOS_PUBLIC_KEY_NOT_FOUND = "eos_public_key_not_found"; 74 | public static final String EOS_ACCOUNT_NAME_INVALID = "eos_account_name_invalid"; 75 | 76 | public static final String AMOUNT_LESS_THAN_MINIMUM = "amount_less_than_minimum"; 77 | public static final String KEYSTORE_CONTAINS_INVALID_PRIVATE_KEY = "keystore_contains_invalid_private_key"; 78 | 79 | public static final String REQUIRED_EOS_WALLET = "required_eos_wallet"; 80 | 81 | 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/model/Metadata.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class Metadata implements Cloneable { 8 | 9 | public static final String FROM_MNEMONIC = "MNEMONIC"; 10 | public static final String FROM_KEYSTORE = "KEYSTORE"; 11 | public static final String FROM_PRIVATE = "PRIVATE"; 12 | public static final String FROM_WIF = "WIF"; 13 | public static final String FROM_NEW_IDENTITY = "NEW_IDENTITY"; 14 | public static final String FROM_RECOVERED_IDENTITY = "RECOVERED_IDENTITY"; 15 | 16 | public static final String P2WPKH = "P2WPKH"; 17 | public static final String NONE = "NONE"; 18 | 19 | public static final String NORMAL = "NORMAL"; 20 | 21 | public static final String HD = "HD"; 22 | public static final String RANDOM = "RANDOM"; 23 | public static final String HD_SHA256 = "HD_SHA256"; 24 | public static final String V3 = "V3"; 25 | 26 | 27 | private String name; 28 | private String passwordHint; 29 | private String chainType; 30 | private long timestamp; 31 | private String network; 32 | private List backup = new ArrayList<>(); 33 | private String source; 34 | private String mode = NORMAL; 35 | private String walletType; 36 | private String segWit; 37 | 38 | // for jackson serial 39 | public Metadata() { 40 | } 41 | 42 | 43 | @Override 44 | public Metadata clone() { 45 | 46 | Metadata metadata = null; 47 | try { 48 | metadata = (Metadata) super.clone(); 49 | } catch (CloneNotSupportedException ex) { 50 | throw new TokenException("Clone metadata filed"); 51 | } 52 | metadata.backup = new ArrayList<>(backup); 53 | return metadata; 54 | } 55 | 56 | public String getSegWit() { 57 | return segWit; 58 | } 59 | 60 | public void setSegWit(String segWit) { 61 | this.segWit = segWit; 62 | } 63 | 64 | public Metadata(String type, String network, String name, String passwordHint) { 65 | this.chainType = type; 66 | this.name = name; 67 | this.passwordHint = passwordHint; 68 | 69 | this.timestamp = System.currentTimeMillis() / 1000; 70 | this.network = network; 71 | } 72 | 73 | public Metadata(String type, String network, String name, String passwordHint, String segWit) { 74 | this.chainType = type; 75 | this.name = name; 76 | this.passwordHint = passwordHint; 77 | this.timestamp = System.currentTimeMillis() / 1000; 78 | this.network = network; 79 | this.segWit = segWit; 80 | } 81 | 82 | public String getName() { 83 | return name; 84 | } 85 | 86 | public void setName(String name) { 87 | this.name = name; 88 | } 89 | 90 | public String getPasswordHint() { 91 | return passwordHint; 92 | } 93 | 94 | public void setPasswordHint(String passwordHint) { 95 | this.passwordHint = passwordHint; 96 | } 97 | 98 | public String getChainType() { 99 | return chainType; 100 | } 101 | 102 | public void setChainType(String chainType) { 103 | this.chainType = chainType; 104 | } 105 | 106 | public long getTimestamp() { 107 | return timestamp; 108 | } 109 | 110 | public void setTimestamp(long timestamp) { 111 | this.timestamp = timestamp; 112 | } 113 | 114 | public List getBackup() { 115 | return backup; 116 | } 117 | 118 | public void setBackup(List backup) { 119 | this.backup = backup; 120 | } 121 | 122 | public String getSource() { 123 | return source; 124 | } 125 | 126 | public void setSource(String source) { 127 | this.source = source; 128 | } 129 | 130 | public String getMode() { 131 | return mode; 132 | } 133 | 134 | public void setMode(String mode) { 135 | this.mode = mode; 136 | } 137 | 138 | public String getWalletType() { 139 | return walletType; 140 | } 141 | 142 | public void setWalletType(String walletType) { 143 | this.walletType = walletType; 144 | } 145 | 146 | @JsonIgnore 147 | public Boolean isMainNet() { 148 | return Network.MAINNET.equalsIgnoreCase(network); 149 | } 150 | 151 | public String getNetwork() { 152 | return network; 153 | } 154 | 155 | public void setNetwork(String network) { 156 | this.network = network; 157 | } 158 | 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/model/MnemonicAndPath.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.model; 2 | 3 | public class MnemonicAndPath { 4 | public String getMnemonic() { 5 | return mnemonic; 6 | } 7 | 8 | public String getPath() { 9 | return path; 10 | } 11 | 12 | private final String mnemonic; 13 | private final String path; 14 | 15 | public MnemonicAndPath(String mnemonic, String path) { 16 | this.path = path; 17 | this.mnemonic = mnemonic; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/model/MultiTo.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.model; 2 | 3 | /** 4 | * 5 | * Created by pie on 2020/8/9 18: 37. 6 | */ 7 | public class MultiTo{ 8 | 9 | public MultiTo(String to, long amount) { 10 | this.to = to; 11 | this.amount = amount; 12 | } 13 | 14 | private String to; 15 | 16 | private long amount; 17 | 18 | public String getTo() { 19 | return to; 20 | } 21 | 22 | public void setTo(String to) { 23 | this.to = to; 24 | } 25 | 26 | public long getAmount() { 27 | return amount; 28 | } 29 | 30 | public void setAmount(long amount) { 31 | this.amount = amount; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/model/Network.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.model; 2 | 3 | /** 4 | * Created by xyz on 2018/3/7. 5 | */ 6 | 7 | public class Network { 8 | public static final String MAINNET = "MAINNET"; 9 | public static final String TESTNET = "TESTNET"; 10 | public static final String KOVAN = "KOVAN"; 11 | public static final String ROPSTEN = "ROPSTEN"; 12 | 13 | private String network; 14 | 15 | public Network(String network) { 16 | this.network = network; 17 | } 18 | 19 | public boolean isMainnet() { 20 | return MAINNET.equalsIgnoreCase(this.network); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/model/TokenException.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.model; 2 | 3 | public class TokenException extends RuntimeException { 4 | private static final long serialVersionUID = 4300404932829403534L; 5 | 6 | public TokenException(String message) { 7 | super(message); 8 | } 9 | 10 | public TokenException(String message, Exception e) { 11 | super(message, e); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/network/BitcoinCashMainNetParams.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.network; 2 | 3 | import com.google.common.base.Preconditions; 4 | import java.net.URI; 5 | import org.bitcoinj.core.ECKey; 6 | import org.bitcoinj.core.Sha256Hash; 7 | import org.bitcoinj.core.Utils; 8 | import org.bitcoinj.net.discovery.HttpDiscovery; 9 | import org.bitcoinj.params.AbstractBitcoinNetParams; 10 | 11 | /** 12 | * 13 | * Created by pie on 2020/7/26 22: 57. 14 | */ 15 | public class BitcoinCashMainNetParams extends AbstractBitcoinNetParams { 16 | private static BitcoinCashMainNetParams instance; 17 | 18 | private BitcoinCashMainNetParams() { 19 | this.interval = 2016; 20 | this.targetTimespan = 1209600; 21 | this.maxTarget = Utils.decodeCompactBits(486604799L); 22 | this.dumpedPrivateKeyHeader = 128; 23 | this.addressHeader = 0; 24 | this.p2shHeader = 5; 25 | this.acceptableAddressCodes = new int[]{this.addressHeader, this.p2shHeader}; 26 | this.port = 8333; 27 | this.packetMagic = 4190024921L; 28 | this.bip32HeaderPub = 76067358; 29 | this.bip32HeaderPriv = 76066276; 30 | this.majorityEnforceBlockUpgrade = 750; 31 | this.majorityRejectBlockOutdated = 950; 32 | this.majorityWindow = 1000; 33 | this.genesisBlock.setDifficultyTarget(486604799L); 34 | this.genesisBlock.setTime(1231006505L); 35 | this.genesisBlock.setNonce(2083236893L); 36 | this.id = "org.bitcoin.production"; 37 | this.subsidyDecreaseBlockCount = 210000; 38 | this.spendableCoinbaseDepth = 100; 39 | String genesisHash = this.genesisBlock.getHashAsString(); 40 | Preconditions.checkState(genesisHash.equals("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), genesisHash); 41 | this.checkpoints.put(91722, Sha256Hash.wrap("00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")); 42 | this.checkpoints.put(91812, Sha256Hash.wrap("00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f")); 43 | this.checkpoints.put(91842, Sha256Hash.wrap("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")); 44 | this.checkpoints.put(91880, Sha256Hash.wrap("00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")); 45 | this.checkpoints.put(200000, Sha256Hash.wrap("000000000000034a7dedef4a161fa058a2d67a173a90155f3a2fe6fc132e0ebf")); 46 | this.dnsSeeds = new String[]{"seed.bitcoin.sipa.be", "dnsseed.bluematt.me", "dnsseed.bitcoin.dashjr.org", "seed.bitcoinstats.com", "seed.bitnodes.io", "bitseed.xf2.org", "seed.bitcoin.jonasschnelli.ch", "bitcoin.bloqseeds.net"}; 47 | this.httpSeeds = new HttpDiscovery.Details[]{new HttpDiscovery.Details(ECKey.fromPublicOnly(Utils.HEX.decode("0238746c59d46d5408bf8b1d0af5740fe1a6e1703fcb56b2953f0b965c740d256f")), URI.create("http://httpseed.bitcoin.schildbach.de/peers"))}; 48 | this.addrSeeds = new int[]{500895794, 1648545344, 1389798469, 769107013, -1974219449, 1931884368, -635190697, -321589929, 172026969, 1977409888, 2113365905, -1992881235, -1527638867, 1987510446, 1670054936, -2037772264, -845204690, 19490110, -862790594, -1771628992, 1447591488, 1533473856, -1311760575, -237215679, -1043256510, -2034625981, 1810161475, 1833758531, -779221436, 447787844, 1703191365, -406739899, 1489937477, -975526843, 568173637, 1027298118, 333796167, 276877898, -1906237877, -2125361589, 1820565067, -491976116, -71562676, 2136698444, 1917067854, 318788942, 543753810, 1514714706, 1975895890, 1127599188, 873451860, -2017430443, 142289750, -1978451369, -1406942633, 1882671449, -77343653, -1180604324, 403535452, 1733256285, 80955742, 501500766, 1323869026, 233642851, -1045410963, -1919806079, -1749554558, 486029966, -1857483859, 165442990, -1704101458, -1569883730, 265326510, 255737778, 1335119794, -861648712, 1053649592, -1445616196, -1929307711, 72980437, -905863719, 223505485, -1410221500, 1722919765, 291894370, 1054329773, -1550864786, 1003670445, -747698873, 551516224, 2050193218, 1419742795, -1872611748, -744884894, 726824097, -1897946797, 1006613319, 1886699352, -1219507283, 1532655963, 49949134, 1516714196, -392048066, -275200955, 1259293004, -1564729770, 1989455203, -1532493629, 100509389, -1625155746, 910850395, 1398965717, -1404778028, 184952141, 353120212, 849677772, -42348984, -245564845, 1723866720, 1807902305, -1069856850, 1306139521, 847619265, -2009290387, 903219788, -622815420, 774983009, -2065545265, 1820411218, 974964312, -1730612350, -444282021, 367550551, 2063683662, -665658040, 1446522710, 173929556, -1596879592, 780775752, -1482944180, -874721196, 1428405315, -1451022784, -1979293372, 949273532, -1418281128, 1778485953, -21016765, 791070296, 823144270, -2048316971, 706484062, -60019503, 665865069, -553499056, 370624696, 683566464, 1472095616, 485386106, -1435781310, -1705798069, 173681997, 607438424, -1706471593, 515575368, -166676387, 125751108, 139536572, 1537309208, 667556674, -123729066, -53165481, -818953890, -661135278, 1286447183, 792065749, 942467273, -450652085, 1066690630, 1623768643, 1104831063, -569646782, -1366666165, -269434284, 504330587, -1364031344, -904483623, 508026328, -1936963543, -763508921, -717547933, 1083617635, -1668425384, -887949216, -1636224932, 1086432325, 1251001020, 742016589, 656929340, 816860013, 1493322936, -1601050963, 833637229, -1029880154, 1670598578, -652063298, -1621552936, 2136148587, 1965989013, 1699034440, -1534804410, -832955559, 1896744728, 1184972056, -626044136, -995557549, -2018792878, -1218130095, -1308467894, 1832197697, -1070218434, -1540177566, 1643066542, -781197892, 141617345, 812705985, -1670256447, -1533467805, -2052983197, 1099228505, 416902872, 397990869, 1625804056, -437383092, -1424511902, 511208987, 1562621794, -231174226, -2010709152, -1638187648, 2107971762, -1251919035, 450109246, -716143286, -2045253207, -1839521124, -219601598, 1202652484, 1941284931, -1179148115, 61294800, 175110242, 1721032546, 1006291272, -1911976113, 1953044163, 49516111, 1086001731, 1783943522, 1611153739, -1525450420, 1363845955, 1668814663, -608798373, -318353320, 1121078086, 353925452, -1691937151, 1944387501, 106199119, 1133365573, 1679312550, 756746564, -1800040120, -1925802664, -1515620766, -1955570878, -829291346, -1923595184, 771102037, -289408701, 779483981, 1056504919, -1725885602, -1909390290, -1246778045, -1980961327, -1661157305, -1882386612, -1102449235, 342527301, -1738106184, -1730603170, -777460627, -2087044424, 1084227948, 1831322199, -1753219155, 2074762589, 303283779, -1318591063, 1678764234, -1130453419, 260180556, 252276825, 1772918140, 913146963, -1313200060, -2007502719, 1890958947, -985316021, -1355358511, -1247498158, 569402065, 1923844530, 1956926232, -2095913284, -1189702049, -1968715688, -25231443}; 49 | 50 | } 51 | 52 | public static synchronized BitcoinCashMainNetParams get() { 53 | if (instance == null) { 54 | instance = new BitcoinCashMainNetParams(); 55 | } 56 | 57 | return instance; 58 | } 59 | 60 | @Override 61 | public String getPaymentProtocolId() { 62 | return "main"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/network/BitcoinSvMainNetParams.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.network; 2 | 3 | import com.google.common.base.Preconditions; 4 | import java.net.URI; 5 | import org.bitcoinj.core.ECKey; 6 | import org.bitcoinj.core.Sha256Hash; 7 | import org.bitcoinj.core.Utils; 8 | import org.bitcoinj.net.discovery.HttpDiscovery; 9 | import org.bitcoinj.params.AbstractBitcoinNetParams; 10 | 11 | /** 12 | * 13 | * Created by pie on 2020/7/26 22: 58. 14 | */ 15 | public class BitcoinSvMainNetParams extends AbstractBitcoinNetParams { 16 | private static BitcoinSvMainNetParams instance; 17 | 18 | private BitcoinSvMainNetParams() { 19 | this.interval = 2016; 20 | this.targetTimespan = 1209600; 21 | this.maxTarget = Utils.decodeCompactBits(486604799L); 22 | this.dumpedPrivateKeyHeader = 128; 23 | this.addressHeader = 0; 24 | this.p2shHeader = 5; 25 | this.acceptableAddressCodes = new int[]{this.addressHeader, this.p2shHeader}; 26 | this.port = 8333; 27 | this.packetMagic = 4190024921L; 28 | this.bip32HeaderPub = 76067358; 29 | this.bip32HeaderPriv = 76066276; 30 | this.majorityEnforceBlockUpgrade = 750; 31 | this.majorityRejectBlockOutdated = 950; 32 | this.majorityWindow = 1000; 33 | this.genesisBlock.setDifficultyTarget(486604799L); 34 | this.genesisBlock.setTime(1231006505L); 35 | this.genesisBlock.setNonce(2083236893L); 36 | this.id = "org.bitcoin.production"; 37 | this.subsidyDecreaseBlockCount = 210000; 38 | this.spendableCoinbaseDepth = 100; 39 | String genesisHash = this.genesisBlock.getHashAsString(); 40 | Preconditions.checkState(genesisHash.equals("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), genesisHash); 41 | this.checkpoints.put(91722, Sha256Hash.wrap("00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")); 42 | this.checkpoints.put(91812, Sha256Hash.wrap("00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f")); 43 | this.checkpoints.put(91842, Sha256Hash.wrap("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")); 44 | this.checkpoints.put(91880, Sha256Hash.wrap("00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")); 45 | this.checkpoints.put(200000, Sha256Hash.wrap("000000000000034a7dedef4a161fa058a2d67a173a90155f3a2fe6fc132e0ebf")); 46 | this.dnsSeeds = new String[]{"seed.bitcoin.sipa.be", "dnsseed.bluematt.me", "dnsseed.bitcoin.dashjr.org", "seed.bitcoinstats.com", "seed.bitnodes.io", "bitseed.xf2.org", "seed.bitcoin.jonasschnelli.ch", "bitcoin.bloqseeds.net"}; 47 | this.httpSeeds = new HttpDiscovery.Details[]{new HttpDiscovery.Details(ECKey.fromPublicOnly(Utils.HEX.decode("0238746c59d46d5408bf8b1d0af5740fe1a6e1703fcb56b2953f0b965c740d256f")), URI.create("http://httpseed.bitcoin.schildbach.de/peers"))}; 48 | this.addrSeeds = new int[]{500895794, 1648545344, 1389798469, 769107013, -1974219449, 1931884368, -635190697, -321589929, 172026969, 1977409888, 2113365905, -1992881235, -1527638867, 1987510446, 1670054936, -2037772264, -845204690, 19490110, -862790594, -1771628992, 1447591488, 1533473856, -1311760575, -237215679, -1043256510, -2034625981, 1810161475, 1833758531, -779221436, 447787844, 1703191365, -406739899, 1489937477, -975526843, 568173637, 1027298118, 333796167, 276877898, -1906237877, -2125361589, 1820565067, -491976116, -71562676, 2136698444, 1917067854, 318788942, 543753810, 1514714706, 1975895890, 1127599188, 873451860, -2017430443, 142289750, -1978451369, -1406942633, 1882671449, -77343653, -1180604324, 403535452, 1733256285, 80955742, 501500766, 1323869026, 233642851, -1045410963, -1919806079, -1749554558, 486029966, -1857483859, 165442990, -1704101458, -1569883730, 265326510, 255737778, 1335119794, -861648712, 1053649592, -1445616196, -1929307711, 72980437, -905863719, 223505485, -1410221500, 1722919765, 291894370, 1054329773, -1550864786, 1003670445, -747698873, 551516224, 2050193218, 1419742795, -1872611748, -744884894, 726824097, -1897946797, 1006613319, 1886699352, -1219507283, 1532655963, 49949134, 1516714196, -392048066, -275200955, 1259293004, -1564729770, 1989455203, -1532493629, 100509389, -1625155746, 910850395, 1398965717, -1404778028, 184952141, 353120212, 849677772, -42348984, -245564845, 1723866720, 1807902305, -1069856850, 1306139521, 847619265, -2009290387, 903219788, -622815420, 774983009, -2065545265, 1820411218, 974964312, -1730612350, -444282021, 367550551, 2063683662, -665658040, 1446522710, 173929556, -1596879592, 780775752, -1482944180, -874721196, 1428405315, -1451022784, -1979293372, 949273532, -1418281128, 1778485953, -21016765, 791070296, 823144270, -2048316971, 706484062, -60019503, 665865069, -553499056, 370624696, 683566464, 1472095616, 485386106, -1435781310, -1705798069, 173681997, 607438424, -1706471593, 515575368, -166676387, 125751108, 139536572, 1537309208, 667556674, -123729066, -53165481, -818953890, -661135278, 1286447183, 792065749, 942467273, -450652085, 1066690630, 1623768643, 1104831063, -569646782, -1366666165, -269434284, 504330587, -1364031344, -904483623, 508026328, -1936963543, -763508921, -717547933, 1083617635, -1668425384, -887949216, -1636224932, 1086432325, 1251001020, 742016589, 656929340, 816860013, 1493322936, -1601050963, 833637229, -1029880154, 1670598578, -652063298, -1621552936, 2136148587, 1965989013, 1699034440, -1534804410, -832955559, 1896744728, 1184972056, -626044136, -995557549, -2018792878, -1218130095, -1308467894, 1832197697, -1070218434, -1540177566, 1643066542, -781197892, 141617345, 812705985, -1670256447, -1533467805, -2052983197, 1099228505, 416902872, 397990869, 1625804056, -437383092, -1424511902, 511208987, 1562621794, -231174226, -2010709152, -1638187648, 2107971762, -1251919035, 450109246, -716143286, -2045253207, -1839521124, -219601598, 1202652484, 1941284931, -1179148115, 61294800, 175110242, 1721032546, 1006291272, -1911976113, 1953044163, 49516111, 1086001731, 1783943522, 1611153739, -1525450420, 1363845955, 1668814663, -608798373, -318353320, 1121078086, 353925452, -1691937151, 1944387501, 106199119, 1133365573, 1679312550, 756746564, -1800040120, -1925802664, -1515620766, -1955570878, -829291346, -1923595184, 771102037, -289408701, 779483981, 1056504919, -1725885602, -1909390290, -1246778045, -1980961327, -1661157305, -1882386612, -1102449235, 342527301, -1738106184, -1730603170, -777460627, -2087044424, 1084227948, 1831322199, -1753219155, 2074762589, 303283779, -1318591063, 1678764234, -1130453419, 260180556, 252276825, 1772918140, 913146963, -1313200060, -2007502719, 1890958947, -985316021, -1355358511, -1247498158, 569402065, 1923844530, 1956926232, -2095913284, -1189702049, -1968715688, -25231443}; 49 | 50 | } 51 | 52 | public static synchronized BitcoinSvMainNetParams get() { 53 | if (instance == null) { 54 | instance = new BitcoinSvMainNetParams(); 55 | } 56 | 57 | return instance; 58 | } 59 | 60 | @Override 61 | public String getPaymentProtocolId() { 62 | return "main"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/network/DashMainNetParams.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.network; 2 | 3 | import com.google.common.base.Preconditions; 4 | import java.net.URI; 5 | import org.bitcoinj.core.ECKey; 6 | import org.bitcoinj.core.Sha256Hash; 7 | import org.bitcoinj.core.Utils; 8 | import org.bitcoinj.net.discovery.HttpDiscovery; 9 | import org.bitcoinj.params.AbstractBitcoinNetParams; 10 | 11 | /** 12 | * Created by pie on 2019-04-25 10: 40. 13 | */ 14 | public class DashMainNetParams extends AbstractBitcoinNetParams { 15 | 16 | private static DashMainNetParams instance; 17 | 18 | private DashMainNetParams() { 19 | this.interval = 2016; 20 | this.targetTimespan = 1209600; 21 | this.maxTarget = Utils.decodeCompactBits(486604799L); 22 | this.dumpedPrivateKeyHeader = 204; 23 | this.addressHeader = 76; 24 | this.p2shHeader = 16; 25 | this.acceptableAddressCodes = new int[]{this.addressHeader, this.p2shHeader}; 26 | this.port = 8333; 27 | this.packetMagic = 4190024921L; 28 | this.bip32HeaderPub = 76067358; 29 | this.bip32HeaderPriv = 76066276; 30 | this.majorityEnforceBlockUpgrade = 750; 31 | this.majorityRejectBlockOutdated = 950; 32 | this.majorityWindow = 1000; 33 | this.genesisBlock.setDifficultyTarget(486604799L); 34 | this.genesisBlock.setTime(1231006505L); 35 | this.genesisBlock.setNonce(2083236893L); 36 | this.id = "org.bitcoin.production"; 37 | this.subsidyDecreaseBlockCount = 210000; 38 | this.spendableCoinbaseDepth = 100; 39 | String genesisHash = this.genesisBlock.getHashAsString(); 40 | Preconditions.checkState(genesisHash.equals("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), genesisHash); 41 | this.checkpoints.put(91722, Sha256Hash.wrap("00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")); 42 | this.checkpoints.put(91812, Sha256Hash.wrap("00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f")); 43 | this.checkpoints.put(91842, Sha256Hash.wrap("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")); 44 | this.checkpoints.put(91880, Sha256Hash.wrap("00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")); 45 | this.checkpoints.put(200000, Sha256Hash.wrap("000000000000034a7dedef4a161fa058a2d67a173a90155f3a2fe6fc132e0ebf")); 46 | this.dnsSeeds = new String[]{"seed.bitcoin.sipa.be", "dnsseed.bluematt.me", "dnsseed.bitcoin.dashjr.org", "seed.bitcoinstats.com", "seed.bitnodes.io", "bitseed.xf2.org", "seed.bitcoin.jonasschnelli.ch", "bitcoin.bloqseeds.net"}; 47 | this.httpSeeds = new HttpDiscovery.Details[]{new HttpDiscovery.Details(ECKey.fromPublicOnly(Utils.HEX.decode("0238746c59d46d5408bf8b1d0af5740fe1a6e1703fcb56b2953f0b965c740d256f")), URI.create("http://httpseed.bitcoin.schildbach.de/peers"))}; 48 | this.addrSeeds = new int[]{500895794, 1648545344, 1389798469, 769107013, -1974219449, 1931884368, -635190697, -321589929, 172026969, 1977409888, 2113365905, -1992881235, -1527638867, 1987510446, 1670054936, -2037772264, -845204690, 19490110, -862790594, -1771628992, 1447591488, 1533473856, -1311760575, -237215679, -1043256510, -2034625981, 1810161475, 1833758531, -779221436, 447787844, 1703191365, -406739899, 1489937477, -975526843, 568173637, 1027298118, 333796167, 276877898, -1906237877, -2125361589, 1820565067, -491976116, -71562676, 2136698444, 1917067854, 318788942, 543753810, 1514714706, 1975895890, 1127599188, 873451860, -2017430443, 142289750, -1978451369, -1406942633, 1882671449, -77343653, -1180604324, 403535452, 1733256285, 80955742, 501500766, 1323869026, 233642851, -1045410963, -1919806079, -1749554558, 486029966, -1857483859, 165442990, -1704101458, -1569883730, 265326510, 255737778, 1335119794, -861648712, 1053649592, -1445616196, -1929307711, 72980437, -905863719, 223505485, -1410221500, 1722919765, 291894370, 1054329773, -1550864786, 1003670445, -747698873, 551516224, 2050193218, 1419742795, -1872611748, -744884894, 726824097, -1897946797, 1006613319, 1886699352, -1219507283, 1532655963, 49949134, 1516714196, -392048066, -275200955, 1259293004, -1564729770, 1989455203, -1532493629, 100509389, -1625155746, 910850395, 1398965717, -1404778028, 184952141, 353120212, 849677772, -42348984, -245564845, 1723866720, 1807902305, -1069856850, 1306139521, 847619265, -2009290387, 903219788, -622815420, 774983009, -2065545265, 1820411218, 974964312, -1730612350, -444282021, 367550551, 2063683662, -665658040, 1446522710, 173929556, -1596879592, 780775752, -1482944180, -874721196, 1428405315, -1451022784, -1979293372, 949273532, -1418281128, 1778485953, -21016765, 791070296, 823144270, -2048316971, 706484062, -60019503, 665865069, -553499056, 370624696, 683566464, 1472095616, 485386106, -1435781310, -1705798069, 173681997, 607438424, -1706471593, 515575368, -166676387, 125751108, 139536572, 1537309208, 667556674, -123729066, -53165481, -818953890, -661135278, 1286447183, 792065749, 942467273, -450652085, 1066690630, 1623768643, 1104831063, -569646782, -1366666165, -269434284, 504330587, -1364031344, -904483623, 508026328, -1936963543, -763508921, -717547933, 1083617635, -1668425384, -887949216, -1636224932, 1086432325, 1251001020, 742016589, 656929340, 816860013, 1493322936, -1601050963, 833637229, -1029880154, 1670598578, -652063298, -1621552936, 2136148587, 1965989013, 1699034440, -1534804410, -832955559, 1896744728, 1184972056, -626044136, -995557549, -2018792878, -1218130095, -1308467894, 1832197697, -1070218434, -1540177566, 1643066542, -781197892, 141617345, 812705985, -1670256447, -1533467805, -2052983197, 1099228505, 416902872, 397990869, 1625804056, -437383092, -1424511902, 511208987, 1562621794, -231174226, -2010709152, -1638187648, 2107971762, -1251919035, 450109246, -716143286, -2045253207, -1839521124, -219601598, 1202652484, 1941284931, -1179148115, 61294800, 175110242, 1721032546, 1006291272, -1911976113, 1953044163, 49516111, 1086001731, 1783943522, 1611153739, -1525450420, 1363845955, 1668814663, -608798373, -318353320, 1121078086, 353925452, -1691937151, 1944387501, 106199119, 1133365573, 1679312550, 756746564, -1800040120, -1925802664, -1515620766, -1955570878, -829291346, -1923595184, 771102037, -289408701, 779483981, 1056504919, -1725885602, -1909390290, -1246778045, -1980961327, -1661157305, -1882386612, -1102449235, 342527301, -1738106184, -1730603170, -777460627, -2087044424, 1084227948, 1831322199, -1753219155, 2074762589, 303283779, -1318591063, 1678764234, -1130453419, 260180556, 252276825, 1772918140, 913146963, -1313200060, -2007502719, 1890958947, -985316021, -1355358511, -1247498158, 569402065, 1923844530, 1956926232, -2095913284, -1189702049, -1968715688, -25231443}; 49 | 50 | } 51 | 52 | public static synchronized DashMainNetParams get() { 53 | if (instance == null) { 54 | instance = new DashMainNetParams(); 55 | } 56 | 57 | return instance; 58 | } 59 | 60 | @Override 61 | public String getPaymentProtocolId() { 62 | return "main"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/network/DogecoinMainNetParams.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.network; 2 | 3 | import com.google.common.base.Preconditions; 4 | import java.net.URI; 5 | import org.bitcoinj.core.ECKey; 6 | import org.bitcoinj.core.Sha256Hash; 7 | import org.bitcoinj.core.Utils; 8 | import org.bitcoinj.net.discovery.HttpDiscovery; 9 | import org.bitcoinj.params.AbstractBitcoinNetParams; 10 | 11 | /** 12 | * 13 | * Created by pie on 2020/7/26 23: 02. 14 | */ 15 | public class DogecoinMainNetParams extends AbstractBitcoinNetParams { 16 | private static DogecoinMainNetParams instance; 17 | 18 | private DogecoinMainNetParams() { 19 | this.interval = 2016; 20 | this.targetTimespan = 1209600; 21 | this.maxTarget = Utils.decodeCompactBits(486604799L); 22 | this.dumpedPrivateKeyHeader = 158; 23 | this.addressHeader = 30; 24 | this.p2shHeader = 22; 25 | this.acceptableAddressCodes = new int[]{this.addressHeader, this.p2shHeader}; 26 | this.port = 8333; 27 | this.packetMagic = 4190024921L; 28 | this.bip32HeaderPub = 76067358; 29 | this.bip32HeaderPriv = 76066276; 30 | this.majorityEnforceBlockUpgrade = 750; 31 | this.majorityRejectBlockOutdated = 950; 32 | this.majorityWindow = 1000; 33 | this.genesisBlock.setDifficultyTarget(486604799L); 34 | this.genesisBlock.setTime(1231006505L); 35 | this.genesisBlock.setNonce(2083236893L); 36 | this.id = "org.bitcoin.production"; 37 | this.subsidyDecreaseBlockCount = 210000; 38 | this.spendableCoinbaseDepth = 100; 39 | String genesisHash = this.genesisBlock.getHashAsString(); 40 | Preconditions.checkState(genesisHash.equals("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), genesisHash); 41 | this.checkpoints.put(91722, Sha256Hash.wrap("00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")); 42 | this.checkpoints.put(91812, Sha256Hash.wrap("00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f")); 43 | this.checkpoints.put(91842, Sha256Hash.wrap("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")); 44 | this.checkpoints.put(91880, Sha256Hash.wrap("00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")); 45 | this.checkpoints.put(200000, Sha256Hash.wrap("000000000000034a7dedef4a161fa058a2d67a173a90155f3a2fe6fc132e0ebf")); 46 | this.dnsSeeds = new String[]{"seed.bitcoin.sipa.be", "dnsseed.bluematt.me", "dnsseed.bitcoin.dashjr.org", "seed.bitcoinstats.com", "seed.bitnodes.io", "bitseed.xf2.org", "seed.bitcoin.jonasschnelli.ch", "bitcoin.bloqseeds.net"}; 47 | this.httpSeeds = new HttpDiscovery.Details[]{new HttpDiscovery.Details(ECKey.fromPublicOnly(Utils.HEX.decode("0238746c59d46d5408bf8b1d0af5740fe1a6e1703fcb56b2953f0b965c740d256f")), URI.create("http://httpseed.bitcoin.schildbach.de/peers"))}; 48 | this.addrSeeds = new int[]{500895794, 1648545344, 1389798469, 769107013, -1974219449, 1931884368, -635190697, -321589929, 172026969, 1977409888, 2113365905, -1992881235, -1527638867, 1987510446, 1670054936, -2037772264, -845204690, 19490110, -862790594, -1771628992, 1447591488, 1533473856, -1311760575, -237215679, -1043256510, -2034625981, 1810161475, 1833758531, -779221436, 447787844, 1703191365, -406739899, 1489937477, -975526843, 568173637, 1027298118, 333796167, 276877898, -1906237877, -2125361589, 1820565067, -491976116, -71562676, 2136698444, 1917067854, 318788942, 543753810, 1514714706, 1975895890, 1127599188, 873451860, -2017430443, 142289750, -1978451369, -1406942633, 1882671449, -77343653, -1180604324, 403535452, 1733256285, 80955742, 501500766, 1323869026, 233642851, -1045410963, -1919806079, -1749554558, 486029966, -1857483859, 165442990, -1704101458, -1569883730, 265326510, 255737778, 1335119794, -861648712, 1053649592, -1445616196, -1929307711, 72980437, -905863719, 223505485, -1410221500, 1722919765, 291894370, 1054329773, -1550864786, 1003670445, -747698873, 551516224, 2050193218, 1419742795, -1872611748, -744884894, 726824097, -1897946797, 1006613319, 1886699352, -1219507283, 1532655963, 49949134, 1516714196, -392048066, -275200955, 1259293004, -1564729770, 1989455203, -1532493629, 100509389, -1625155746, 910850395, 1398965717, -1404778028, 184952141, 353120212, 849677772, -42348984, -245564845, 1723866720, 1807902305, -1069856850, 1306139521, 847619265, -2009290387, 903219788, -622815420, 774983009, -2065545265, 1820411218, 974964312, -1730612350, -444282021, 367550551, 2063683662, -665658040, 1446522710, 173929556, -1596879592, 780775752, -1482944180, -874721196, 1428405315, -1451022784, -1979293372, 949273532, -1418281128, 1778485953, -21016765, 791070296, 823144270, -2048316971, 706484062, -60019503, 665865069, -553499056, 370624696, 683566464, 1472095616, 485386106, -1435781310, -1705798069, 173681997, 607438424, -1706471593, 515575368, -166676387, 125751108, 139536572, 1537309208, 667556674, -123729066, -53165481, -818953890, -661135278, 1286447183, 792065749, 942467273, -450652085, 1066690630, 1623768643, 1104831063, -569646782, -1366666165, -269434284, 504330587, -1364031344, -904483623, 508026328, -1936963543, -763508921, -717547933, 1083617635, -1668425384, -887949216, -1636224932, 1086432325, 1251001020, 742016589, 656929340, 816860013, 1493322936, -1601050963, 833637229, -1029880154, 1670598578, -652063298, -1621552936, 2136148587, 1965989013, 1699034440, -1534804410, -832955559, 1896744728, 1184972056, -626044136, -995557549, -2018792878, -1218130095, -1308467894, 1832197697, -1070218434, -1540177566, 1643066542, -781197892, 141617345, 812705985, -1670256447, -1533467805, -2052983197, 1099228505, 416902872, 397990869, 1625804056, -437383092, -1424511902, 511208987, 1562621794, -231174226, -2010709152, -1638187648, 2107971762, -1251919035, 450109246, -716143286, -2045253207, -1839521124, -219601598, 1202652484, 1941284931, -1179148115, 61294800, 175110242, 1721032546, 1006291272, -1911976113, 1953044163, 49516111, 1086001731, 1783943522, 1611153739, -1525450420, 1363845955, 1668814663, -608798373, -318353320, 1121078086, 353925452, -1691937151, 1944387501, 106199119, 1133365573, 1679312550, 756746564, -1800040120, -1925802664, -1515620766, -1955570878, -829291346, -1923595184, 771102037, -289408701, 779483981, 1056504919, -1725885602, -1909390290, -1246778045, -1980961327, -1661157305, -1882386612, -1102449235, 342527301, -1738106184, -1730603170, -777460627, -2087044424, 1084227948, 1831322199, -1753219155, 2074762589, 303283779, -1318591063, 1678764234, -1130453419, 260180556, 252276825, 1772918140, 913146963, -1313200060, -2007502719, 1890958947, -985316021, -1355358511, -1247498158, 569402065, 1923844530, 1956926232, -2095913284, -1189702049, -1968715688, -25231443}; 49 | 50 | } 51 | 52 | public static synchronized DogecoinMainNetParams get() { 53 | if (instance == null) { 54 | instance = new DogecoinMainNetParams(); 55 | } 56 | 57 | return instance; 58 | } 59 | 60 | @Override 61 | public String getPaymentProtocolId() { 62 | return "main"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/network/LitecoinMainNetParams.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.network; 2 | 3 | import com.google.common.base.Preconditions; 4 | import java.net.URI; 5 | import org.bitcoinj.core.ECKey; 6 | import org.bitcoinj.core.Sha256Hash; 7 | import org.bitcoinj.core.Utils; 8 | import org.bitcoinj.net.discovery.HttpDiscovery; 9 | import org.bitcoinj.params.AbstractBitcoinNetParams; 10 | 11 | public class LitecoinMainNetParams extends AbstractBitcoinNetParams { 12 | public static final int MAINNET_MAJORITY_WINDOW = 1000; 13 | public static final int MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED = 950; 14 | public static final int MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 750; 15 | public static final int ADDRESS_HEADER_2 = 50; 16 | 17 | private static LitecoinMainNetParams instance; 18 | 19 | private LitecoinMainNetParams() { 20 | this.interval = 2016; 21 | this.targetTimespan = 1209600; 22 | this.maxTarget = Utils.decodeCompactBits(486604799L); 23 | this.dumpedPrivateKeyHeader = 176; 24 | this.addressHeader = 48; 25 | this.p2shHeader = 5; 26 | this.acceptableAddressCodes = new int[]{this.addressHeader, this.p2shHeader, ADDRESS_HEADER_2}; 27 | this.port = 8333; 28 | this.packetMagic = 4190024921L; 29 | this.bip32HeaderPub = 76067358; 30 | this.bip32HeaderPriv = 76066276; 31 | this.majorityEnforceBlockUpgrade = 750; 32 | this.majorityRejectBlockOutdated = 950; 33 | this.majorityWindow = 1000; 34 | this.genesisBlock.setDifficultyTarget(486604799L); 35 | this.genesisBlock.setTime(1231006505L); 36 | this.genesisBlock.setNonce(2083236893L); 37 | this.id = "org.bitcoin.production"; 38 | this.subsidyDecreaseBlockCount = 210000; 39 | this.spendableCoinbaseDepth = 100; 40 | String genesisHash = this.genesisBlock.getHashAsString(); 41 | Preconditions.checkState(genesisHash.equals("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), genesisHash); 42 | this.checkpoints.put(91722, Sha256Hash.wrap("00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")); 43 | this.checkpoints.put(91812, Sha256Hash.wrap("00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f")); 44 | this.checkpoints.put(91842, Sha256Hash.wrap("00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")); 45 | this.checkpoints.put(91880, Sha256Hash.wrap("00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")); 46 | this.checkpoints.put(200000, Sha256Hash.wrap("000000000000034a7dedef4a161fa058a2d67a173a90155f3a2fe6fc132e0ebf")); 47 | this.dnsSeeds = new String[]{"seed.bitcoin.sipa.be", "dnsseed.bluematt.me", "dnsseed.bitcoin.dashjr.org", "seed.bitcoinstats.com", "seed.bitnodes.io", "bitseed.xf2.org", "seed.bitcoin.jonasschnelli.ch", "bitcoin.bloqseeds.net"}; 48 | this.httpSeeds = new HttpDiscovery.Details[]{new HttpDiscovery.Details(ECKey.fromPublicOnly(Utils.HEX.decode("0238746c59d46d5408bf8b1d0af5740fe1a6e1703fcb56b2953f0b965c740d256f")), URI.create("http://httpseed.bitcoin.schildbach.de/peers"))}; 49 | this.addrSeeds = new int[]{500895794, 1648545344, 1389798469, 769107013, -1974219449, 1931884368, -635190697, -321589929, 172026969, 1977409888, 2113365905, -1992881235, -1527638867, 1987510446, 1670054936, -2037772264, -845204690, 19490110, -862790594, -1771628992, 1447591488, 1533473856, -1311760575, -237215679, -1043256510, -2034625981, 1810161475, 1833758531, -779221436, 447787844, 1703191365, -406739899, 1489937477, -975526843, 568173637, 1027298118, 333796167, 276877898, -1906237877, -2125361589, 1820565067, -491976116, -71562676, 2136698444, 1917067854, 318788942, 543753810, 1514714706, 1975895890, 1127599188, 873451860, -2017430443, 142289750, -1978451369, -1406942633, 1882671449, -77343653, -1180604324, 403535452, 1733256285, 80955742, 501500766, 1323869026, 233642851, -1045410963, -1919806079, -1749554558, 486029966, -1857483859, 165442990, -1704101458, -1569883730, 265326510, 255737778, 1335119794, -861648712, 1053649592, -1445616196, -1929307711, 72980437, -905863719, 223505485, -1410221500, 1722919765, 291894370, 1054329773, -1550864786, 1003670445, -747698873, 551516224, 2050193218, 1419742795, -1872611748, -744884894, 726824097, -1897946797, 1006613319, 1886699352, -1219507283, 1532655963, 49949134, 1516714196, -392048066, -275200955, 1259293004, -1564729770, 1989455203, -1532493629, 100509389, -1625155746, 910850395, 1398965717, -1404778028, 184952141, 353120212, 849677772, -42348984, -245564845, 1723866720, 1807902305, -1069856850, 1306139521, 847619265, -2009290387, 903219788, -622815420, 774983009, -2065545265, 1820411218, 974964312, -1730612350, -444282021, 367550551, 2063683662, -665658040, 1446522710, 173929556, -1596879592, 780775752, -1482944180, -874721196, 1428405315, -1451022784, -1979293372, 949273532, -1418281128, 1778485953, -21016765, 791070296, 823144270, -2048316971, 706484062, -60019503, 665865069, -553499056, 370624696, 683566464, 1472095616, 485386106, -1435781310, -1705798069, 173681997, 607438424, -1706471593, 515575368, -166676387, 125751108, 139536572, 1537309208, 667556674, -123729066, -53165481, -818953890, -661135278, 1286447183, 792065749, 942467273, -450652085, 1066690630, 1623768643, 1104831063, -569646782, -1366666165, -269434284, 504330587, -1364031344, -904483623, 508026328, -1936963543, -763508921, -717547933, 1083617635, -1668425384, -887949216, -1636224932, 1086432325, 1251001020, 742016589, 656929340, 816860013, 1493322936, -1601050963, 833637229, -1029880154, 1670598578, -652063298, -1621552936, 2136148587, 1965989013, 1699034440, -1534804410, -832955559, 1896744728, 1184972056, -626044136, -995557549, -2018792878, -1218130095, -1308467894, 1832197697, -1070218434, -1540177566, 1643066542, -781197892, 141617345, 812705985, -1670256447, -1533467805, -2052983197, 1099228505, 416902872, 397990869, 1625804056, -437383092, -1424511902, 511208987, 1562621794, -231174226, -2010709152, -1638187648, 2107971762, -1251919035, 450109246, -716143286, -2045253207, -1839521124, -219601598, 1202652484, 1941284931, -1179148115, 61294800, 175110242, 1721032546, 1006291272, -1911976113, 1953044163, 49516111, 1086001731, 1783943522, 1611153739, -1525450420, 1363845955, 1668814663, -608798373, -318353320, 1121078086, 353925452, -1691937151, 1944387501, 106199119, 1133365573, 1679312550, 756746564, -1800040120, -1925802664, -1515620766, -1955570878, -829291346, -1923595184, 771102037, -289408701, 779483981, 1056504919, -1725885602, -1909390290, -1246778045, -1980961327, -1661157305, -1882386612, -1102449235, 342527301, -1738106184, -1730603170, -777460627, -2087044424, 1084227948, 1831322199, -1753219155, 2074762589, 303283779, -1318591063, 1678764234, -1130453419, 260180556, 252276825, 1772918140, 913146963, -1313200060, -2007502719, 1890958947, -985316021, -1355358511, -1247498158, 569402065, 1923844530, 1956926232, -2095913284, -1189702049, -1968715688, -25231443}; 50 | } 51 | 52 | public static synchronized LitecoinMainNetParams get() { 53 | if (instance == null) { 54 | instance = new LitecoinMainNetParams(); 55 | } 56 | 57 | return instance; 58 | } 59 | 60 | public String getPaymentProtocolId() { 61 | return "main"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/transaction/EthereumSign.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.transaction; 2 | 3 | 4 | import static com.google.common.base.Preconditions.checkState; 5 | 6 | import com.subgraph.orchid.encoders.Hex; 7 | import java.math.BigInteger; 8 | import java.nio.charset.Charset; 9 | import java.security.SignatureException; 10 | import java.util.Arrays; 11 | import java.util.Locale; 12 | import org.bitcoinj.core.ECKey; 13 | import org.bitcoinj.core.Sha256Hash; 14 | import com.ldd.foundation.crypto.Hash; 15 | import com.ldd.foundation.utils.ByteUtil; 16 | import com.ldd.foundation.utils.NumericUtil; 17 | import com.ldd.wallet.address.EthereumAddressCreator; 18 | 19 | /** 20 | * Created by xyz on 2017/12/20. 21 | */ 22 | 23 | public class EthereumSign { 24 | 25 | public static String personalSign(String data, byte[] prvKeyBytes) { 26 | byte[] dataBytes = dataToBytes(data); 27 | int msgLen = dataBytes.length; 28 | String headerMsg = String.format(Locale.ENGLISH, "\u0019Ethereum Signed Message:\n%d", msgLen); 29 | byte[] headerMsgBytes = headerMsg.getBytes(Charset.forName("UTF-8")); 30 | byte[] dataToSign = ByteUtil.concat(headerMsgBytes, dataBytes); 31 | return signMessage(dataToSign, prvKeyBytes).toString(); 32 | } 33 | 34 | public static String sign(String data, byte[] prvKeyBytes) { 35 | return signMessage(dataToBytes(data), prvKeyBytes).toString(); 36 | } 37 | 38 | public static BigInteger ecRecover(String data, String signature) throws SignatureException { 39 | byte[] msgBytes = dataToBytes(data); 40 | signature = NumericUtil.cleanHexPrefix(signature); 41 | byte[] r = Hex.decode(signature.substring(0, 64)); 42 | byte[] s = Hex.decode(signature.substring(64, 128)); 43 | int receiveId = Integer.valueOf(signature.substring(128), 16); 44 | SignatureData signatureData = new SignatureData((byte) receiveId, r, s); 45 | 46 | return signedMessageToKey(msgBytes, signatureData); 47 | } 48 | 49 | public static String recoverAddress(String data, String signature) { 50 | try { 51 | BigInteger pubKey = ecRecover(data, signature); 52 | return new EthereumAddressCreator().fromPublicKey(pubKey); 53 | } catch (SignatureException e) { 54 | return ""; 55 | } 56 | } 57 | 58 | private static byte[] dataToBytes(String data) { 59 | byte[] messageBytes; 60 | if (NumericUtil.isValidHex(data)) { 61 | messageBytes = NumericUtil.hexToBytes(data); 62 | } else { 63 | messageBytes = data.getBytes(Charset.forName("UTF-8")); 64 | } 65 | return messageBytes; 66 | } 67 | 68 | static SignatureData signMessage(byte[] message, byte[] prvKeyBytes) { 69 | ECKey ecKey = ECKey.fromPrivate(prvKeyBytes); 70 | byte[] messageHash = Hash.keccak256(message); 71 | return signAsRecoverable(messageHash, ecKey); 72 | } 73 | 74 | /** 75 | * Given an arbitrary piece of text and an Ethereum message signature encoded in bytes, 76 | * returns the public key that was used to sign it. This can then be compared to the expected 77 | * public key to determine if the signature was correct. 78 | * 79 | * @param message RLP encoded message. 80 | * @param signatureData The message signature components 81 | * @return the public key used to sign the message 82 | * @throws SignatureException If the public key could not be recovered or if there was a 83 | * signature format error. 84 | */ 85 | private static BigInteger signedMessageToKey(byte[] message, SignatureData signatureData) throws SignatureException { 86 | 87 | byte[] r = signatureData.getR(); 88 | byte[] s = signatureData.getS(); 89 | checkState(r != null && r.length == 32, "r must be 32 bytes"); 90 | checkState(s != null && s.length == 32, "s must be 32 bytes"); 91 | 92 | int header = signatureData.getV() & 0xFF; 93 | // The header byte: 0x1B = first key with even y, 0x1C = first key with odd y, 94 | // 0x1D = second key with even y, 0x1E = second key with odd y 95 | if (header < 27 || header > 34) { 96 | throw new SignatureException("Header byte out of range: " + header); 97 | } 98 | 99 | ECKey.ECDSASignature sig = new ECKey.ECDSASignature( 100 | new BigInteger(1, signatureData.getR()), 101 | new BigInteger(1, signatureData.getS())); 102 | 103 | byte[] messageHash = Hash.keccak256(message); 104 | int recId = header - 27; 105 | ECKey key = ECKey.recoverFromSignature(recId, sig, Sha256Hash.wrap(messageHash), false); 106 | if (key == null) { 107 | throw new SignatureException("Could not recover public key from signature"); 108 | } 109 | byte[] pubKeyBytes = key.getPubKeyPoint().getEncoded(false); 110 | return NumericUtil.bytesToBigInteger(Arrays.copyOfRange(pubKeyBytes, 1, pubKeyBytes.length)); 111 | } 112 | 113 | public static SignatureData signAsRecoverable(byte[] value, ECKey ecKey) { 114 | 115 | ECKey.ECDSASignature sig = ecKey.sign(Sha256Hash.wrap(value)); 116 | 117 | // Now we have to work backwards to figure out the recId needed to recover the signature. 118 | int recId = -1; 119 | for (int i = 0; i < 4; i++) { 120 | ECKey recoverKey = ECKey.recoverFromSignature(i, sig, Sha256Hash.wrap(value), false); 121 | if (recoverKey != null && recoverKey.getPubKeyPoint().equals(ecKey.getPubKeyPoint())) { 122 | recId = i; 123 | break; 124 | } 125 | } 126 | if (recId == -1) { 127 | throw new RuntimeException( 128 | "Could not construct a recoverable key. This should never happen."); 129 | } 130 | 131 | int headerByte = recId + 27; 132 | 133 | // 1 header + 32 bytes for R + 32 bytes for S 134 | byte v = (byte) headerByte; 135 | byte[] r = NumericUtil.bigIntegerToBytesWithZeroPadded(sig.r, 32); 136 | byte[] s = NumericUtil.bigIntegerToBytesWithZeroPadded(sig.s, 32); 137 | 138 | return new SignatureData(v, r, s); 139 | } 140 | 141 | 142 | } 143 | 144 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/transaction/EthereumTransaction.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.transaction; 2 | 3 | 4 | import java.math.BigInteger; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import com.ldd.foundation.crypto.Hash; 8 | import com.ldd.foundation.rlp.RlpEncoder; 9 | import com.ldd.foundation.rlp.RlpList; 10 | import com.ldd.foundation.rlp.RlpString; 11 | import com.ldd.foundation.rlp.RlpType; 12 | import com.ldd.foundation.utils.ByteUtil; 13 | import com.ldd.foundation.utils.NumericUtil; 14 | import com.ldd.wallet.Wallet; 15 | 16 | /** 17 | * Transaction class used for signing transactions locally.
18 | * For the specification, refer to p4 of the 19 | *

20 | * yellow paper. 21 | */ 22 | public class EthereumTransaction implements TransactionSigner { 23 | 24 | private BigInteger nonce; 25 | private BigInteger gasPrice; 26 | private BigInteger gasLimit; 27 | private String to; 28 | private BigInteger value; 29 | private String data; 30 | 31 | public EthereumTransaction(BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String to, 32 | BigInteger value, String data) { 33 | this.nonce = nonce; 34 | this.gasPrice = gasPrice; 35 | this.gasLimit = gasLimit; 36 | this.to = to; 37 | this.value = value; 38 | 39 | if (data != null) { 40 | this.data = NumericUtil.cleanHexPrefix(data); 41 | } 42 | } 43 | 44 | public BigInteger getNonce() { 45 | return nonce; 46 | } 47 | 48 | public BigInteger getGasPrice() { 49 | return gasPrice; 50 | } 51 | 52 | public BigInteger getGasLimit() { 53 | return gasLimit; 54 | } 55 | 56 | public String getTo() { 57 | return to; 58 | } 59 | 60 | public BigInteger getValue() { 61 | return value; 62 | } 63 | 64 | public String getData() { 65 | return data; 66 | } 67 | 68 | @Override 69 | public TxSignResult signTransaction(String chainID, String password, Wallet wallet) { 70 | 71 | String signedTx = signTransaction(Integer.parseInt(chainID), wallet.decryptMainKey(password)); 72 | String txHash = this.calcTxHash(signedTx); 73 | return new TxSignResult(signedTx, txHash); 74 | } 75 | 76 | String signTransaction(int chainId, byte[] privateKey) { 77 | SignatureData signatureData = new SignatureData(chainId, new byte[]{}, new byte[]{}); 78 | byte[] encodedTransaction = encodeToRLP(signatureData); 79 | signatureData = EthereumSign.signMessage(encodedTransaction, privateKey); 80 | 81 | SignatureData eip155SignatureData = createEip155SignatureData(signatureData, chainId); 82 | byte[] rawSignedTx = encodeToRLP(eip155SignatureData); 83 | return NumericUtil.bytesToHex(rawSignedTx); 84 | } 85 | 86 | String calcTxHash(String signedTx) { 87 | return NumericUtil.prependHexPrefix(Hash.keccak256(signedTx)); 88 | } 89 | 90 | private static SignatureData createEip155SignatureData(SignatureData signatureData, int chainId) { 91 | int v = signatureData.getV() + (chainId * 2) + 8; 92 | 93 | return new SignatureData(v, signatureData.getR(), signatureData.getS()); 94 | } 95 | 96 | byte[] encodeToRLP(SignatureData signatureData) { 97 | List values = asRlpValues(signatureData); 98 | RlpList rlpList = new RlpList(values); 99 | return RlpEncoder.encode(rlpList); 100 | } 101 | 102 | List asRlpValues(SignatureData signatureData) { 103 | List result = new ArrayList<>(); 104 | 105 | result.add(RlpString.create(getNonce())); 106 | result.add(RlpString.create(getGasPrice())); 107 | result.add(RlpString.create(getGasLimit())); 108 | 109 | // an empty to address (contract creation) should not be encoded as a numeric 0 value 110 | String to = getTo(); 111 | if (to != null && to.length() > 0) { 112 | // addresses that start with zeros should be encoded with the zeros included, not 113 | // as numeric values 114 | result.add(RlpString.create(NumericUtil.hexToBytes(to))); 115 | } else { 116 | result.add(RlpString.create("")); 117 | } 118 | 119 | result.add(RlpString.create(getValue())); 120 | 121 | // value field will already be hex encoded, so we need to convert into binary first 122 | byte[] data = NumericUtil.hexToBytes(getData()); 123 | result.add(RlpString.create(data)); 124 | 125 | if (signatureData != null && signatureData.getV() > 0) { 126 | result.add(RlpString.create(signatureData.getV())); 127 | result.add(RlpString.create(ByteUtil.trimLeadingZeroes(signatureData.getR()))); 128 | result.add(RlpString.create(ByteUtil.trimLeadingZeroes(signatureData.getS()))); 129 | } 130 | 131 | return result; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/transaction/MyHMacDSAKCalculator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.transaction; 2 | 3 | import java.math.BigInteger; 4 | import java.security.SecureRandom; 5 | import org.spongycastle.crypto.Digest; 6 | import org.spongycastle.crypto.macs.HMac; 7 | import org.spongycastle.crypto.params.KeyParameter; 8 | import org.spongycastle.crypto.signers.DSAKCalculator; 9 | import org.spongycastle.util.Arrays; 10 | import org.spongycastle.util.BigIntegers; 11 | 12 | public class MyHMacDSAKCalculator implements DSAKCalculator { 13 | private static final BigInteger ZERO = BigInteger.valueOf(0); 14 | 15 | private final HMac hMac; 16 | private final byte[] K; 17 | private final byte[] V; 18 | 19 | private BigInteger n; 20 | 21 | private boolean needTry; 22 | 23 | /** 24 | * Base constructor. 25 | * 26 | * @param digest digest to build the HMAC on. 27 | */ 28 | public MyHMacDSAKCalculator(Digest digest) { 29 | this.hMac = new HMac(digest); 30 | this.V = new byte[hMac.getMacSize()]; 31 | this.K = new byte[hMac.getMacSize()]; 32 | } 33 | 34 | public boolean isDeterministic() { 35 | return true; 36 | } 37 | 38 | public void init(BigInteger n, SecureRandom random) { 39 | throw new IllegalStateException("Operation not supported"); 40 | } 41 | 42 | public void init(BigInteger n, BigInteger d, byte[] message) { 43 | this.n = n; 44 | this.needTry = false; 45 | 46 | Arrays.fill(V, (byte) 0x01); 47 | Arrays.fill(K, (byte) 0); 48 | 49 | byte[] x = new byte[(n.bitLength() + 7) / 8]; 50 | byte[] dVal = BigIntegers.asUnsignedByteArray(d); 51 | 52 | System.arraycopy(dVal, 0, x, x.length - dVal.length, dVal.length); 53 | 54 | byte[] m = new byte[(n.bitLength() + 7) / 8]; 55 | 56 | BigInteger mInt = bitsToInt(message); 57 | 58 | if (mInt.compareTo(n) > 0) { 59 | mInt = mInt.subtract(n); 60 | } 61 | 62 | byte[] mVal = BigIntegers.asUnsignedByteArray(mInt); 63 | 64 | System.arraycopy(mVal, 0, m, m.length - mVal.length, mVal.length); 65 | 66 | hMac.init(new KeyParameter(K)); 67 | 68 | hMac.update(V, 0, V.length); 69 | hMac.update((byte) 0x00); 70 | hMac.update(x, 0, x.length); 71 | hMac.update(m, 0, m.length); 72 | 73 | hMac.doFinal(K, 0); 74 | 75 | hMac.init(new KeyParameter(K)); 76 | 77 | hMac.update(V, 0, V.length); 78 | 79 | hMac.doFinal(V, 0); 80 | 81 | hMac.update(V, 0, V.length); 82 | hMac.update((byte) 0x01); 83 | hMac.update(x, 0, x.length); 84 | hMac.update(m, 0, m.length); 85 | 86 | hMac.doFinal(K, 0); 87 | 88 | hMac.init(new KeyParameter(K)); 89 | 90 | hMac.update(V, 0, V.length); 91 | 92 | hMac.doFinal(V, 0); 93 | } 94 | 95 | public BigInteger nextK() { 96 | byte[] t = new byte[((n.bitLength() + 7) / 8)]; 97 | 98 | if (needTry) { 99 | hMac.init(new KeyParameter(K)); 100 | hMac.update(V, 0, V.length); 101 | hMac.update((byte) 0x00); 102 | 103 | hMac.doFinal(K, 0); 104 | 105 | hMac.init(new KeyParameter(K)); 106 | 107 | hMac.update(V, 0, V.length); 108 | 109 | hMac.doFinal(V, 0); 110 | } 111 | 112 | int tOff = 0; 113 | 114 | while (tOff < t.length) { 115 | hMac.init(new KeyParameter(K)); 116 | hMac.update(V, 0, V.length); 117 | 118 | hMac.doFinal(V, 0); 119 | 120 | int len = Math.min(t.length - tOff, V.length); 121 | System.arraycopy(V, 0, t, tOff, len); 122 | tOff += len; 123 | } 124 | 125 | BigInteger k = bitsToInt(t); 126 | needTry = true; 127 | return k; 128 | 129 | } 130 | 131 | private BigInteger bitsToInt(byte[] t) { 132 | BigInteger v = new BigInteger(1, t); 133 | 134 | if (t.length * 8 > n.bitLength()) { 135 | v = v.shiftRight(t.length * 8 - n.bitLength()); 136 | } 137 | 138 | return v; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/transaction/SignatureData.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.transaction; 2 | 3 | import java.util.Arrays; 4 | import com.ldd.foundation.utils.NumericUtil; 5 | 6 | /** 7 | * Created by xyz on 2018/3/2. 8 | */ 9 | public class SignatureData { 10 | private final int v; 11 | private final byte[] r; 12 | private final byte[] s; 13 | 14 | public SignatureData(int v, byte[] r, byte[] s) { 15 | this.v = v; 16 | this.r = r; 17 | this.s = s; 18 | } 19 | 20 | public int getV() { 21 | return v; 22 | } 23 | 24 | public byte[] getR() { 25 | return r; 26 | } 27 | 28 | public byte[] getS() { 29 | return s; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) { 35 | return true; 36 | } 37 | if (o == null || getClass() != o.getClass()) { 38 | return false; 39 | } 40 | 41 | SignatureData that = (SignatureData) o; 42 | 43 | if (v != that.v) { 44 | return false; 45 | } 46 | if (!Arrays.equals(r, that.r)) { 47 | return false; 48 | } 49 | return Arrays.equals(s, that.s); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | int result = v; 55 | result = 31 * result + Arrays.hashCode(r); 56 | result = 31 * result + Arrays.hashCode(s); 57 | return result; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | String r = NumericUtil.bytesToHex(getR()); 63 | String s = NumericUtil.bytesToHex(getS()); 64 | return String.format("%s%s%02x", r, s, getV()); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/transaction/TransactionSigner.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.transaction; 2 | 3 | import com.ldd.wallet.Wallet; 4 | 5 | public interface TransactionSigner { 6 | TxSignResult signTransaction(String chainId, String password, Wallet wallet); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/transaction/TxMultiSignResult.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.transaction; 2 | 3 | import java.util.List; 4 | 5 | public class TxMultiSignResult { 6 | 7 | public TxMultiSignResult(String txHash, List signed) { 8 | this.txHash = txHash; 9 | this.signed = signed; 10 | } 11 | 12 | String txHash; 13 | List signed; 14 | 15 | public String getTxHash() { 16 | return txHash; 17 | } 18 | 19 | public void setTxHash(String txHash) { 20 | this.txHash = txHash; 21 | } 22 | 23 | public List getSigned() { 24 | return signed; 25 | } 26 | 27 | public void setSigned(List signed) { 28 | this.signed = signed; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/transaction/TxSignResult.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.transaction; 2 | 3 | /** 4 | * Created by xyz on 2018/1/22. 5 | */ 6 | 7 | public class TxSignResult { 8 | private String signedTx; 9 | private String txHash; 10 | private String wtxID; 11 | 12 | public String getWtxID() { 13 | return wtxID; 14 | } 15 | 16 | public void setWtxID(String wtxID) { 17 | this.wtxID = wtxID; 18 | } 19 | 20 | public String getSignedTx() { 21 | return signedTx; 22 | } 23 | 24 | public void setSignedTx(String signedTx) { 25 | this.signedTx = signedTx; 26 | } 27 | 28 | public String getTxHash() { 29 | return txHash; 30 | } 31 | 32 | public void setTxHash(String txHash) { 33 | this.txHash = txHash; 34 | } 35 | 36 | public TxSignResult(){ 37 | 38 | } 39 | 40 | public TxSignResult(String signedTx, String txHash) { 41 | this.signedTx = signedTx; 42 | this.txHash = txHash; 43 | } 44 | 45 | public TxSignResult(String signedTx, String txHash, String wtxID) { 46 | this.signedTx = signedTx; 47 | this.txHash = txHash; 48 | this.wtxID = wtxID; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/validators/ETHAddressValidator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.validators; 2 | 3 | import static com.google.common.base.Preconditions.checkState; 4 | 5 | import com.google.common.base.Strings; 6 | import java.util.regex.Pattern; 7 | import com.ldd.foundation.crypto.Hash; 8 | import com.ldd.foundation.utils.NumericUtil; 9 | import com.ldd.wallet.model.Messages; 10 | import com.ldd.wallet.model.TokenException; 11 | 12 | /** 13 | * Created by xyz on 2018/3/12. 14 | */ 15 | 16 | public class ETHAddressValidator implements Validator { 17 | private String address; 18 | 19 | private static final Pattern ignoreCaseAddrPattern = Pattern.compile("^(0x)?[0-9a-f]{40}$", 20 | Pattern.CASE_INSENSITIVE); 21 | private static final Pattern lowerCaseAddrPattern = Pattern.compile("^(0x)?[0-9a-f]{40}$"); 22 | private static final Pattern upperCaseAddrPattern = Pattern.compile("^(0x)?[0-9A-F]{40}$"); 23 | 24 | 25 | public ETHAddressValidator(String address) { 26 | this.address = address; 27 | } 28 | 29 | @Override 30 | public Void validate() { 31 | if (!isValidAddress(this.address)) { 32 | throw new TokenException(Messages.WALLET_INVALID_ADDRESS); 33 | } 34 | return null; 35 | } 36 | 37 | 38 | private boolean isValidAddress(String address) { 39 | 40 | // if not [0-9]{40} return false 41 | if (Strings.isNullOrEmpty(address) || !ignoreCaseAddrPattern.matcher(address).find()) { 42 | return false; 43 | } else if (lowerCaseAddrPattern.matcher(address).find() || upperCaseAddrPattern.matcher(address).find()) { 44 | // if it's all small caps or caps return true 45 | return true; 46 | } else { 47 | // if it is mixed caps it is a checksum address and needs to be validated 48 | return validateChecksumAddress(address); 49 | } 50 | } 51 | 52 | private boolean validateChecksumAddress(String address) { 53 | address = NumericUtil.cleanHexPrefix(address); 54 | checkState(address.length() == 40); 55 | 56 | String lowerAddress = NumericUtil.cleanHexPrefix(address).toLowerCase(); 57 | String hash = NumericUtil.bytesToHex(Hash.keccak256(lowerAddress.getBytes())); 58 | 59 | for (int i = 0; i < 40; i++) { 60 | if (Character.isLetter(address.charAt(i))) { 61 | // each uppercase letter should correlate with a first bit of 1 in the hash 62 | // char with the same index, and each lowercase letter with a 0 bit 63 | int charInt = Integer.parseInt(Character.toString(hash.charAt(i)), 16); 64 | if ((Character.isUpperCase(address.charAt(i)) && charInt <= 7) 65 | || (Character.isLowerCase(address.charAt(i)) && charInt > 7)) { 66 | return false; 67 | } 68 | } 69 | } 70 | return true; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/validators/MetadataValidator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.validators; 2 | 3 | import static com.google.common.base.Preconditions.checkState; 4 | 5 | import com.google.common.base.Strings; 6 | import java.util.HashMap; 7 | import com.ldd.wallet.model.ChainType; 8 | import com.ldd.wallet.model.Metadata; 9 | 10 | /** 11 | * Created by xyz on 2018/4/10. 12 | */ 13 | 14 | public final class MetadataValidator implements Validator { 15 | private HashMap map; 16 | private String source; 17 | 18 | public MetadataValidator(HashMap map) { 19 | this.map = map; 20 | } 21 | 22 | public MetadataValidator(HashMap map, String source) { 23 | this.map = map; 24 | this.source = source; 25 | } 26 | 27 | @Override 28 | public Metadata validate() { 29 | String name = (String) map.get("name"); 30 | String passwordHint = (String) map.get("passwordHint"); 31 | String chainType = (String) map.get("chainType"); 32 | String network = null; 33 | String segWit = null; 34 | if (!ChainType.ETHEREUM.equalsIgnoreCase(chainType)) { 35 | if (map.containsKey("network")) { 36 | network = ((String) map.get("network")).toUpperCase(); 37 | } 38 | if (map.containsKey("segWit")) { 39 | segWit = ((String) map.get("segWit")).toUpperCase(); 40 | } 41 | } 42 | 43 | checkState(!Strings.isNullOrEmpty(name), "Can't allow empty name"); 44 | ChainType.validate(chainType); 45 | 46 | Metadata metadata = new Metadata(chainType, network, name, passwordHint); 47 | if (!Strings.isNullOrEmpty(this.source)) { 48 | metadata.setSource(this.source); 49 | } 50 | metadata.setSegWit(segWit); 51 | return metadata; 52 | } 53 | 54 | 55 | } -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/validators/PrivateKeyValidator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.validators; 2 | 3 | import java.math.BigInteger; 4 | import org.bitcoinj.core.ECKey; 5 | import com.ldd.foundation.utils.NumericUtil; 6 | import com.ldd.wallet.model.Messages; 7 | import com.ldd.wallet.model.TokenException; 8 | 9 | 10 | /** 11 | * Created by xyz on 2018/2/27. 12 | */ 13 | 14 | public final class PrivateKeyValidator implements Validator { 15 | 16 | private String privateKey; 17 | 18 | public PrivateKeyValidator(String prvKey) { 19 | this.privateKey = prvKey; 20 | } 21 | 22 | // todo: BitcoinJ provides a wrapper of NativeSecp256k1, but not provide a workable .so file 23 | // For stability, we do not compile the .so by ourselves, instead, we write some simple java code. 24 | @Override 25 | public String validate() { 26 | try { 27 | // validating private key 28 | BigInteger pkNum = NumericUtil.hexToBigInteger(privateKey); 29 | if (NumericUtil.hexToBytes(this.privateKey).length != 32 30 | || pkNum.compareTo((ECKey.CURVE.getN().subtract(BigInteger.ONE))) >= 0 31 | || pkNum.compareTo(BigInteger.ONE) <= 0) { 32 | throw new TokenException(Messages.PRIVATE_KEY_INVALID); 33 | } 34 | 35 | // validating public key 36 | byte[] pubKeyBytes = ECKey.fromPrivate(pkNum).getPubKey(); 37 | BigInteger pubKeyNum = new BigInteger(1, pubKeyBytes); 38 | if (pubKeyNum.compareTo(BigInteger.ZERO) == 0) { 39 | throw new TokenException(Messages.PRIVATE_KEY_INVALID); 40 | } 41 | } catch (Exception ex) { 42 | throw new TokenException(Messages.PRIVATE_KEY_INVALID); 43 | } 44 | return this.privateKey; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/validators/Validator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.validators; 2 | 3 | /** 4 | * Created by xyz on 2018/2/27. 5 | */ 6 | 7 | public interface Validator { 8 | T validate(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ldd/wallet/validators/WIFValidator.java: -------------------------------------------------------------------------------- 1 | package com.ldd.wallet.validators; 2 | 3 | import org.bitcoinj.core.AddressFormatException; 4 | import org.bitcoinj.core.DumpedPrivateKey; 5 | import org.bitcoinj.core.NetworkParameters; 6 | import org.bitcoinj.core.WrongNetworkException; 7 | import com.ldd.wallet.model.Messages; 8 | import com.ldd.wallet.model.TokenException; 9 | 10 | /** 11 | * Created by xyz on 2018/2/27. 12 | */ 13 | 14 | public final class WIFValidator implements Validator { 15 | 16 | String wif; 17 | NetworkParameters network; 18 | boolean requireCompressed = false; 19 | public WIFValidator(String wif, NetworkParameters network) { 20 | this.wif = wif; 21 | this.network = network; 22 | } 23 | 24 | public WIFValidator(String wif, NetworkParameters network, boolean requireCompressed) { 25 | this.wif = wif; 26 | this.network = network; 27 | this.requireCompressed = requireCompressed; 28 | } 29 | 30 | @Override 31 | public String validate() { 32 | try { 33 | DumpedPrivateKey.fromBase58(network, wif); 34 | } catch (WrongNetworkException addressException) { 35 | throw new TokenException(Messages.WIF_WRONG_NETWORK); 36 | } catch (AddressFormatException addressFormatException) { 37 | throw new TokenException(Messages.WIF_INVALID); 38 | } 39 | if (requireCompressed && !DumpedPrivateKey.fromBase58(network, wif).getKey().isCompressed()) { 40 | throw new TokenException(Messages.SEGWIT_NEEDS_COMPRESS_PUBLIC_KEY); 41 | } 42 | return this.wif; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | ${LOG_PATTERN} 11 | UTF-8 12 | 13 | 14 | 15 | 17 | 19 | 20 | log/first-token-invoker.%d{yyyy-MM-dd}.%i.log 21 | 22 | 100MB 23 | 24 | 30 25 | 26 | 27 | ${LOG_PATTERN} 28 | UTF-8 29 | 30 | 31 | 32 | 34 | 35 | ERROR 36 | ACCEPT 37 | DENY 38 | 39 | 40 | 41 | log/first-token-invoker-error.%d{yyyy-MM-dd}.log 42 | 43 | 30 44 | true 45 | 46 | 47 | ${LOG_PATTERN} 48 | UTF-8 49 | 50 | 51 | 53 | 55 | 0 56 | 57 | 512 58 | 59 | 60 | true 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/test/java/com/ldd/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.ldd; 2 | 3 | import com.ldd.wallet.Identity; 4 | import com.ldd.wallet.WalletManager; 5 | import com.ldd.wallet.keystore.IMTKeystore; 6 | import com.ldd.wallet.model.Metadata; 7 | import com.ldd.wallet.model.Network; 8 | import java.io.File; 9 | import java.nio.file.Files; 10 | import java.nio.file.Paths; 11 | import java.util.ArrayList; 12 | import java.util.Hashtable; 13 | import java.util.List; 14 | import java.util.Set; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | 18 | /** 19 | * Unit test for simple App. 20 | */ 21 | public class AppTest { 22 | 23 | private final String password = "123456"; 24 | 25 | @Before 26 | public void before() { 27 | // try { 28 | // Files.createDirectories(Paths.get("${keyStoreProperties.dir}/wallets")); 29 | // } catch (Throwable ignored) { 30 | // } 31 | //KeystoreStorage是接口,实现它的getdir方法 32 | WalletManager.storage = () -> new File("D:\\btchd"); 33 | Identity identity = Identity.getCurrentIdentity(); 34 | if (identity == null) { 35 | Identity.createIdentity( 36 | "token", 37 | password, 38 | "", 39 | Network.MAINNET, 40 | Metadata.NONE 41 | ); 42 | } 43 | WalletManager.scanWallets(); 44 | } 45 | 46 | /** 47 | * 清空当前目录所有已经导入的钱包 48 | */ 49 | @Test 50 | public void test1() { 51 | WalletManager.scanWallets(); 52 | Hashtable keyMap = WalletManager.getKeyMap(); 53 | Set strings = keyMap.keySet(); 54 | List list = new ArrayList<>(strings); 55 | for (String s : list) { 56 | WalletManager.removeWallet(s, password); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/ldd/test/a.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldd19920711/WalletBase/5f6eaaa659022fd68285c1c14b07764dd37a04ce/src/test/java/com/ldd/test/a.txt -------------------------------------------------------------------------------- /src/test/java/com/ldd/test/b.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldd19920711/WalletBase/5f6eaaa659022fd68285c1c14b07764dd37a04ce/src/test/java/com/ldd/test/b.txt -------------------------------------------------------------------------------- /src/test/java/com/ldd/trx/TrxTest.java: -------------------------------------------------------------------------------- 1 | package com.ldd.trx; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.ldd.wallet.Identity; 5 | import com.ldd.wallet.Wallet; 6 | import com.ldd.wallet.WalletManager; 7 | import com.ldd.wallet.model.BIP44Util; 8 | import com.ldd.wallet.model.ChainType; 9 | import com.ldd.wallet.model.Metadata; 10 | import com.ldd.wallet.model.Network; 11 | import java.io.File; 12 | import java.security.SecureRandom; 13 | import java.util.List; 14 | import org.bitcoinj.crypto.MnemonicCode; 15 | import org.bitcoinj.wallet.DeterministicSeed; 16 | import org.junit.Before; 17 | import org.junit.Test; 18 | 19 | /** 20 | * TRX-Wallet相关操作 21 | * 22 | * @author ldd 23 | */ 24 | public class TrxTest { 25 | 26 | private final String password = "123456"; 27 | 28 | @Before 29 | public void before() { 30 | // try { 31 | // Files.createDirectories(Paths.get("D:\btchd")); 32 | // } catch (Throwable ignored) { 33 | // } 34 | //KeystoreStorage是接口,实现它的getdir方法 35 | WalletManager.storage = () -> new File("D:\\btchd"); 36 | Identity identity = Identity.getCurrentIdentity(); 37 | if (identity == null) { 38 | Identity.createIdentity( 39 | "token", 40 | password, 41 | "", 42 | Network.MAINNET, 43 | Metadata.NONE 44 | ); 45 | } 46 | WalletManager.scanWallets(); 47 | } 48 | 49 | /** 50 | * 助记词导入钱包 51 | */ 52 | @Test 53 | public void test1() throws Exception { 54 | SecureRandom secureRandom = new SecureRandom(); 55 | byte[] entropy = new byte[DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS / 8]; 56 | secureRandom.nextBytes(entropy); 57 | //生成12位助记词 58 | List mnemonicList = MnemonicCode.INSTANCE.toMnemonic(entropy); 59 | //使用助记词生成钱包种子 60 | //可以指定助记词生成钱包 61 | String mnemonic = String.join(" ", mnemonicList); 62 | System.out.println("输出助记词: " + mnemonic); 63 | //普通地址钱包生成逻辑 64 | Metadata metadata = new Metadata(); 65 | metadata.setChainType(ChainType.TRON); 66 | metadata.setNetwork(Network.MAINNET); 67 | metadata.setSource(Metadata.FROM_MNEMONIC); 68 | Wallet wallet = WalletManager 69 | .importWalletFromMnemonic(metadata, mnemonic, BIP44Util.TRON_PATH, password, true); 70 | System.out.println(wallet.getAddress()); 71 | System.out.println(wallet.exportPrivateKey(password)); 72 | System.out.println(JSONObject.toJSONString(wallet.getKeystore())); 73 | } 74 | 75 | /** 76 | * 私钥生成钱包 77 | */ 78 | @Test 79 | public void test2() { 80 | Metadata metadata = new Metadata(); 81 | metadata.setChainType(ChainType.TRON); 82 | metadata.setNetwork(Network.MAINNET); 83 | metadata.setSource(Metadata.FROM_PRIVATE); 84 | Wallet wallet = WalletManager 85 | .importWalletFromPrivateKey(metadata, 86 | "386e00fab25879d6904605c1b3f2f93086ac1563b3699d6a22014099fbe6e8b8", password, true); 87 | System.out.println(wallet.getAddress()); 88 | System.out.println(wallet.exportPrivateKey(password)); 89 | System.out.println(JSONObject.toJSONString(wallet.getKeystore())); 90 | } 91 | 92 | /** 93 | * keystore生成钱包 94 | */ 95 | @Test 96 | public void test3() throws Exception { 97 | //region keystoreContent 98 | String keystoreContent = "{\n" 99 | + " \"address\":\"TFSBecVak43HibvAjnrMcdf3ya3xfntCVY\",\n" 100 | + " \"crypto\":{\n" 101 | + " \"cipher\":\"aes-128-ctr\",\n" 102 | + " \"cipherparams\":{\n" 103 | + " \"iv\":\"7a1e99d090c1d94a753dfb6c980cfb8f\"\n" 104 | + " },\n" 105 | + " \"ciphertext\":\"2a3dd424f23d58774ed5c35be2bdc73912d3f3b97a2aa43f2fd5ffbb3c6c7d7a\",\n" 106 | + " \"kdf\":\"pbkdf2\",\n" 107 | + " \"kdfparams\":{\n" 108 | + " \"c\":10240,\n" 109 | + " \"dklen\":32,\n" 110 | + " \"prf\":\"hmac-sha256\",\n" 111 | + " \"salt\":\"a89f551b247008ef8d790693be8bba4af3badee0a5b0fb4280fc38af562a358d\"\n" 112 | + " },\n" 113 | + " \"mac\":\"66a0bbb6b8614f36e736343d259cb95f7f0fe7e29d097faff3551808e89c6c84\"\n" 114 | + " },\n" 115 | + " \"encMnemonic\":{\n" 116 | + " \"encStr\":\"5cf7bef5eeca650dfbed6f083006135a64028b4030730297f5d0b0ea4402e844b2a9014b0df9fb4d7bf422b626e135667f2c01028428b29595397eb3cb97134d76e8154e37f75046f5bb1e9b4de69d\",\n" 117 | + " \"nonce\":\"69b0738589fead0a68a5be8b62af9e4c\"\n" 118 | + " },\n" 119 | + " \"id\":\"4785828e-20bb-4402-a785-8edee76b5e24\",\n" 120 | + " \"metadata\":{\n" 121 | + " \"backup\":[\n" 122 | + "\n" 123 | + " ],\n" 124 | + " \"chainType\":\"TRON\",\n" 125 | + " \"mainNet\":true,\n" 126 | + " \"mode\":\"NORMAL\",\n" 127 | + " \"network\":\"MAINNET\",\n" 128 | + " \"source\":\"MNEMONIC\",\n" 129 | + " \"timestamp\":1607596549,\n" 130 | + " \"walletType\":\"V3\"\n" 131 | + " },\n" 132 | + " \"mnemonicPath\":\"m/44'/195'/0'/0/0\",\n" 133 | + " \"version\":3\n" 134 | + "}"; 135 | //endregion 136 | Metadata metadata = new Metadata(); 137 | metadata.setChainType(ChainType.TRON); 138 | metadata.setNetwork(Network.MAINNET); 139 | metadata.setSource(Metadata.FROM_KEYSTORE); 140 | Wallet wallet = WalletManager 141 | .importWalletFromKeystore(metadata, keystoreContent, password, true); 142 | System.out.println(wallet.getAddress()); 143 | System.out.println(wallet.exportPrivateKey(password)); 144 | System.out.println(JSONObject.toJSONString(wallet.getKeystore())); 145 | } 146 | } 147 | --------------------------------------------------------------------------------