ZYCORD docs
Français
ZycordDocsPortefeuille

Portefeuille

Clés, adresses et envoi — ainsi que le contrat de comportement que tout portefeuille sur ce protocole doit respecter. Chaque règle décrit une façon de perdre de l'argent que le protocole autorise, et qu'un portefeuille doit donc empêcher.

Les commandes#

zcd wallet new     --out KEYFILE                        # create an encrypted key file
zcd wallet address --key KEYFILE                        # show an address
zcd wallet balance --key KEYFILE                        # ask a node for a balance
zcd wallet send    --key KEYFILE --to ADDR --amount N
zcd wallet sweep   --key KEYFILE --to ADDR --one-shot
zcd wallet retire  --key KEYFILE [--addr ADDR]
zcd ui             --key KEYFILE                        # the same wallet in a browser

Le portefeuille graphique n'est pas une seconde implémentation. zcd wallet, zcd ui et l'application de bureau sont trois interfaces sur une seule wallet/session : chacune y construit son certificat, et les vérifications de règles s'exécutent à l'intérieur de cet appel. Un portefeuille graphique est donc structurellement incapable d'être plus permissif que la ligne de commande — non parce que celui qui l'a écrit a été soigneux, mais parce qu'il n'existe pas de second chemin de code sur lequel il pourrait l'être.

Deux sortes d'adresses#

VersionTypeÀ utiliser pourRéutilisable ?
0x01Usage uniqueUn unique paiement attendu. Dérivez-en une nouvelle par paiement reçu.Non. Un débit la brûle définitivement.
0x02PersistanteCommerçants, adresses de dons, portefeuilles chauds, paiements de minage.Oui, pour toujours. Ne peut jamais entrer dans le registre des adresses dépensées.

Usage unique contre persistante est une politique de portefeuille exposée sous forme de bit d'adresse, et non deux registres. Les portefeuilles utilisent par défaut 0x01 par paiement reçu, parce que c'est ce qui rend un paiement non reliable au suivant ; quiconque veut un compte utilise 0x02.

Les huit règles#

Chaque règle ci-dessous décrit une manière de perdre de l'argent ou des frais que le protocole permet — délibérément, parce que l'autre option était pire — et qu'un portefeuille doit donc empêcher. Le portefeuille de référence les implémente. Il ne se contente pas de les documenter.

Règle 1 — balayez des cellules entières#

Lorsque vous dépensez depuis une adresse 0x01, déplacez le solde entier — vers le bénéficiaire et vers une adresse de monnaie que vous contrôlez. Le certificat porte un MARK_SPENT de cette adresse, et une fois qu'il s'applique, toute lecture et toute écriture sous cette adresse échouent définitivement. Il n'y a pas de seconde transaction.

La chaîne adoucit désormais ceci : tout ce qu'une adresse brûlée détient encore passe dans la cellule RefundTo propre au certificat au moment du commit — pour le solde natif de l'adresse comme pour chaque cellule que le certificat nomme lui-même. Un balayage incomplet ne détruit donc plus les reliquats ; il les remet à votre adresse de monnaie.

L'obligation qui l'accompagne

Un certificat n'a qu'un RefundTo et peut brûler des adresses à usage unique appartenant à plusieurs signataires, si bien qu'un reliquat sous votre cellule peut être remis à la leur. Ne co-signez jamais un certificat qui brûle une de vos adresses à usage unique et rembourse vers une adresse que vous ne contrôlez pas.

Ce que cette règle ne peut pas sauver : un solde dans un actif que le certificat ne mentionne jamais. Les actifs sous une adresse ne sont pas dérivables d'un slot, atteindre un actif non nommé supposerait donc de parcourir toute la table des cellules dans l'unique étape qui ne se parallélise pas. Nommez chaque actif dans le certificat qui brûle l'adresse.

Règle 2 — RefundTo doit être une adresse encore utilisable#

Elle doit nommer soit une adresse persistante que vous contrôlez, soit une adresse à usage unique fraîche que ce certificat ne brûle pas. Un règlement dans une cellule brûlée immobilise le reliquat : le fold le brûle plutôt que de l'écrire dans une cellule que personne ne peut lire, et le signale comme refund_burned plutôt que refunded, de sorte qu'un portefeuille qui rapproche un solde peut s'en apercevoir.

Règle 3 — une adresse, un paiement attendu#

Deux obligations, une de chaque côté :

  • À la réception. Dérivez une nouvelle adresse 0x01 par paiement que vous attendez. Ne publiez jamais la même deux fois. Ne balayez ni ne retirez une adresse que vous avez divulguée au cours des derniers ttl_max blocs, à moins d'accepter qu'un paiement en vol vers elle donne lieu à une omission facturée à son expéditeur.
  • À l'envoi. Refusez de payer une adresse à usage unique dont vous voyez qu'elle a déjà été créditée ou dépensée. Si un bénéficiaire vous donne une adresse une seconde fois, traitez cela comme une erreur, non comme une commodité.

Pour tout ce qui est payé plus d'une fois — un commerçant, une adresse de dons, un paiement de minage — utilisez une adresse persistante (0x02). C'est l'unique règle qui supprime l'exposition plutôt que de la restreindre.

Règle 4 — chaînes dépendantes : confirmez, ou acceptez le risque#

Incrémentez Seq pour chaque certificat qui dépend d'un précédent. À l'intérieur d'un même bloc, le fold valide les certificats d'un signataire dans l'ordre de Seq, si bien qu'une chaîne dépendante dans le même bloc est sûre. D'un bloc à l'autre elle ne l'est pas : ne diffusez Seq = n+1 qu'après confirmation de Seq = n, ou acceptez que le certificat dépendant puisse être validé seul et donner lieu à une omission. Ce n'est pas un défaut du protocole — le signataire a accepté le risque de péremption en signant.

Règle 5 — fixez le maximum généreusement et la priorité honnêtement#

Chaque marché prend deux prix. Le maximum est une borne de solvabilité : dès que les frais de base le dépassent, le certificat est inincluable et doit être re-signé. La priorité est ce qu'un mineur est effectivement payé.

  • Relever le maximum ne coûte rien en frais — la marge de sécurité est gratuite.
  • Mais le dépôt réserve gas × max ; le maximum borne donc la part du solde immobilisée pour une étape de fold. Dimensionnez-le d'après le solde réellement disponible.
  • Un certificat à long TTL a besoin de plus de marge qu'un certificat à TTL court, parce que les frais de base disposent de plus de blocs pour bouger.

La porte de sortie pour les petits soldes consiste à réduire la fenêtre, non la marge de sécurité : signez avec un TTL court et re-signez à l'expiration, en échangeant de l'immobilisation contre de la latence. La mauvaise réponse est de garder le TTL long et de réduire le maximum, ce qui rend le certificat immobilisable exactement quand le marché bouge — le cas pour lequel la marge existait.

Règle 6 — triez les mouvements canoniquement#

Le protocole n'impose aucun ordre aux mouvements d'un transfert, si bien que sans ordre canonique une reprise du même paiement logique produit un identifiant différent et que le portefeuille ne peut pas distinguer « déjà envoyé » de « envoyé deux fois ». wallet.Transfer trie par actif, source, destination, puis montant, de sorte qu'une reprise reproduit l'identifiant.

Une reprise n'a pas à reproduire la signature : la préimage de l'identifiant exclut la liste des signatures, si bien qu'une reprise re-signée avec un nonce frais est le même identifiant et que le réseau la refuse comme le doublon qu'elle est. L'idempotence est une propriété que le protocole fournit, pour tout portefeuille plutôt que pour les seuls portefeuilles soigneux.

Règle 7 — ne communiquez jamais une graine#

La graine est la clé. Quiconque la lit possède tout ce que détiennent ses deux adresses — celle à usage unique et la persistante, sans rapport sur la chaîne mais dérivées de la même clé.

Les fichiers de clés sont toujours chiffrés : Argon2id sur la phrase secrète, AES-256-GCM sur la graine, tous deux avec leurs paramètres stockés dans le fichier, afin qu'un utilisateur privé d'accès à ce binaire puisse récupérer ses données avec la bibliothèque standard de n'importe quel langage. Il n'existe aucun drapeau pour en écrire un non chiffré. Les phrases secrètes sont lues depuis le terminal sans écho, jamais depuis un drapeau — une phrase secrète sur une ligne de commande se trouve dans l'historique du shell et dans la table des processus.

L'écriture tient deux propriétés à la fois, et toutes deux portent sur la même phrase — en perdre une, c'est perdre de l'argent :

  • Ne jamais écraser en silence. Un fichier de clé déjà présent à la destination est laissé intact et l'écriture échoue.
  • Ne jamais en laisser un tronqué. La graine va dans un fichier temporaire du même répertoire, y est synchronisée sur disque, et seulement ensuite publiée sous son nom définitif en une unique opération du système de fichiers.

Les deux tiennent sur tous les systèmes de fichiers contre lesquels la CLI a été mesurée, FAT32 et exFAT compris — les formats qu'une sauvegarde USB hors ligne a le plus de chances d'utiliser.

Un fichier de clé chiffré sur une clé USB formatée en FAT n'est protégé que par sa phrase de passe et par rien d'autre

FAT32 et exFAT n'ont pas de bits de permission Unix, si bien que le 0600 avec lequel un fichier de clé est créé n'y survit pas — ce sont les options de montage qui décident. Sous Linux, le fmask par défaut le laisse typiquement lisible par tous ; macOS monte de tels volumes en noowners, ce qui rapporte 0700 et n'impose rien. Choisissez la phrase secrète en conséquence. Ils n'ont pas non plus de journal : si une publication est interrompue sur du FAT et signale un échec, vérifiez la destination avant de relancer.

Règle 8 — refusez ce que le réseau refusera#

Un portefeuille qui rapporte un succès pour quelque chose que le réseau ne peut qu'écarter a déplacé la défaillance là où l'utilisateur ne regardera jamais. Deux formes de cela, toutes deux rencontrées sur le terrain :

  • Des octets qu'aucun pair ne peut décoder. Le codec fait autorité à part entière, avec des règles que le validateur ne redit pas. Le constructeur affirme désormais la propriété directement : quoi qu'il émette, le décodeur l'accepte.
  • Un transfert dont le fold ne peut faire qu'une omission. Un transfert supérieur au solde de la source n'enfreint aucune règle ; tout nœud l'admet donc — et rien ne refuse un producteur qui l'inclut malgré tout, après quoi le fold le règle à skip_fee, brûlé sur le dépôt et versé à personne. Le mauvais cas n'est pas « aucun effet », c'est « les frais ont été brûlés et la valeur n'a jamais bougé ».

zcd wallet send --force contourne ce refus-là et rien d'autre, parce qu'un dépôt censé arriver à l'intérieur de la fenêtre du TTL rend le même certificat applicable et que le portefeuille ne peut pas voir l'avenir. Il ne peut pas rendre valide un certificat invalide ; il peut seulement soumettre un certificat valide susceptible d'être omis — et une omission n'est pas gratuite.

Faire confiance à un nœud que vous n'exploitez pas#

zcd et le portefeuille ne sont pas des nœuds complets. Ils croient ce qu'un nœud leur dit, et une CLI qui parle RPC à un nœud qu'elle ne valide pas elle-même doit faire confiance à quelque chose dans les réponses de ce nœud — ce n'est pas un bogue à corriger, c'est ce qu'est un portefeuille. Trois atténuations, chacune pour un mensonge précis :

AtténuationCe qu'elle attrape
--devnet / --testnet / --paramsL'opérateur affirme pour quel réseau il entend signer, et chaque nœud interrogé est vérifié contre cette affirmation. L'identité du réseau n'est jamais tirée d'un nœud seul. Cela vaut aussi pour balance, et non pour les seules commandes qui signent.
--confirm-rpc NODENomme un second nœud indépendant. Le solde et le drapeau de dépense de chaque adresse doivent être rapportés à l'identique par les deux avant que quoi que ce soit ne se poursuive. Un nœud dont le chain_id diverge est refusé, et un --confirm-rpc qui nomme le point d'accès que --rpc nomme déjà est refusé d'emblée — un nœud qui se recoupe avec lui-même est toujours d'accord avec lui-même.
La confirmation saisieAvant de soumettre, zcd wallet sweep affiche les chiffres exacts et exige que vous tapiez sweep. --yes ne saute que cette invite, jamais les vérifications qui la précèdent.

Liste de contrôle pour une implémentation de portefeuille#

Si vous écrivez un portefeuille contre ce protocole, voici le contrat :

  • Dépenser depuis une 0x01 déplace la totalité du solde, en comptant la réservation de dépôt comme en faisant partie — y compris pour des programmes sans aucun mouvement.
  • RETIRE refuse une cible qui détient encore un solde.
  • Le rapport de solde d'un nœud n'est pas tenu pour indiscutable : identité du réseau affirmée par l'opérateur, seconde source recoupable, et chiffres exacts confirmés avant un balayage irréversible.
  • RefundTo est validé comme persistante ou nouvelle avant la signature.
  • La réception dérive une nouvelle adresse par paiement attendu ; l'envoi refuse une adresse à usage unique déjà créditée ou dépensée.
  • Les adresses de commerçants et de paiements utilisent 0x02 par défaut.
  • Seq s'incrémente par certificat dépendant, et le portefeuille attend la confirmation avant de diffuser le suivant — ou dit à haute voix qu'il ne le fait pas.
  • Les maxima de frais sont dimensionnés d'après le solde disponible et le TTL, et non codés en dur.
  • Les mouvements sont triés canoniquement, de sorte qu'une nouvelle tentative reproduise l'identifiant du certificat.
  • Les fichiers de clés sont chiffrés, écrits durablement sans écrasement silencieux, et les phrases de passe n'atteignent jamais un drapeau.
  • Rien n'est signé dont le propre encodage ne puisse être redécodé, et rien n'est soumis que le solde de la source ne puisse couvrir — tout contournement étant nommé, étroit, et rapporté dans l'aperçu.