We can create an account by wallet like MetaMask or StarMask, but I’m curious about how an account is created on the blockchain system.
As a wallet has been embedded in the starcoin node, we can use it to create an account as follow:
$ ./target/debug/starcoin -d ~/.starcoin -n dev account create -p my-pass
{
"ok": {
"address": "0x2f1aeb63bd30d8eb841d6a941c5d6df3",
"is_default": false,
"is_readonly": false,
"public_key": "0x91f79bdd9ced49332bf85b751d02339e05aff047c386d0c14b380d8519d2fb4b",
"receipt_identifier": "stc1p9udwkcaaxrvwhpqad22pchtd7vy2276p"
}
}
As above we can see our account has been created, and the address is: 0x2f1aeb63bd30d8eb841d6a941c5d6df3
. We’ll find some files are created, if we check our local directory at ~/.starcoin/dev
:
$ ls -l ~/.starcoin/dev
drwxr-xr-x 10 wh staff 320 Jun 16 18:32 account_vaults
-rw-r--r-- 1 wh staff 257 Jun 16 18:32 config.toml
-rw-r--r-- 1 wh staff 89048 Jun 16 18:32 genesis
-rw-r--r-- 1 wh staff 10710 Jun 16 18:32 genesis_config.json
-rw------- 1 wh staff 64 Jun 16 18:32 network_key
srw------- 1 wh staff 0 Jun 16 18:32 starcoin.ipc
-rw-r--r-- 1 wh staff 14678 Jun 16 18:32 starcoin.log
drwxr-xr-x 3 wh staff 96 Jun 16 18:32 starcoindb
We’ll meet them later, for now, let’s follow the source code to find how an account is created.
Two Account Provider Strategies
From the code here we can see:
- If there is a
account_dir
configured, then it should beAccountProviderStrategy::Local
- Otherwise
AccountProviderStrategy::RPC
is used.
As we specify the -d ~/.starcoin
to run above command to create an account, so I think the account_dir
could be ~/.starcoin/dev/account_vaults
.
Just simply add a print statement in account/provider/src/provider/mod.rs, and remove the directory, then build & run it again:
$ rm -rf ~/.starcoin/dev/
$ cargo build
$ ./target/debug/starcoin -d ~/.starcoin -n dev account create -p my-pass
+++++++ RPC
{
"ok": {
"address": "0x7760d5c787b9918005d82cca6d8225a9",
"is_default": false,
"is_readonly": false,
"public_key": "0xa8459efd0f6becd563bca5ea6d8b930e9f6f19a30aef1b9632baa62f59e2a21d",
"receipt_identifier": "stc1pwasdt3u8hxgcqpwc9n9xmq394y0qwh24"
}
}
Emmm, things don’t always happen as your thoughts, aren’t they? AccountProviderStrategy
only will be used if we run command and pass --local-account-dir
option:
$ ./target/debug/starcoin -d ~/.starcoin/ --local-account-dir=~/.starcoin/dev/account_vaults/ -n dev account create -p my-pass
+++++++ LOCAL
{
"ok": {
"address": "0x18d44098682869343f232d6cf0bdf9a9",
"is_default": true,
"is_readonly": false,
"public_key": "0x17e9deb0b3784b5abebad3a7cb8030c23360d6b7f8d108a8186d20e2a23b51ff",
"receipt_identifier": "stc1prr2ypxrg9p5ng0er94k0p00e4yhnhgw0"
}
}
There it is.
AccountManager
& AccountStorage
I’m tracing the invocation path, and find that no matter which strategy is used, then eventually, the AccountManager is used to do the actually job. And it mainly relys on AccountStorage.
Now let’s check the defination of AccountManager.create_account
:
pub fn create_account(&self, password: &str) -> AccountResult<Account> {
let private_key = gen_private_key();
let private_key = AccountPrivateKey::Single(private_key);
let address = private_key.public_key().derived_address();
self.save_account(
address,
private_key.public_key(),
Some((private_key, password.to_string())),
)
}
It’s very simple, just generate a private key, and use it’s public key to derive an address, that’s how your account address comes out.
Two more invocations are involved: generate_private_key
and AccountManager.save_account
. Let’s check the first one:
generate_private_key
pub(crate) fn gen_private_key() -> Ed25519PrivateKey {
let mut seed_rng = rand::rngs::OsRng;
let seed_buf: [u8; 32] = seed_rng.gen();
let mut rng: StdRng = SeedableRng::from_seed(seed_buf);
Ed25519PrivateKey::generate(&mut rng)
}
Emmm, we are using Ed25519
, as konwn as EdDSA(Edwards-curve Digital Signature Algorithm).
Then how an address is derived?
As we can see, the address is derived by a public key, which defined in starcoin_vm_types::transaction::authenticator::AccountPublicKey.derived_address.
The main function of this invocation is use starcoin_vm_types::transaction::authenticator::AuthenticationKey::from_preimage create and AuthenticationKey
can call
derived_address that bind to it:
/// Create an authentication key from a preimage by taking its sha3 hash
pub fn from_preimage(preimage: &AuthenticationKeyPreimage) -> AuthenticationKey {
AuthenticationKey::new(*HashValue::sha3_256_of(&preimage.0).as_ref())
}
/// Return an address derived from the last `AccountAddress::LENGTH` bytes of this
/// authentication key.
pub fn derived_address(&self) -> AccountAddress {
// keep only last 16 bytes
let mut array = [0u8; AccountAddress::LENGTH];
array.copy_from_slice(&self.0[Self::LENGTH - AccountAddress::LENGTH..]);
AccountAddress::new(array)
}
How to avoid collision of address?
TODO
How is an Account be Saved?
It’s mainly implemented in AccountManager.save_account:
- Check existence.
- Verify private key and password.
- Invoke Account::create to create the account: store it to corresponding place, and change the corresponding settings.
- Add to store.
- Set it to default, if it’s the first address.
Conclusion
So, an account has been created. There is no magic happens, which means no on chain operations are involved. Just generates a private key and public key pair, and derives an address.
The key pair is just a way that proof you own the address. So on the blockchain, you can send coins to any address, if nobody can proof that his owns that address, then the coins some how is lost forever.