Introducing Quill, a Ledger and Governance Toolkit for the Internet Computer

DFINITY
The Internet Computer Review
8 min readJun 21, 2021

The open source Quill toolkit maximizes both the security and convenience of managing cold wallets.

By Christian Müller, Senior Software Engineer | DFINITY

To support self-custody options for managing the Internet Computer’s ICP utility tokens, the DFINITY Foundation R&D team is announcing the open source release of Quill.

Quill is positioned as a ledger and governance toolkit that maximizes both the security and convenience of managing your wallet, providing a simple way to create signed messages for the Internet Computer’s ledger and governance canisters on an offline computer. These messages have to be transferred to an online machine and sent to the Internet Computer in order to take effect.

Motivation

When it comes to holding tokens or coins, users are usually confronted with a plethora of applications, which can be roughly categorized into the following buckets:

  1. Centralized wallet services
  2. Hot wallets (apps on devices connected to the internet)
  3. Cold wallets (apps on offline, or “air-gapped,” devices)

As with everything, the choice of one of the above options boils down to a tradeoff between convenience and security.

Holding the tokens on a centralized service is very convenient and user-friendly, but the cost you pay for that is your loss of control over the private key belonging to the address where your assets reside.

The use of so-called hot wallets allows you to be in full control of your private key. Popular hot wallets are smartphone apps, desktop apps, or browser extensions. You operate them by loading your secret key into the app, which is then able to control your assets on your behalf. But the fact that your private key is exposed to an app that’s connected to the internet spans a serious attack surface, which unfortunately has been exploited numerous times in the past. For example, users can be tricked into downloading an imposter hot-wallet app, or a legitimate app might become compromised by the injection of a malicious dependency, which hijacks the secret key and steals all of the funds from your account.

The cold wallet option involves using a device that is entirely disconnected from the internet. How is this possible? This is where the “crypto” prefix of “cryptocurrencies” comes into play. Blockchains run on cryptographic protocols, which are driven by cryptographically verifiable messages. That is, if you can craft such a message on your offline computer, you can then simply transmit it onto your online computer and broadcast to the blockchain, which will accept it no matter how and where it was crafted. In this scenario, if your private key is only exposed to the offline computer, and this computer stays offline forever, the attack surface through which your key might become compromised is much smaller. It’s important to note that there are still other ways to exploit this setup, such as when software that is already compromised is installed on the offline computer. A malicious program on the offline computer could, for example, change the contents of the transaction before it gets signed, so maintaining the security of the offline computer is paramount.

Arguably, all three of these different approaches provide increasing levels of security along with decreasing levels of convenience. To work against this lack of convenience, the DFINITY Foundation has been actively developing Quill to maximize user ease while also ensuring the maximum level of security for your cold wallet.

Quill

Quill provides a ledger and governance toolkit for self-custody. As previously explained, it gives token holders using self-custody a simple way to create signed messages for the Internet Computer’s ledger and governance canisters on an offline computer. These messages have to be transferred to an online machine and sent to the Internet Computer to be executed.

Quill is well documented, and you can start exploring its API by invoking the help command:

$ quill help
quill
Ledger & Governance ToolKit for cold wallets
USAGE:
quill [OPTIONS] <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS: --pem-file <pem-file> Path to your PEM file (use "-" for STDIN)SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s)
neuron-manage Signs a neuron configuration change
neuron-stake Signs topping up of a neuron (new or existing)
public-ids Prints the principal id and the account id
send Sends a signed message or a set of messages
transfer Signs an ICP transfer transaction

To get help on every one of the Quill commands, just append the command name:

$ quill help public-ids
quill-public-ids
Prints the principal id and the account id
USAGE:
quill public-ids
FLAGS:
-h, --help Prints help information
-V, --version Prints version information

The main parameter required for almost all operations is the path to your secret key. Quill currently supports only secret keys in PEM format. According to Wikipedia:

Privacy-Enhanced Mail (PEM) is a de facto file format for storing and sending cryptographic keys, certificates, and other data, based on a set of 1993 IETF standards defining “privacy-enhanced mail.”

Most established tools support serialization of private keys in PEM format. You can also use DFINITY’s keysmith tool to generate a PEM file from your BIP39 seed phrase, which you can generate using keysmith as well, or use any other tool of your choice.

When you generated your PEM file, you can use it to display the corresponding principal id and the account number, which you can use to receive ICP token transfers:

$ quill --pem-file <path-to-pem> public-ids
Principal id: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae
Account id: 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752

To sign a transfer to someone’s account, just run:

$ quill --pem-file <path-to-pem> transfer <account-id> --amount 10

This will generate a message containing two signed messages, which are the actual transfer transaction and request status query:

{
"ingress": {
"call_type": "update",
"request_id": "6d824444da967236cda8e62e1c2ef597919ca4a62ac4693e5a6d34ee227051af",
"content": "…"
},
"request_status": {
"canister_id": "ryjl3-tyaaa-aaaaa-aaaba-cai",
"request_id": "6d824444da967236cda8e62e1c2ef597919ca4a62ac4693e5a6d34ee227051af",
"content": "…"
}
}

For maximum security, it’s important to note that you have to verify the contents of the signed transaction before you transmit it to an online computer in order to exclude any kind of unwanted compromise of your original inputs (like exchanging the destination address).

Ideally, you should use a tool developed by a different trusted party than the signing tool. But if you compile Quill yourself and inspect the source code beforehand, you can use Quill to display the contents of the verified transaction. For this you can use the send command with the --dry-run option on the offline computer (which is fine, because we do not send it anywhere in the dry-run mode):

$ quill send --dry-run <path-to-message>
Sending message with
Call type: update
Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae
Canister id: ryjl3-tyaaa-aaaaa-aaaba-cai
Method name: send_dfx
Arguments: (
record {
to = "345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752";
fee = record { e8s = 10_000 };
memo = 0;
from_subaccount = null;
created_at_time = null;
amount = record { e8s = 1_000_000_000 };
},
)

After making sure that the transaction is correct, you can now transmit this message to an online computer, save it to a file, and use Quill on the online computer to broadcast it to the Internet Computer:

$ quill send <path-to-message>
Sending message with
Call type: update
Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae
Canister id: ryjl3-tyaaa-aaaaa-aaaba-cai
Method name: send_dfx
Arguments: (
record {
to = “345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752”;
fee = record { e8s = 10_000 };
memo = 0;
from_subaccount = null;
created_at_time = null;
amount = record { e8s = 1_000_000_000 };
},
)
Do you want to send this message? [y/N]
y
Request ID: 0xdde8ea6332b5ae0f5264a841b7e43932f451d5998dd08a7ab48153d7b7fc578f
The request is being processed…
The request is being processed…
The Replica returned an error: code 5, message: "Canister ryjl3-tyaaa-aaaaa-aaaba-cai trapped explicitly: Panicked at 'You tried to withdraw funds from empty account 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752',
rosetta-api/ledger_canister/src/lib.rs:296:25”

What happened above is that Quill first asked for an explicit confirmation, then submitted the transaction to the Internet Computer and then polled for the status of the transaction. Note that in the example above we used a test account containing no ICP tokens, so the Internet Computer returned a corresponding error.

The same approach applies to all governance commands. For example, to stake your ICP tokens to a new or an existing neuron, use the following command:

$ quill --pem-file <path-to-pem> neuron-stake --amount 1 --name "myneuron"

The reason why we use an ASCII name that is eight characters long to identify the neuron when we stake or top it up is a consequence of the existing governance API and is beyond the scope of this article. In any case, a successful execution of the command above will return the neuron id, which needs to be used for the subsequent configuration of the neuron. Also, it is currently recommended that you give your neurons sensible names and write them down. It is possible to easily recover the name of the neuron — which is just being used as a memo for the transaction, transferring the staked ICP tokens to the governance sub-account — but this is future work and will be addressed later.

For the neuron configuration, you can use the neuron-stake command:

$ quill help neuron-manage
quill-neuron-manage
Signs a neuron configuration change
USAGE:
quill neuron-manage [FLAGS] [OPTIONS] <neuron-id>
ARGS:
<neuron-id> The id of the neuron to manage
FLAGS:
--disburse Disburse the entire staked amount to the controller’s account
--start-dissolving Start dissolving
--stop-dissolving Stop dissolving
OPTIONS:
--add-hot-key <add-hot-key>
Principal to be used as a hot key
-a, --additional-dissolve-delay-seconds
<additional-dissolve-delay-seconds>
Number of dissolve seconds to add
--remove-hot-key <remove-hot-key>
Principal hotkey to be removed

So a recommended workflow would be to:

  1. Stake your ICP tokens to neurons using the neuron-stake command.
  2. Specify the dissolve delay using theneuron-manage command and the
    --additional-dissolve-delay-seconds option.
  3. Start dissolving of the neuron (if desired) with neuron-managecommand and the--start-dissolving option.
  4. Add the principal id of your internet identity as a hotkey with neuron-manage command and the--add-hot-key option.

This workflow will allow you to create a neuron from your cold wallet while enabling you to use the NNS dapp to see all the details about your neurons, such as the staked amount, maturity, voting history, and even do some basic controlling, such as configuring the followees. Note that in the fourth step you should use the principal id that corresponds to your Internet Identity, which is displayed in the UI of the NNS dapp on the “Neurons” tab.

For users who would prefer a less technical approach to custody and neuron staking, third-party token custody services will also soon support Internet Computer neuron operation. More details on staking can be found at: “Earn Substantial Voting Rewards by Staking in the Network Nervous System.”
____

Start building at smartcontracts.org and join our developer community at forum.dfinity.org.

--

--

DFINITY
The Internet Computer Review

The Internet Computer is a revolutionary blockchain that hosts unlimited data and computation on-chain. Build scalable Web3 dapps, DeFi, games, and more.