Key rotation

Once the new account is created at a, the user can sign transactions to be sent from that account
using the private signing key sk. The user can also rotate the key used to sign transactions from the
account without changing its address, e.g., to proactively change the key or to respond to a possible
compromise of the key.

Can someone explain how key rotation is possible without changing the address?

3 Likes

Hi Bram,
Looking at the source code that implements key rotation in the LibraAccount module might help. Basically, the scheme works like this:

  • When an account is created at address A, a LibraAccount.T resource is created. The authentication_key field of this resource is set to A.
  • Any time a transaction is sent from address A, the transaction must include a public key to check against the signature on the transaction.
  • In addition to checking the public key against the signature, the transaction validation code checks that the hash of the public key is equal to the account authentication key.
  • To rotate the key, the owner of the account at address A can generate a new keypair with private key K_s and public key K_v. The user then send a transaction from A that calls rotate_authentication_key with sha3(K_v) as an argument. By doing so, the user has decoupled the authentication key from the account address.
  • The user can then sign subsequent transactions sent from A with K_s and include K_v as the public key in the transaction.
5 Likes

Thanks for the clear explanation and reference to the source code!

Sam, your answer with pointers to the code is crystal-clear! Thanks. Didier

And so the advantage from this feature is that you can change the key pair without changing the address, so that you do not have to notify anyone of an address change when you change the key pair when the possibility exists your private key is compromised?
Are there more situations or uses for this feature?

And so the advantage from this feature is that you can change the key pair without changing the address, so that you do not have to notify anyone of an address change when you change the key pair when the possibility exists your private key is compromised?

If a malicious actor manages to compromise a user’s key pair, I don’t see any reason why they wouldn’t immediately rotate to a new authentication_key that only they control. @sam could you shed some light on what the value of rotate a key is? From my perspective, the most valuable attribute that key rotation provides is the ability to confidently transfer control of accounts. Not sure if I’m missing something.

As a simple example, imagine that you keep two backup copies of your key. You’re not able to locate one of the copies. You don’t know that it has been stolen, but you simply can’t find it. Key rotation will let you know for sure that the key has been changed.

A more complex example. An exchange uses a threshold-based signature scheme to protect an account. They are about to terminate one of the key holders. The threshold shares can be regenerated, but the soon-to-be-fired shareholder may have kept his share. If other shareholders kept their share he could collude with them. Multiple shareholders might be terminated over time, eventually resulting in a set of fired former-shareholders that can generate a signature. Rotating the key can prevent this from happening. The key could be rotated while the employee is being terminated, preventing them from taking action once they learn they have been fired.

2 Likes

@sam If I understand correctly, authentication key and account address are decoupled only in the sense that the former can be changed without affecting the latter, but not in the sense that both have to be the same during account creation.

Then why not allow users to specify meaningful and easier to remember addresses by supporting an additional parameter during public create_new_account? EOS does this and somewhat avoided the land grabbing situation as with internet domain names by requiring certain address format and the non-trivial cost associated. Could you shed some light on the thinking behind this decision?

If I understand correctly, authentication key and account address are decoupled only in the sense that the former can be changed without affecting the latter,

This is correct.

but not in the sense that both have to be the same during account creation.

This is correct in principle, but is not the case in the current implementation (where both are the same during account creation).

Then why not allow users to specify meaningful and easier to remember addresses by supporting an additional parameter during public create_new_account? EOS does this and somewhat avoided the land grabbing situation as with internet domain names by requiring certain address format and the non-trivial cost associated. Could you shed some light on the thinking behind this decision?

There are a few reasons for not supporting something like this. The land grab problem that you mention is one of them. Another is that if you allow users to customize the signing key for an account at account creation time, the only way to claim an address is to actually send a transaction to explicitly create an account at that address. In a system like Libra, you can (in expectation) claim an address offline by simply generating a keypair. Use-cases like EarmarkedLibraCoin wouldn’t be possible without this ability.

Whether claim address offline will be the prevalent use case aside, I think EarmarkedLibraCoin can still work just that one’d be earmarking for an authentication key rather than address, which just happen to be the same in current implementation.

Below is my attempt to earmark for both auth key (for proof of ownership) and, let’s call it vanity address (for human readability). Note that I assumed a get_txn_pub_key() to get the public key that’s associated with every transaction instead of passing it in as a parameter, this is safer since we can rely on Admission Control to guarantee that the public key matches signature in said transaction.

If the recipient failed to acquire the intended vanity address, the creator can simply claim the Libra coins and re-earmark. If this is too much hassle, one can provide simplified overloads that ignores vanity address and just earmark/assert against authentication key, only losing some convenience in human readability.

// A module for earmarking a coin for a specific recipient
module EarmarkedLibraCoin {
  import 0x0.LibraCoin;

  // A wrapper containing a Libra coin and the address of the recipient the
  // coin is earmarked for.
  resource T {
    coin: R#LibraCoin.T,
    recipient: address,
    authentication_key: bytes
  }

  // Create a new earmarked coin with the given `recipient`.
  // Publish the coin under the transaction sender's account address.
  public create(coin: R#LibraCoin.T, recipient: address, authentication_key: bytes) {
    let t: R#Self.T;

    // Construct or "pack" a new resource of type T. Only procedures of the
    // `EarmarkedLibraCoin` module can create an `EarmarkedLibraCoin.T`.
    t = T {
      coin: move(coin),
      recipient: move(recipient),
      authentication_key: move(authentication_key)
    };

    // Publish the earmarked coin under the transaction sender's account
    // address. Each account can contain at most one resource of a given type; 
    // this call will fail if the sender already has a resource of this type.
    move_to_sender<T>(move(t));
    return;
  }

  // Allow the transaction sender to claim a coin that was earmarked for her.
  public claim_for_recipient(earmarked_coin_address: address): R#Self.T {
    let t: R#Self.T;
    let t_ref: &R#Self.T;
    let sender: address;

    // Remove the earmarked coin resource published under `earmarked_coin_address`.
    // If there is no resource of type T published under the address, this will fail.
    t = move_from<T>(move(earmarked_coin_address));

    t_ref = &t;
    // This is a builtin that returns the address of the transaction sender.
    sender = get_txn_sender();
    pub_key = get_txn_pub_key();
    // Ensure that the transaction sender is the recipient. If this assertion
    // fails, the transaction will fail and none of its effects (e.g.,
    // removing the earmarked coin) will be committed.  99 is an error code
    // that will be emitted in the transaction output if the assertion fails.
    assert(*(&move(t_ref).recipient) == move(sender), 99);
    assert(*(&move(t_ref).authentication_key) == sha3(move(pub_key)), 99)

    return move(t);
  }

  // Allow the creator of the earmarked coin to reclaim it.
  public claim_for_creator(): R#Self.T {
    let t: R#Self.T;
    let sender: address;

    sender = get_txn_sender();
    // This will fail if no resource of type T under the sender's address.
    t = move_from<T>(move(sender));
    return move(t);
  }

  // Extract the Libra coin from its wrapper and return it to the caller.
  public unwrap(t: R#Self.T): R#LibraCoin.T {
    let coin: R#LibraCoin.T;
    let recipient: address;

    // This "unpacks" a resource type by destroying the outer resource, but
    // returning its contents. Only the module that declares a resource type
    // can unpack it.
    T { coin, recipient, authentication_key } = move(t);
    return move(coin);
  }

}