Portefeuille déterministe

Portefeuille hors-ligne

Pour accroître la sécurité, les clés privées peuvent être générées et stockées par un portefeuille hors-ligne ou dans un environnement sécurisé, et utilisé conjointement avec un autre portefeuille qui lui interagit avec le réseau Bitcoin mais ne connaît que les clés publiques:

  1. Le portefeuille A génère les clés privées et publiques.

  2. Les clés publiques sont copiées vers un portefeuille B, qui lui interagit avec le réseau.

  3. Lorsqu’on veut dépenser des bitcoins, le portefeuille B génère une transaction non signée.

  4. La transaction non-signée est copiée vers le portefeuille A, qui la signe avec la clé privée appropiée.

  5. La transaction signée est copiée vers le portefeuille B, qui s’occupe de distribuer la transaction sur le réseau.

Portefeuille matériel

Un portefeuille matériel est un appareil qui stocke un portefeuille hors-ligne et permet d’effectuer le transfert portefeuille hors-ligne / en-ligne de manière plus rapide: plutôt que de copier sur une clé USB pour effectuer le transfert entre deux ordinateurs (un hors-ligne, un en-ligne), on utilise un appareil qui fait en quelque sorte à la fois clé USB et ordinateur hors-ligne.

Cold Wallet

Les utilisateurs séparent généralement leur solde en deux types de portefeuille:


Portefeuille déterministe

La sécurité supplémentaire des clés pré-générées est assez faible face aux dommages dus à l’insuffisance des sauvegardes et la pression accrue de garder un seul portefefeuille en ligne est énorme. Bitcointalk

Seed phrase (BIP39)

Pour aller plus loin:
Mnemonic Code Converter
Mnemonic Code Words
BIP39

Portefeuille déterministe hiérarchique (BIP32)

La séparation privé/publique

La hiérarchie

Génération de la clé principale

  1. Comme la clé principale n’a pas de clé parente, là où les clés enfants utiliseront une clé et un code, on utilise la chaîne d’amorçage et une chaîne de caractère arbitraire (“Bitcoin seed”), que l’on donne à une fonction de hashage HMAC-SHA512.

    var hasher = new jsSHA(seed, 'HEX'),
        hash   = hasher.getHMAC("Bitcoin seed", "TEXT", "SHA-512", "HEX");
    
  2. Clé privée + code:
    La fonction HMAC retourne une valeur de 64 octets (512 bits), que l’on sépare en deux 32 octets:

    • la première moitié est la clé privée, qui est un nombre aléatoire comme toute autre clé privée.
    • le deuxième moitié est le code, qui est un nombre aléatoire supplémentaire.
    var prv        = Crypto.util.hexToBytes(hash.slice(0, 64)),
        chain_code = Crypto.util.hexToBytes(hash.slice(64, 128));
    
  3. Clé privée étendue:
    Une clé privée étendue est en quelque sorte un conteneur qui permet de stocker la clé privée et le code, ainsi qu’un certain nombre d’information supplémentaires. En ayant la clé privée mais pas le code, on ne peut pas calculer les clés enfants, c’est la clé privée étendue qui nous servira.

     var xprv = build_extended_key({
       version            : 0x0488ADE4, // mainnet (base58: xprv)
       depth              : 0x00,
       parent_fingerprint : 0x00000000,
       child_index        : 0x00000000,
       chain_code         : chain_code,
       key                : prv
     });
    

    Les clés étendues privée et publique sont sérialisées comme suit:

     function build_extended_key({
       version,
       depth,
       parent_fingerprint,
       child_index,
       chain_code,
       key
     }) {
       var serial = '';
       serial += tobytes(version, 4);
       serial += tobytes(depth, 1);
       serial += tobytes(parent_fingerprint, 4);
       serial += tobytes(child_index, 4);
       serial += tobytes(chain_code, 32);
       serial += tobytes(key, 33);
       return base58check(serial);
     }
    

    Le résultat est la liste des informations (suivit d’une checksum) encodé en base58.
    On peut facilement désérialiser la clé étendue pour récupérer les infos individuellement.

  1. Clé publique:
    On calcule la clé publique à partir de la clé privée, comme on le ferait d’habitude.
    BIP32 utilise la clé version compressée (coordonnée x + 1 bit de parité) — 33 octets au lieu de 64.

     var eckey   = new Bitcoin.ECKey(prv),
         obj_pub = eckey.getPubPoint();
    
     obj_pub.setCompressed(true);
     var pub = obj_pub.getEncoded(true);
    
  2. Clé publique étendue:
    Même principe que pour la clé privée mais avec la clé publique.

     var xpub = build_extended_key({
       version            : 0x0488B21E, // mainnet (base58: xpub)
       depth              : 0x00,
       parent_fingerprint : 0x00000000,
       child_index        : 0x00000000,
       chain_code         : chain_code,
       key                : pub
     });
    

Génération d’une clé enfant, à partir de xpriv

Chaque enfant a un numéro d’index sur 4 octets, ce qui permet de générer 232 enfants à partir d’une clé étendue.

  1. On additionne la clé publique et l’index de l’enfant, et on donne le résultat à une fonction de hashage HMAC-SHA512 avec le code. Pour rappel, la clé privée et le code sont stockés dans la clé privée étendue et on peut calculer la clé publique à partir de la clé privée.

    var child_index = 0,
        data        = pub.concat(tobytes(child_index, 4));
    
    var hasher = new jsSHA(Crypto.util.bytesToHex(data), 'HEX'),
        hash   = hasher.getHMAC(Crypto.util.bytesToHex(chain_code), "TEXT", "SHA-512", "HEX");
    
  2. Clé privée + code:
    Le résultat est séparé en deux:

    • la première moitié est interprétée comme un nombre de 256 bits, qui est ajouté à la clé privée d’origine pour créer une clé privée enfant. Il s’agit essentiellement de prendre la clé privée et de l’augmenter d’un nombre aléatoire de 32 octets.

      child_privkey = privkey + token
      

      On prend le résultat modulo n (l’ordre de la courbe elliptique) pour maintenir la clé dans la plage de nombres valides pour la courbe.

    • la deuxième moitié est le nouveau code, qui sera utilisé pour créer des sous-enfants.

     var child_prv = new BigInteger(hash.slice(0, 64), 16)
     child_prv = child_prv.add(prv);
     child_prv = child_prv.mod(getSECCurveByName("secp256k1").getN());
    
     var child_chain_code = Crypto.util.hexToBytes(hash.slice(64, 128));
    
  3. Clé privée étendue:
    Les 4 premiers octets du Hash160 (RIPEMD160-SHA256) de la clé publique constituent l’empreinte (fingerprint en anglais) de la clé parente. Ça permet au logiciel de détecter rapidement l’aborescence des clés.

    var pub_hash        = Bitcoin.Util.sha256ripe160(pub),
        pub_fingerprint = pub_hash.slice(0,4);
    
     var child_xprv = build_extended_key({
       version            : 0x0488ADE4, // mainnet (base58: xprv)
       depth              : 1,          // xprv.depth + 1
       parent_fingerprint : pub_fingerprint,
       child_index        : child_index,
       chain_code         : child_chain_code,
       key                : child_prv
     });
    
  4. Clé publique:
    Comme pour la clé principale, la clé publique est calculée à partir de la clé privée.

     var eckey   = new Bitcoin.ECKey(prv),
         obj_pub = eckey.getPubPoint();
    
     obj_pub.setCompressed(true);
     var child_pub = obj_pub.getEncoded(true);
    
  5. Clé publique étendue:
    Même principe que pour la clé privée mais avec la clé publique.

     var child_xpub = build_extended_key({
       version            : 0x0488B21E, // mainnet (base58: xpub)
       depth              : 1,          // xprv.depth + 1
       parent_fingerprint : pub_fingerprint,
       child_index        : child_index,
       chain_code         : child_chain_code,
       key                : child_pub
     });
    

Génération d’une clé enfant, à partir de xpub

La clé publique étendue permet de calculer les clés publiques enfant — les mêmes que celles générées à partir de la clé privée étendue. Elle ne peut pas calculer les clés privées enfant.

  1. Comme pour la clé privée étendue, on additionne la clé publique et l’index de l’enfant, et on donne le résultat à une fonction de hashage HMAC-SHA512 avec le code.

  2. Clé publique:
    On sépare le résultat en deux:

    • la première moitié est interprétée comme un nombre de 256 bits, multiplié par le générateur de la courbe elliptique, et on l’ajoute à la clé publique pour créer une clé publique enfant.

      child_Pubkey = Pubkey + token * G
      
    • la deuxième moitié est le nouveau code

    var child_pub = new BigInteger(hash.slice(0, 64), 16);
    child_pub = getSECCurveByName("secp256k1").getG().multiply(child_pub);
    child_pub = child_pub.add(pub);
    
    var child_chain_code = Crypto.util.hexToBytes(hash.slice(64, 128));
    
  3. Clé publique étendue:
    Même chose qu’avec la clé privée étendue.

Pour rappel, ça marche puisqu’on peut utiliser la relation mathématique suivante:

  privkey2 = privkey + token
  Pubkey2  = Pubkey + token * G

Clé renforcée

Chemin de dérivation

Par convention, les clés d’un portefeuille sont identifiées par un chemin de dérivation (derivation path), qui est la liste des index utilisés pour générer la clé, séparés par une barre oblique (/).

Les clés publiques, dérivées de la clé publique principale, commencent par “M”.
Les clés privées, dérivées de la clé privée principale, commencent par “m”.

Type de clé Chemin Clé dérivée de
Privée
m/1/2/3
CKDpriv(CKDpriv(CKDpriv(m,1),2),3)
Publique
M/1/2/3
CKDpub(CKDpub(CKDpub(M,1),2),3)
Privée
m/3H/2/5
CKDpriv(CKDpriv(CKDpriv(m,3H),2),5) 
Publique
N(m/3H/2/5)
CKDpub(CKDpub(CKDpub(m,3H),2),5) 

Sources:
Extended Keys
brainwallet.js + bip32.js

Keys, Addresses, Wallets
Hierarchical deterministic Bitcoin wallets that tolerate key leakage

Pour aller plus loin:
BIP32

Hiérarchie normalisée

Pour aller plus loin:
BIP44


Brain wallet

Avec les brain wallet, les clés privées ne sont pas générées aléatoirement: c’est l’utilisateur qui choisit la phrase d’amorçage qui génère la clé privée. Il est recommandé de ne pas utiliser ce type de portefeuille car beaucoup plus facile à cracker.