Skip to content

Instantly share code, notes, and snippets.

@flowerornament
Last active September 19, 2021 05:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flowerornament/1c729d454d11f64f376b89e39bdf031c to your computer and use it in GitHub Desktop.
Save flowerornament/1c729d454d11f64f376b89e39bdf031c to your computer and use it in GitHub Desktop.

UP 8: Urbit HD Wallet

Overview

The Urbit Hierarchical Deterministic (HD) Wallet is a type-1 HD wallet of BIP32 type-2 HD wallets that uses public-key cryptography to secure the ownership and control of Urbit address space.

The Urbit HD Wallet is not one key-pair, but a system of related key-pairs that each have distinct powers, from setting networking keys for communicating in the Urbit network to transferring ownership of your Urbit ship.

We use the Ethereum blockchain for the Urbit public-key infrastructure (PKI). If you are interested in reading about the motivation behind the decision to use Ethereum, see our post from 2017.9.

Rationale

Urbit address space is non-fungible and finite. Thus, in addition to its functional value, address space has financial value. Owners of address space need safeguards that allow for interacting on the Urbit network without jeopardizing cryptographic ownership of their assets.

Toward this end, Ethereum addresses that own a particular ship can designate additional addresses as proxies for specific sets of functions. These functions include resetting networking keys, spawning child ships, voting on Urbit proposals, and so forth.

The Urbit HD Wallet's derivation paths also have this hierarchical structure, so that keys with different powers can be physically separated. A "master ticket" can re-derive the entire wallet in case of loss. The encryption and authentication keys that Urbit ships use to sign messages within the network are also derived from the wallet.

While evaluating wallet designs, it became clear that a design using BIP32 with BIP44-like paths wasn't sufficient, since it doesn't give you separate seeds that you can use to physically separate the keys that are assigned different rights. Using a pure BIP32 hierarchy would also have made the derivation of the networking keys, which use a different curve, less elegant.

The full specification is detailed in the next section.

Specification: Key Derivation

Reference

Parameters

Ship

A "username".

The numeric value of an Urbit identity/ship (max (2^32)-1), for deriving keys intended for use with that specific ship.

Ticket

A "password".

64, 128 or 384 bits of entropy, encoded in @q notation.

@q notation encodes bytes as syllables, connecting two syllables as words, separating words by dashes. 0x01 becomes .~nec, 0x0201 .~binnec, 0x030201 .~wes-binnec, etc. Not to be confused with @p, which also encodes in syllables, but scrambles them for values of 4 bytes and larger.

Revision

Integer to be incremented for generating new Urbit networking (curve25519, ed25519) keys while using the same ship and ticket, in case of breach or key compromise. Starts at 0.

Passphrase

Optional string, used when turning generated BIP39 mnemonics into BIP32 seeds.

Derivation of a wallet from a ticket

  • Generate a "master seed" from the ticket
    • Convert the ticket, a @q string, into a numeric value
    • Call Argon2 with the following parameters:
      • Out: 32 bytes
      • Type: u
      • Pass: the ticket value (64, 128 or 384 bits)
      • Salt: the string "urbitkeygen" with the ship number appended (ie "urbitkeygen123")
      • Parallelization: 4
      • Memory cost: 512.000 KB
      • Time cost: 1
  • For each type, "ownership", "transfer", "spawn", "voting", "management", derive a mnemonic phrase (also referred to as "seed"):
    • Append the type to the master seed
    • Hash the salted master seed using SHA2-256
    • Convert the resulting 256-bit output into a 24-word mnemonic sentence as specified in BIP39. We recommend using the standard English wordlist.
  • For each seed, to derive public/private keys and addresses:
    • Convert the seed (mnemonic phrase) plus the optional passphrase into a BIP32 seed, as specified in BIP39
    • Generate a BIP32 master node from the BIP32 seed (as per BIP32)
    • Derive the wallet at the path m/44'/60'/0'/0/0.
    • To derive an Ethereum address from the private key, use secp256k1 to get the uncompressed public key from the private key, then hash its last 64 bytes with keccak-256 and take the last 20 bytes from the result. If outputting addresses as strings rather than hex values, the strings should be checksummed as per EIP-55.
  • For the type "network", derive a seed as follows:
    • Convert the "management" seed (mnemonic phrase) plus the optional passphrase into a BIP32 seed, as specified in BIP39
    • Append the revision number to the string "network" (ie "network0"); the revision number defaults to 0
    • Append that whole string to the BIP32 seed
    • For a revision number of 0, hash the "salted" BIP32 seed using SHA2-256
    • For higher revision numbers, hash the "salted" BIP32 seed using SHA2-256d (hash with SHA2-256 twice)
  • Optionally, for the type "network", derive encryption and authentication keys as follows (identical to ++pit:nu:crub:crypto in Urbit's standard library, zuse):
    • Hash the "network" seed using SHA2-512
    • Take the first 256 bits of the result as the authentication private key
    • Take last 256 bits of the result as the encryption private key
    • Derive public keys for those using ed25519

up-8

Integration Plan

Tlon has developed the keygen-js library, which implements the above specification. This functionality will also exist in Arvo as a library.

Tlon is currently developing a Wallet Generator, a browser-based application for the purpose of easily generating Urbit HD Wallets while offline. Wallet Generator uses the keygen-js library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment