以一个账户举例:
路径:
m / purpose' / coin_type' / account' / change / address_index
m/44'/60'/0'/0
助记词:
lonely quote life good erode key benefit cube brass degree nurse april
私钥:
0x0c09d05b07006d4e7389ad962969eed6c429399ef6608aab92407e85f33d8fb3
公钥:
0x037549b8496362e87decad41c543e9ee868189fb191ebb4c5cd668b4b251b7b9e2
地址:
0xe1DA06fff8Ca2Dc3bE69276B47501F89112dCF96
以太坊钱包使用户用来管理以太坊上面的钱包账户。一个钱包账户有以下信息:私钥、公钥和地址。
其中,私钥的另一种形式又有Keystore的形式。
创建账户
创建账号关键是生成一个私钥, 私钥是一个32个字节的数, 生成一个私钥在本质上在1到之间选一个数字。
因此生成密钥的第一步也是最重要的一步,是要找到足够安全的熵源,即随机性来源,只要选取的结果是不可预测或不可重复的,那么选取数字的具体方法并不重要。
比如可以掷硬币256次,用纸和笔记录正反面并转换为0和1,随机得到的256位二进制数字可作为钱包的私钥。
从编程的角度来看,一般是通过在一个密码学安全的随机源(不建议大家自己去写一个随机数)中取出一长串随机字节,对其使用SHA256哈希算法进行运算,这样就可以方便地产生一个256位的数字。
形象说法
- 地址=银行卡号
- 密码=银行卡密码
- 私钥=银行卡号+银行卡密码
- 助记词=银行卡号+银行卡密码
- Keystore+密码=银行卡号+银行卡密码
备份钱包
1、既然私钥、助记词、Keystore+密码」如此重要,那么如何进行保存呢,最安全的方法就是:手抄纸上。
2、由于 Keystore 内容较多,手抄不方便,保存在电脑上也不安全,因此可以不对 Keystore 进行备份,只手抄私钥、助记词就足够了,手抄备份要注意以下几点:
(1)多抄几份,分别放在不同的安全区域,并告诉家人。
(2)对手抄内容进行验证,导入钱包看能不能成功,防止抄写错误。
(3)备份信息不要在联网设备上进行传播。
私钥
0x0c09d05b07006d4e7389ad962969eed6c429399ef6608aab92407e85f33d8fb3
16进制
长度固定:64个16进制数,32个字节
随机性,2次方种(256位二进制数、32个字节数、64个16进制数)
总之,私钥是一个32个字节的数, 生成一个私钥在本质上在1到2之间选一个数字,即64位长度的16进制字符组成
导出私钥
创建钱包后,输入密码可以导出私钥,这个私钥属于明文私钥,由 64 位字符串组成,一个钱包只有一个私钥且不能修改。
导入私钥
在导入钱包时,输入私钥并设置一个密码(不用输入原密码),就能进入钱包并拥有这个钱包的掌控权,就可以把钱包中的代币转移走。
公钥
1、长度:40
2、以太坊的公钥以0x开头
地址
1、创建钱包后会生成一个以 0x 开头的 42 位字符串,这个字符串就是钱包地址。也就是20的16进制的字符组成。
2、一个地址就是一个钱包账户,可以公开,用于转账(转入、转出)和查询账户余额。相当于你的银行卡账号。
3、地址不是公钥,但是地址是由公钥生成的,而公钥又是由私钥生辰的,这个过程是不可逆的。
钱包地址形式
以太坊,0x开头(包括基于以太坊平台代币)。形如:
0x0bac871ed2677c4b790065f20bdb20a16fdd9dd3
比特币:a. 普通地址:1开头。b. 隔离见证地址:3开头
瑞波币地址:r开头。
莱特币地址:L开头。
keystroe
keystore常见于以太坊钱包,是你独有的、用于签署交易的以太坊私钥的加密文件。
keystore是一串Json格式的字符串,可以用任何以太坊钱包打开它。keystore必须配合你的钱包密码来使用,备份了keystore同时别忘了备份钱包的密码。
用户可以使用备份的助记词,重新导入imToken之类的钱包工具,用新的密码生成一个新的Keystore,可以用这种方法来修改钱包密码
本质是加密后的私钥。
形式
{
"address": "fa1d10334277d2a8b9115c0369a6511605cd47f5",
"id": "4cea804f-db18-4820-8338-e29c9f9b6f73",
"version": 3,
"crypto": {
"cipher": "aes-128-ctr",
"cipherparams": {
"iv": "9cdf057d21539030ff261d0951eefe10"
},
"ciphertext": "4fa65d6ecd41af51f958c1682a512217b09e26791b63282ba4e104e66f6876a5",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 4096,
"p": 6,
"r": 8,
"salt": "040b2d729fd672e6b2406fc56c0e1e51a3d87cc1aa45199b92863462c5f21634"
},
"mac": "dfb33788f5f336e463bfe348264d31d1db0e8fb24a35d40658db23745ccaab8c"
}
}
备份
钱包里有一个备份 keystore 功能,选择备份 keystore,输入密码,会出现一大段字符,这个就是 keystore。
用途
1、Keystore必须配合你的钱包密码来使用,如果只是备份了KS,忘记密码,这个账户也就没了……
2、直接使用以太坊钱包,很少会直接看到自己的私钥,而是让你备份Keystore, 配合钱包密码来使用。即使黑客获取了你的Keystore, 还要破解钱包密码也还是有一定难度的,当然钱包密码也别设太简单。
导入钱包
1、在导入钱包中,输入 keystore 和密码,就能进入钱包了。
2、需要说明的是,这个密码是本手机原来设置的本钱包密码, 这一点和用私钥或助记词导入钱包不一样,用私钥或助记词导入钱包,不需要知道原密码,直接重置密码。
转账
助记词
其实助记词是分层确定性钱包 (Hierarchical Deterministic, 我们也称 HD-Wallet) 特有的私钥表现形式
1、创建钱包后,会出现一个备份助记词功能,选择备份助记词,输入密码,会出现 12 个单词,每个单词之间有一个空格,这个就是助记词,一个钱包只有一个助记词且不能修改。
2、助记词是私钥的另一种表现形式,具有和私钥同样的功能,在导入钱包中,输入助记词并设置一个密码(不用输入原密码),就能进入钱包并拥有这个钱包的掌控权,就可以把钱包中的代币转移走。
3、助记词是需要配合路径使用的, 相同的助记词配合不同的路径也会生成不同的地址
备份助记词
一般来说,钱包不保留助记词信息,在创建完成后,需要备份助记词。不备份助记词的钱包,是不给用户使用的。
一旦备份助记词助记词只能备份一次,备份后,在钱包中再也不会显示,因此在备份时一定要抄写下来。
导入钱包
通过助记词导入钱包
/**
* 此方式导入到的钱包时轻钱包
*
* @param walletName
* @param mnemonicCode
* @param password
* @param callback
*/
//导入钱包--助记词---KS
public static void importWalletByMnemonicCode(String walletName, List<String> mnemonicCode, String password, OnImportMnemonicCallback callback) {
String[] pathArray = ETH_TYPE.split("/");
String passphrase = "";
long creationTimeSeconds = System.currentTimeMillis() / 1000;
DeterministicSeed ds = new DeterministicSeed(mnemonicCode, null, passphrase, creationTimeSeconds);
//种子
byte[] seedBytes = ds.getSeedBytes();
if (seedBytes == null) {
callback.onImportMnemonicFailure(ERROR_CREATE_WALLET_FAILURE);
return;
}
DeterministicKey dkKey = HDKeyDerivation.createMasterPrivateKey(seedBytes);
for (int i = 1; i < pathArray.length; i++) {
ChildNumber childNumber;
if (pathArray[i].endsWith("'")) {
int number = Integer.parseInt(pathArray[i].substring(0,
pathArray[i].length() - 1));
childNumber = new ChildNumber(number, true);
} else {
int number = Integer.parseInt(pathArray[i]);
childNumber = new ChildNumber(number, false);
}
dkKey = HDKeyDerivation.deriveChildKey(dkKey, childNumber);
}
ECKeyPair ecKeyPair = ECKeyPair.create(dkKey.getPrivKeyBytes());
WalletBeanNew walletBean = new WalletBeanNew();
// WalletFile walletFile = Wallet.create(password, ecKeyPair, 1024, 1); // WalletUtils. .generateNewWalletFile();
String privateKey = ecKeyPair.getPrivateKey().toString(16);
String publicKey = ecKeyPair.getPublicKey().toString(16);
walletBean.setPrivateKey(privateKey);
walletBean.setPublic_key(publicKey);
LoggerUtils.i("私钥: " + privateKey);
try {
String keystore = WalletUtils.generateWalletFile(password, ecKeyPair, new File(MySDK.getKeystoreDir()), false);
keystore = MySDK.getKeystoreDir() + "/" + keystore;
walletBean.setKeystore(keystore);
LoggerUtils.i("钱包keystore: " + keystore);
if (StringUtils.isEmpty(walletName)) {
walletName = generateNewWalletName();
}
walletBean.setName(walletName);
// String addr1 = walletFile.getAddress();
String addr2 = Keys.getAddress(ecKeyPair);
String walletAddress = Keys.toChecksumAddress(addr2);
walletBean.setAddress(walletAddress);//设置钱包地址
LoggerUtils.i("钱包地址: " + walletAddress);
walletBean.setPassword(password);
StringBuilder sb = new StringBuilder();
for (String mnemonic : mnemonicCode) {
sb.append(mnemonic);
sb.append(" ");
}
LoggerUtils.i("助记词 === " + sb.toString());
String mnemonicWord = sb.toString();
walletBean.setMnemonic(mnemonicWord);
walletBean.setMIconIndex(getNum(7));
callback.onImportMnemonicSuccess(walletBean);
} catch (CipherException | IOException e) {
callback.onImportMnemonicFailure(e.getMessage());
}
}
通过keystore导入钱包
/**
* 导入钱包--KS
* 此方式导入到的钱包要判断是不是以太坊标准钱包 使用{@link #loadCredentialsByP(int, String, File)}
*
* @param walletName
* @param password
* @param keystoreContent
* @param callback
*/
public static void importWalletByKeyStore(final String walletName, final String password, final KeystoreBean keystoreBean,String keystoreContent, final OnCreateWalletCallback callback) {
new Thread() {
@Override
public void run() {
super.run();
try {
if (keystoreBean == null) {
MySDK.getHandler().post(() -> callback.onCreateFailure(ERROR_KEYSTORE));
return;
}
final WalletBeanNew bean = new WalletBeanNew();
String path = MySDK.getKeystoreDir() + "/" + getWalletFileName(keystoreBean.getAddress());
File file = new File(path);
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream out = new FileOutputStream(file, false); //如果追加方式用true,此处覆盖
// StringBuffer sb = new StringBuffer();
byte[] bytes = keystoreContent.getBytes();
out.write(bytes);
// out.write(sb.toString().getBytes("utf-8"));//注意需要转换对应的字符集
out.close();
//创建钱包
int p = keystoreBean.getCrypto().getKdfparams().getP();
LoggerUtils.i("p = " + p);
Credentials credentials = loadCredentialsByP(p, password, file);
if (p == 6) {
bean.setLight(true);
} else {
bean.setLight(false);
}
ECKeyPair keyPair = credentials.getEcKeyPair();
String wallet_name = walletName;
if (wallet_name.equals("")) {
//兼容SDK
wallet_name = "new-wallet" + RandomUntil.getSmallLetter(3);
}
bean.setName(wallet_name);
bean.setPrivateKey(keyPair.getPrivateKey().toString(16));//私钥
bean.setPublic_key(keyPair.getPublicKey().toString(16));//公钥
bean.setAddress("0x" + Keys.getAddress(keyPair)); //地址
bean.setPassword(password); //密码
bean.setKeystore(path);
bean.setMIconIndex(getNum(7));
MySDK.getHandler().post(() -> callback.onCreateSuccess(bean));
} catch (IOException | CipherException | NullPointerException | OutOfMemoryError e) {
MySDK.getHandler().post(() -> {
LoggerUtils.e(e.getMessage() + " " + password);
callback.onCreateFailure(e.getMessage());
});
}
}
}.start();
}
通过私钥导入钱包
/**
* @param walletName 钱包名字
* @param privateKey 钱包私钥
* @param password 钱包密码
* @param callback 导入结果的回调
*/
//导入钱包--私钥---产生KS
public static void importPrivateKey(String walletName, final String privateKey, final String password, final OnCreateWalletCallback callback) {
try {
final WalletBeanNew wallet = new WalletBeanNew();
String name = walletName;
if (name.equals("")) {
name = "new-wallet" + RandomUntil.getSmallLetter(3);
}
BigInteger key = new BigInteger(privateKey, 16);
ECKeyPair keyPair = ECKeyPair.create(key);
String private_key = keyPair.getPrivateKey().toString(16);
wallet.setPrivateKey(private_key);
wallet.setPublic_key(keyPair.getPublicKey().toString(16));
wallet.setAddress("0x" + Keys.getAddress(keyPair));
wallet.setName(name);
wallet.setMnemonic("");
wallet.setPassword(password);
String keystore = WalletUtils.generateWalletFile(password, keyPair, new File(MySDK.getKeystoreDir()), false);
keystore = MySDK.getKeystoreDir() + "/" + keystore;
wallet.setKeystore(keystore);
wallet.setMIconIndex(getNum(7));
MySDK.getHandler().post(() -> callback.onCreateSuccess(wallet));
} catch (CipherException | IOException e) {
MySDK.getHandler().post(() -> callback.onCreateFailure(e.getMessage()));
}
}