Master Key Generation
Master key generation is the process by which the wallet turns a recovery-phrases (entropy) into a secure cryptographic key. Child keys can be derived from a master key to produce a derivation tree structure as outlined in hierarchical-deterministic-wallets.
In Cardano, the master key generation algorithm is different depending on which style of wallet one is considering. In each case however, the generation is a function from an initial seed to an extended private key (XPrv) composed of:
- 64 bytes: an extended Ed25519 secret key composed of:
- 32 bytes: Ed25519 curve scalar from which few bits have been tweaked (see below)
- 32 bytes: Ed25519 binary blob used as IV for signing
- 32 bytes: chain code for allowing secure child key derivation
Additional resources
- SLIP 0010
- BIP 0032
- BIP 0039
- RFC 8032
- CIP 3 — "Wallet key generation"
- CIP 1852 — "HD (Hierarchy for Deterministic) Wallets for Cardano"
History
Throughout the years, Cardano has been using different styles of HD wallets. We categorize these wallets in the following terms:
Wallet Style | Compatible Products |
---|---|
Byron | Daedalus, Yoroi |
Icarus | Yoroi, Trezor |
Ledger | Ledger |
Each wallet is based on Ed25519 elliptic curves though differs in subtle ways highlighted in the next sections.
Pseudo-code
Byron
function generateMasterKey(seed) {
return hashRepeatedly(seed, 1);
}
function hashRepeatedly(key, i) {
(iL, iR) = HMAC
( hash=SHA512
, key=key
, message="Root Seed Chain " + UTF8NFKD(i)
);
let prv = tweakBits(SHA512(iL));
if (prv[31] & 0b0010_0000) {
return hashRepeatedly(key, i+1);
}
return (prv + iR);
}
function tweakBits(data) {
// * clear the lowest 3 bits
// * clear the highest bit
// * set the highest 2nd bit
data[0] &= 0b1111_1000;
data[31] &= 0b0111_1111;
data[31] |= 0b0100_0000;
return data;
}
Icarus
Icarus master key generation style supports setting an extra password as an arbitrary byte array of any size. This password acts as a second factor applied to cryptographic key retrieval. When the seed comes from an encoded recovery phrase, the password can therefore be used to add extra protection in case where the recovery phrase were to be exposed.
function generateMasterKey(seed, password) {
let data = PBKDF2
( kdf=HMAC-SHA512
, iter=4096
, salt=seed
, password=password
, outputLen=96
);
return tweakBits(data);
}
function tweakBits(data) {
// on the ed25519 scalar leftmost 32 bytes:
// * clear the lowest 3 bits
// * clear the highest bit
// * clear the 3rd highest bit
// * set the highest 2nd bit
data[0] &= 0b1111_1000;
data[31] &= 0b0001_1111;
data[31] |= 0b0100_0000;
return data;
}
More info
For a detailed analysis of the cryptographic choices and the above requirements, have a look at: Wallet Cryptography and Encoding
function generateMasterKey(seed, password) {
let data = PBKDF2
( kdf=HMAC-SHA512
, iter=2048
, salt="mnemonic" + UTF8NFKD(password)
, password=UTF8NFKD(spaceSeparated(toMnemonic(seed)))
, outputLen=64
);
let cc = HMAC
( hash=SHA256
, key="ed25519 seed"
, message=UTF8NFKD(1) + seed
);
let (iL, iR) = hashRepeatedly(data);
return (tweakBits(iL) + iR + cc);
}
function hashRepeatedly(message) {
let (iL, iR) = HMAC
( hash=SHA512
, key="ed25519 seed"
, message=message
);
if (iL[31] & 0b0010_0000) {
return hashRepeatedly(iL + iR);
}
return (iL, iR);
}
function tweakBits(data) {
// * clear the lowest 3 bits
// * clear the highest bit
// * set the highest 2nd bit
data[0] &= 0b1111_1000;
data[31] &= 0b0111_1111;
data[31] |= 0b0100_0000;
return data;
}