Sans frais de transaction, comment résister aux attaques

L’attaque sera dans ce cas limitée au nombre de Ğ1 possédées, à raison de 1 Ğ1 par compte. Tandis que dans mon exemple il n’y a aucune limite.

Ceci dit, c’est un fonctionnement “normal”, légitime, tout utilisateur a le droit de répartir ses Ğ1 et les faire circuler. Cela peut s’assimiler à une utilisation plus intensive de la blockchain. Tant que cela n’empêche pas les autres utilisateurs de faire leurs virements, il n’y a aucun problème en fait (à part pour ceux qui stockent la blockchain).

@cgeek ok je veut bien implémenter ça dans la 1.6 mais quelqu’un peut t’il résumé les règles a mettre en place ?

Juste pour éviter toute ambiguïté : je t’ai mis en copie pour que tu suives le problème, car il me semble important. Tu peux le faire si tu veux, mais on en est encore au stade de la réflexion pour l’instant, avec propositions/réfutations.

Il faudra ensuite spécifier cela techniquement dans le document de protocole.

1 Like

merci c’est bien ce qu’il me semblais a la lecture il n’y a pas encore de conclusion précise sur les règles a coder !

Ok alors je résume les idées intéressantes qui se sont dégagés :

  1. Limiter les transactions chainés à une profondeur maximale de 5
  2. Prioriser les transactions qui réduisent le nombre de sources.
  3. Prioriser les transactions a plus gros montant
  4. Fixer des quotas par tranches de montants

Voici donc une proposition de spec :

Créer des quotas par tranche ET par issuer :
Tranche 1 : montant total inférieur à 3 DU courant : maximum 1 transaction par bloc et par issuer
Tranche 2 : montant total inférieur à 10 DU courant : maximum 2 transactions par bloc et par issuer
Tranche 3 : montant total entre 10 et 100 DU courant : maximum 3 transactions par bloc et par issuer
Tranche 4 : montant total supérieur a 100 Du courant : maximum 5 transactions par bloc et par issuer

Lorsqu’une transaction contient plusieurs issuers le compteur de chaque issuer est incrémenté, s’il existe un seul issuer pour lequel le compteur dépasse le quota alors le bloc est invalide.

Chaque transaction compte pour 1, qu’elle soit chainée ou pas. Notez la cohérence avec la profondeur max pour les transactions a gros montant !

Enfin , un attaquant qui tenterai plein de micro-transactions en parallèle serait limité a 1 transaction par clé (sauf a dépasser les 3 DU courant mais ça commence a faire des fonds…).

Point important : pour éviter qu’un spammer ne squate les blocs pour empecher les autres d’échanger, je ne propose aucun limite sur le nombre d’issuers ni sur le nombre de transactions totales, la règle de la taille limite des blocs me semble déjà bien.

Une dernière chose : je serais favorable a ce que l’on limite le nombre max d’input d’une transaction à 64, dans les faits les clients respectent déjà cette règle et chaine au delà de 40 inputs. Idem pour le nombre max d’output, afin d’empêcher une attaque par transactions prenant trop de place dans le blocs.

a noté que ces propositions concernent exclusivement les règles blockchain, et ne remplacent pas le besoin de fixer des quotas par piscine déjà prévu ici : https://git.duniter.org/nodes/typescript/duniter/issues/1152

3 Likes

Quand je parlais de tranches, ce n’était pas par rapport aux montants mais à la taille en octet des transactions. Il n’y a pas de problème de scaling entre une transaction de 1 G1 et une de 10 kG1. Il y en a par contre une entre une transaction de 100 octets et une de 20000. C’est là qu’il doit y avoir un limite, et un système de tranches (ou encore mieux une notion de quota selon une fonction continue de la taille) me semble nécessaire.

EDIT : Après relecture en effet il est plus simple de spammer avec 1 G1 que 10k :stuck_out_tongue: Par contre il faut aussi penser au payload de la transaction, qui ne contient pas uniquement la somme d’argent mais aussi les conditions de dépenses, qui pourraient augmenter de taille avec le RFC2. C’est dans ce cas là que des tranches en poids de transaction me semblent importantes.

Les spec que je propose s’appliquent au protocole actuel. De plus, quelque soit les conditions d’output il y a toujours un montant total d’inputs nécessairement connu et certain au moment de l’écriture de la transaction, alors la seule faille qu’il restera dans le protocole binarisé c’est la possibilité d’un output au script trop gros, dans ce cas le plus simple me semble être d’imposer une taille binaire maximale pour chaque output OU par transaction.

La taille maximale devrait être dynamique et évoluer dans le temps pour ne pas empêcher de nouveaux usages qui pourraient nécessiter de plus gros contrats. On ne peux pas encore savoir tout ce qu’il sera possible de faire avec, alors limitons le uniquement en fonction du l’usage fait.

Ok je ne maitrise pas du tout cette partie, peut tu proposer une spec anti-spam ?

J’ai avait parlé dans la review de la RFC1. On désigne un certain nombre de tranches par poids (en octets) de la transactions. Puis pour chaque bloc on autorise un certain pourcentage du nombre de transactions.

Par exemple, imaginons les tranches 0 à 100 octets, 101 à 300, 301 à 700, 701+.
On peut avoir dans un bloc au maximum respectivement 30, 10, 5, 2 transactions.

L’idée est d’observer la tendance depuis un certain nombre de blocs pour ajuster les bornes des tranches ainsi que leur capacité. Si la tendance depuis disons 50 blocs est une augmentation progressive des transactions lourdes, on peut en autoriser quelques-unes de plus voir déplacer la borne entre les 2 derniers paliers vers le haut.

On a alors les règles suivantes :

  • une tranche est très remplie, on augmente légèrement son nombre max.
  • une tranche est peu remplie: on diminue légèrement son nombre max.
  • deux tranches adjacentes ont un taux de remplissage trop différent, on décale légèrement la borne commune pour diminuer cet écart. Avec ça on pourrait avoir des limites qui s’adaptent automatiquement à l’usage la monnaie à “moyen terme”, tout en bloquant les spams (a court terme).

J’avais déjà présenté ce système pour les poids, mais on peut aussi le proposer pour les montants (en combinaison avec les poids). Pour qu’une transaction soit mise dans un bloc, il faut qu’elle puisse rentrer dans sa tranche de montant et de poids. Les 2 systèmes seraient indépendants pour vraiment refléter l’usage des utilisateurs de la monnaie.

Enfin il faut toujours laisser un peu de place pour l’évolution. Il faudrait définir des limites minimum au nombre de transactions par tranches, ainsi qu’a la taille minimum de chaque tranche; pour qu’il soit toujours possible de publier de gros scripts pour de nouveaux usages.

On peut aussi envisager de faire ces limites non pas au niveau de blocs de 5 mins mais dans une durée plus grande. 5 minutes c’est assez court et une transaction très lourde toutes les 5 minutes pourrait être problématique. On pourrait alors utiliser des nombres a virgule, par exemple 0.5 transactions pour la plus grosse tranche, qui autorise alors uniquement une transaction lourde tous les 2 blocs.

Je pense qu’il y a moyen de faire quelque chose de très propre avec ce système, peut-être as-t-on l’a une belle RFC à rédiger :slight_smile:

2 Likes

Dans les idées intéressantes, je pense qu’on peut ajouter l’augmentation de taille du bloc de façon logarithmique, qu’en penses-tu ? Car il est vrai qu’une augmentation exponentielle telle que permise aujourd’hui n’a d’une part pas beaucoup de sens, et permet une vulnérabilité.

2 Likes

Je pense que c’est a part, c’est cependant une bonne idée, on peut créer y faire un ticket dédié :slight_smile:

Fait : https://git.duniter.org/nodes/typescript/duniter/issues/1244

1 Like

Je vais faire une RFC de mon idée de quotas pour que ça soit plus clair et que vous puissiez me donner votre avis.

1 Like

Alors en fait il se trouve que, en tentant d’implémenter cette fonctionnalité, je découvre durant les tests que nous sommes en fait déjà protégés par un bug : il n’est pas possible d’avoir de transactions chaînées au sein d’un même bloc aujourd’hui, Duniter fait en sorte de les étaler à raison de 1 transaction par bloc.

Mais c’est un bug :slight_smile: (c’est la BR_G46 du fichier indexer.ts : en fait on ne regarde pas les sources du bloc, donc les transactions chaînées n’ont pas de source justifiée).

Je compte le corriger et implémenter la règle de profondeur maximale de 5 tout de même pour la 1.6. J’ai déjà codé le truc et possède de tests qui passent bien, il nous suffira ensuite de décider du moment d’activation de la fonction.

2 Likes

Suite aux discussions concernant le Whitepaper et le spam de transactions, je suis arrivé à une proposition qui me semble possible pour empêcher le spam, hors protocole. Cette proposition nécessite la création d’un nouveau noeud ou d’une option sur les noeuds existants. Elle se base simplement sur une priorisation des tx acceptées en piscine en fonction de la distance TdC au membre calculant.

Ce n’est évidemment qu’un brouillon. Il y a certainement des points que je ne maîtrise pas, par exemple une limite de la taille des Whitelist.


Etat actuel et prévu

  • Duniter priorise déjà l’enregistrement des tx de la clef publique du noeud
  • @elois prévoit d’implémenter dans DuniTrust la possibilité d’ajout d’une Whitelist
  • Les discussions que j’ai pu avoir autour des mesures anti-spam non-protocolaires montrent que les tx qui seraient priorisées sont celles concernant des pubkeys « proches » des membres calculant (abonnement, cotisation à la transaction, noeud communautaire, etc.).

D’où mon interrogation : est-il possible d’utiliser la proximité avec le membre calculant dans la TdC pour prioriser des transactions légitimes par rapport à des transactions de spam ?

Lexique

Whitelist : liste de pubkeys dont on fait passer les transactions entrantes et sortantes en priorité. Elles sont enregistrées dans la pool, quitte à élaguer des tx moins prioritaires.

Whitelist « arbitraire » : est entrée directement par l’admin du noeud sur des critères arbitraires, par exemple une whitelist sur abonnement est « arbitraire ».

commercer : envoyer de la monnaie (non en recevoir)

Proposition initiale

  • je suis membre et je possède un noeud

  • j’ai envie de vivre dans un environnement économique fluide

  • donc je vais exécuter un script régulièrement sur mon noeud, qui va mettre sur ma whitelist :

    • toutes les pubkeys avec lesquels j’ai commercé depuis 1 an
    • toutes les pubkeys avec lesquels les membres à 1 pas de moi ont commercé depuis 1 an
  • Une autre solution, plus sujette à spam, serait de whitelister :

    • toutes les pubkeys avec lesquels j’ai commercé depuis 1 an
    • toutes les pubkeys avec lesquels eux-mêmes ont commercé depuis 1 an

… ainsi, l’environnement économique dans lequel je me trouve a au moins un noeud où les tx sont prioritaires. Le spam n’est pas évité à l’échelle du réseau, mais mon environnement proche en est un minimum protégé. Si des noeuds avec une telle option appartiennent à des membres bien répartis sur le réseau, alors la G1 est protégée du spam par la TdC :wink:

Inconvénient : la whitelist peut être très grosse. Mais :

  • le délai choisi (1 an) pour faire partie de la whitelist peut tout à fait être réduit.
  • on peut choisir les X dernieres pubkeys avec lesquelles ces comptes ont commercé.
    • Par exemple, je whitelist les 100 derniers comptes vers lesquels moi ou mes contacts WOT directs ont envoyé de la monnaie -> ma whitelist est limitée à maximum 10000 pubkeys.
    • Je whitelist les 100 dernieres pubkeys avec lesquelles j’ai commercé, et les leurs -> pareil, limite à 10000.

Evolution de la proposition et généralisation

Si je possède un petit Rasp qui calcule 1 blocs/semaine (~50 tx), je priorise ainsi ~200 tx/mois pour mon environnement proche. C’est ridicule pour 10000 pubkeys. Donc il faut re-prioriser, et si possible généraliser à tout le réseau calculant. Tous les noeuds qui le décideraient auraient plusieurs whitelists de priorités différentes, mises à jour régulièrement (chaque semaine ?) :

  • priorité 0 : mes tx
  • priorité -1: les tx de la Whitelist arbitraire
  • priorité -2 : les tx des X derniers comptes avec lesquels j’ai commercé et des membres à 1 pas
  • priorité -3 : les tx des X derniers comptes avec lesquels ils ont commercé et des membres à 2 pas
  • priorité -4 : pubkeys avec lesquelles les membres à 2 pas ont commercé (pas certain de ça) + membres à 3 pas (on se base desormais sur les comptes membres et plus sur les comptes avec lesquels j’ai commercé)
  • etc…
  • les pubkeys qui n’ont reçu de transactions depuis aucun compte membre (ni émis vers) passent en dernier si elles ne font pas partie d’une whitelist arbitraire.

On a ainsi plusieurs whitelists, basées sur la TdC, qui priorisent les tx en fonction de leur proximité directe avec un compte membre. Un.e attaquant.e qui voudrait spammer le réseau devrait se rapprocher des membres pour pouvoir enregistrer ses transactions, et pourrait être ainsi identifié.e (et blacklisté.e). Le passage par un anonymiseur la ferait s’éloigner des comptes membres. Ses tx passeraient en dernier, sauf si elles sont destinées à un compte membre, et dans ce cas soit l’attaquant.e perd des unités, soit iel est démasqué.e.

… Et si un.e attaquant.e ne peut pas bloquer la majorité des transactions légitimes, il est probable qu’il n’y ait pas d’attaque spam du tout et que les transactions non prioritaires (typiquement vers/entre des comptes anonymes) puissent passer. En revanche, en cas de surcharge légitime du réseau, les systèmes de whitelist ou de paiement à la transaction seraient d’actualité.

Inconvénient 1 : cela suppose de créer des Whitelist contenant de très nombreuses pubkeys. Je n’ai aucune idée de la place que cela représente ni du temps de calcul.

Inconvénient 2 : cela favorise les membres proches (TdC) d’un membre calculant.

Je trouve cette solution assez élégante, en toute subjectivité :wink: @Salome_Iris, en demandant des précisions sur le spam, a fait un Ğtravail remarquable :wink:

Est-ce que les transactions vers les portefeuilles vierges auraient toujours facilité d’entrée ? Elles semblent prises en compte par cette proposition.

Dans la théorie, l’idée d’utiliser la WoT est bonne. Mais dans la pratique, un nœud devra faire plein de calculs de distance WoT à chaque entrée de tx. C’est assez lourd comme processus pour un nœud. Et le nombre de tx va augmenter. Il y a un débit assez immense sur le Bitcoin, et ne parlons pas sur le réseau des UNL.

@matograine tout ça me semble bien compliqué et peut être dommageable pour certains usages (anonymisation notamment).

Mais qu’importe, ma seule exigence est que le protocole reste neutre sur cette question.
Du coté de Dunitrust, ce que je peut proposer, c’est que Dunitrust consomme un fichier JSON a fréquence régulière contenant la whiteliste (fréquence définissable par l’admin du nœud).
Ainsi, vous serez libre d’expérimenter ce que bon vous semble sans que cela ne soit géré par la codebase de Dunitrust :slight_smile:

Non @Moul, les nœuds ne gèreront pas ça (pas Dunitrust en tout cas), ça devra être fait par des taches cron mises en place par l’utilisateur qui produiront un fichier JSON que Dunitrust consommera régulièrement.
Ensuite, libre aux contributeurs de coder ces taches cron en python, en bash, ou que sait-je encore :wink:

Si elles sont émises par un compte membre ou ayant reçu une tx d’un compte membre, elles sont dans la whitelist du compte émetteur.

Non, il calculerait régulièrement (chaque semaine, mois ?) les whitelist. Mais oui, c’est lourd, ça implique de stocker un grand nombre de pubkeys.

(j’entends par “noeud” le logiciel DUP et l’ensemble des processus directement liés à son fonctionnement. Par exemple, pour moi les scripts de redémarrage de Jytou font partie de mon noeud.)

Youpi \o/