Safe Network новини 🇧🇬 21.10.2021

Защо да използвате фиксирани суми при криптовалутата ни, когато плащанията ни могат да използват произволни суми? В крайна сметка тук говорим за числа, а не за парчета метал и парчета хартия. Отговорът е поверителност. Ако извършите транзакция за 4589234127 SNT, тази цифра е много вероятно да бъде уникална и следователно проследима.

Въпреки това, дори ако приемем фиксираните суми като начин за натрупване на анонимност, възниква и въпросът за ефективността. Малък брой възможни фиксирани суми (да речем 1 и 10) биха направили транзакциите изключително трудни за свързване и биха увеличили максимално заменяемостта, но с цената на неприемлива допълнителна работа за мрежата.

Проучването на @mav за транзакциите при биткойн показва, че разделянето от 8 знака след десетичната запетая е обичайна практика, вероятно защото плащанията в BTC се изчисляват въз основа на фиатни валути и са подчинени на какъвто е обменният курс в момента. Това ще бъде същото за SNT, така че не можем да очакваме идеално равномерни суми.

Екипът ни се спря на междинен подход, който позволява масивна делимост, в съответствие с транзакциите в реалния свят и не трябва да натоварва прекомерно монетните дворове.

Това е обобщение. За тези, които искат да задълбаят по-дълбоко, @danda представя нашето текущо мислене в целия му математически блясък по-долу.

Общ напредък

@chriso работи усилено, за да обедини api-то и cli-то. От към код всичко е включено, но сливането на тези хранилища означава, че се налага да адаптираме непрекъснатата си интеграция и потоците за увеличаване на версиите. Намерени са приемливи решения, което би трябвало да означава, че всяка версия на safe_network ще има всички контейнери, прикачени към нея, но със собствени различни версии (старият ни поток би означавал само една версия, което много вероятно ще доведе до объркване там). Така че сега той работи по прилагането на тези промени.

@Chris.Connelly продължи да рови в qp2p и потвърди, че изпращането на много съобщения между два възела е много по-бързо при една връзка, отколкото при много връзки. Това следва от използването на TLS от QUIC, което означава, че всяка връзка трябва да използва протокол за ръкостискане. За щастие QUIC също така поддържа много логически независими потоци през една връзка, така че тук няма реален недостатък – по-малко връзки, по-малко проблеми. Това ще доведе до последните етапи на премахване на обединяването на връзки от qp2p, където ще трябва да се постараем, за да гарантираме, че все още сме ефективни с връзките.

@Lionel.Faber работи върху подобряването на процеса за стартиране на тестови мрежи в Digital Ocean, като сега се изисква одобрение за всяка стъпка в разгръщането и премахването на тестовата мрежа, което ни позволява да рестартираме всяка стъпка или да използваме тестовата мрежа отвън, вместо тя да бъде автоматично унищожена.

Също така подобрихме стартирането на възел/клиент. @yogesh внедрява запис на мрежовата префиксна карта върху хард диска, така че да може да бъде споделяна и така клиентите и възлите ще могат да започнат с малко познания за мрежата. Добавяме и подобряваме някои локални проверки на регистрационни файлове, за да можем по-лесно да идентифицираме и проследяваме проблеми.

Добра новина от DBC лабораторията ни. Sentproofs вече работят в тестовата среда и скоро трябва да са готови за сливане.


Деноминации - обща информация

DBC монетен двор използващ слепи подписи, не може да види съдържанието на изходен DBC, преди да го подпише, но трябва да провери, че sum(inputs) == sum(outputs). Монетният двор не може да се довери на преиздаващата страна да посочи сума с изхода, защото може да е лъжа. Схемите за поемане на ангажименти са възможни, но получените преиздавания стават свързани, губейки целта на слепите подписи.

Тогава решението е монетният двор да третира всеки DBC, подписан с даден ключ, като равен на всеки друг DBC, подписан със същия ключ. Разширявайки това, можем да дефинираме набор от фиксирани деноминации, всеки с уникален ключ за подпис. Извличането на ключ ни позволява да имаме един единствен ключ от монетния двор, но детерминистично да извличаме деноминационен ключ, използвайки самото наименование като индекс на извличане. Това означава, че можем да стигнем толкова далеч, че да имаме уникална деноминация за всяка възможна стойност на u128!

Но се оказва, че това би било много лошо за поверителността и заменяемостта. Уникални суми като 4589234127 правят възможно свързването на едно преиздаване с друго. Помислете за парите в кеш за момент. С USD плащаме с хартиени банкноти от 1,2,5,10,50,100. А също и монети: .01, .05, .10, .25, .50. Когато купите нещо на цена от $365,23, можете да платите с три 100, едно 50, едно 10, едно 5, две .20 и три .01. Всяка от тези банкноти или монети е много трудна за свързване с други транзакции, защото толкова много други хора използват точно същите суми. И все пак, ако можете по някакъв начин да произведете хартиена банкнота на стойност 365.23, това е доста уникално и позволява сметката ви наистина да се открои от останалите.

Следователно можем да кажем, че всяка банкнота представлява набор от анонимност или пул, в който вашата транзакция да се скрие. Колкото повече банкноти (или групи) имаме, толкова по-малък е всеки пул. Така че по отношение на поверителност/заменяемост, ние ще се стремим към възможно най-малък брой пулове, което всъщност е 1, т.е. най-малката налична единица.

Трябва обаче да вземем предвид и ефективността. Това се измерва с броя на “монети” (банкноти или монети), необходими за извършване на промяна за дадена сума. Ако използваме нашата примерна цена в USD от $365.23, ще ни трябват 11 отделни монети. Освен това, имайте предвид, че при типично преиздаване на DBC ще има две логически изходни суми: 1: плащането на получателя и 2: промяна за подателя. Създаването, подписването и валидирането на DBC монети представлява работа за възлите на монетния двор и също така води до значителен мрежов трафик, свързан с изразходването на всеки DBC. Така че трябва да се опитаме да сведем до минимум броя на монетите за размяна, необходими за всяка транзакционна сума.

Така става ясно, че има взаимоизключване между оптимизирането за заменяемост и оптимизирането за ефективност. Оптималната заменяемост би била да има само една банкнота. Оптималната ефективност би била да има уникална банкнота за всяка възможна сума.

Трансакционни суми спрямо банкнотите

Когато мислим за банкноти (деноминации), трябва да внимаваме да правим разлика между „трансакционни суми“ и „суми на банкнотите“.

„Транзакционна сума“ е цена или сума за плащане, която се извършва по сделката. Една или повече суми на банкнотите могат да бъдат комбинирани/добавени, за да се получи „сума, която може да се изплаща“.

В бъдеще ще наричаме „сума на банкнотата“ просто „банкнота“.

Ще използваме термина „монета“, за да обозначим конкретен случай на банкнота.

Нашия първи подход към банкнотите

забележка: кодът е тук.

Първоначално дефинирахме транзакционна сума като 128-битово цяло число (u128).

След това дефинирахме серия от деноминации, базирани на степени на десет, напр.

enum {
    One, Two, Three, Four, Five, Six, Seven, Eight, Nine, 
    Ten, Twenty, Thirty, Forty, Fifty, Sixty, Seventy, Eight, Ninety,
    Hundred, TwoHundred, ThreeHundred, FourHundred, FiveHundred, SixHundred, SevenHundred, EightHundred, NineHundred,
    Thousand, TwoThousand, ThreeThousand, FourThousand, FiveThousand, SixThousand,SevenThousand, EightThousand, NineThousand,
    TenThousand, TwentyThousand, ThirtyThousand, FortyThousand, FiftyThousand, SixtyThousand, SeventyThousand, EightyThousand, NinetyThousand
}

И така нататък, до 10^38, което се доближава до границата на u128. Общият брой на деноминациите е около 340.

Също така измислихме кратки имена за всяка степен на десет, въз основа на имена с големи числа.

tau → taus
mil → mils
bil → bils
tril → trils
quad → quads
quint → quints
sic → sics
set → sets
ott → otts
non → nons
det → dets
unt → unts

Основната идея е, че „Едно“ представлява най-малката единица, например, еквивалентът в биткойн се нарича Satoshi. Изобщо няма да дефинираме (произволно) местоположение с десетична запетая, а по-скоро пазарът ще реши кой е правилният набор от единици, които да се използват за ежедневни транзакции скоро след стартирането, и с течение на времето, когато „пазарната капитализация“ се увеличава, тя ще прогресира от големи единици, например комплекти, към по-малки единици, например четворки.

В случая, десетичната запетая е насочено към потребителя (изобразяването), защото стойността е представена като u128. Така че няма да го обсъждаме повече тук.

Този подход има някои хубави свойства. Като дефинираме 1-9 за всяка степен на десет, можем да представим всяка цифра от “транзакционната сума” с една монета или по-малко, ако има нули.

Например, ако имаме „сума за транзакция“ 55034, можем да я представим с: 1 Петдесет хиляди, 1 Пет хиляди, 1 Трийсет и 1 Четири. Така сумата има 5 цифри, едната от които е нула и правим промяна за нея само с 4 монети.

Този подход има и няколко недостатъка.

  1. Първият е в известна степен незначителен. Кодът за реализиране на enum с 340+ варианта и картографирани имена е огромен и труден за поддръжка. В крайна сметка написахме скрипт за генериране на файла с изходен код denominations.rs. Макар и работещо, това е тромаво, неудобно решение.

  2. огромен брой разменни монети. В нашите тестове средният брой монети, необходими за извършване на промяна за произволно генерирани u128 „транзакционни суми“, беше 38-39. И това е за единичен логически изход. Не забравяйте, че типичното преиздаване ще има два логически изхода, може би повече. Така че средно бихме могли да разглеждаме 76+ изходни DBC на преиздаване и сравним брой входове. Това е много работа за монетния двор и мрежата и също така ще доведе до сериозна неефективност при Сляпо Подписване в сравнение с прилагането на Клиене на Сумите (което обаче е проследимо).

Например, нека използваме u128::MAX:

$ calc 2^128
        340282366920938463463374607431768211456

Имаме вариант на деноминация за всяко [0…9] от всяка степен на десет. В това число има 40 цифри. Две цифри са нула, можем да ги игнорираме. Така че имаме нужда от 38 “монети”, за да представим числото. напр.:

3*10^38
4*10^37
2*10^35
8*10^34

…и така нататък…

Ами ако използваме u64 вместо u128? Е, това е малко по-добре.

$ calc 2^64
        18446744073709551616

Но все пак е 20 цифри. И сега имаме само 19 степени от десет, с които да работим, така че потенциалната ни делимост е по-ограничена.

Сега може да се твърди, че на практика потребителите биха избрали да изпращат повече цели числа с много нули. Това обаче изглежда не е вярно. @mav направи анализ на целия биткойн блокчейн и установи, че повечето суми на транзакции използват точност от 8 цифри с предимно произволни числа. Вярваме, че това е така, защото хората все още извършват предимно плащания въз основа на суми измерени във фиат (напр. USD) и изчисляват еквивалентната сума в BTC, която се превръща в число с много разнообрезни цифри. Същото поведение изглежда ще важи и за нашите DBC.

Във всеки случай системата трябва да бъде проектирана така, че да работи адекватно добре/ефективно дори и в най-лошия случай.

Така че трябваше да намерим по-добър подход.

Нашият втори (текущ) подход

Измислихме по-просто и по-мощно представяне на деноминациите. Внедрихме десетичното степенуване в изброяването на деноминацията като цяло число със знак. Така че вместо голяма 300+ вариация, имаме следното:

pub type PowerOfTen = i8;

pub enum Denomination {
    One(PowerOfTen),
    Two(PowerOfTen),
    Three(PowerOfTen),
    Four(PowerOfTen),
    Five(PowerOfTen),
    Six(PowerOfTen),
    Seven(PowerOfTen),
    Eight(PowerOfTen),
    Nine(PowerOfTen),
}

Така че с i8 можем да представим 9*10^-128…9*10^127. Не забравяйте, че u128, който вече се счита за огромен, ни даде само 10^38 по отношение на делимост. Така че за практични цели това представяне е почти неограничено. (Лесно бихме могли да стигнем още по-далеч, като използваме i16 или i32 вместо i8, но изглежда излишно за момента.)

забележка: Това не е предвидено за обсъждане на плановете за паричната политика на SNT. То е чисто хипотетично за илюстративни цели.

Този подход към деноминациите не прави деноминацията „Едно“ равна на най-малката възможна сума, както направи първият ни подход. Вместо това „Едно“ може да представлява нашето най-добро предположение при например 1 DBC = 1 USD. Можем да се стремим „Едно“ да има някаква полезна покупателна способност по отношение на стоките от реалния свят, но не много. Например с „Едно“ трябва да може да се купи кутия сода или може би дъвка, а не къща.

Също така е интересно да се замислим какво би могло да бъде крайното парично предлагане. Просто като хипотетична отправна точка, да кажем, че приемем сумата на първоначалното DBC количество монети, като вземем текущата пазарна капитализация на SNT и я приравним на 1 DBC = 1 USD. Не сме изчислявали каква би била първоначалната сума при този метод, но нека просто приемем, че е 50 милиона. Добре, така че стартираме с деноминация Едно (1*10^0) = ~1 USD и първоначално количество монети DBC = 50 милиона. Готино. Засега ще игнорираме ефектите върху дистрибуцията/фермерството, като отбележим, че те биха могли временно да надуят/девалвират валутата.

Добре, имаме дефлационна (с фиксирано парично предлагане) валута и с течение на времето всяка единица става все по-ценна от гледна точка на реалната стойност (или проектът вероятно ще се провали). Когато това се случи, потребителите постепенно ще преминат към използване на повече 10^-1 деноминации. След това 10^-2 и т.н. Можем да поберем 128 такива 10x разширения. Можем също така да приемем изпращане на 3 милиона SNT в един dbc, например като използваме деноминацията 3*10^6. Или много, много, много повече от това, ако например дефинираме сумата от първоначалното количество монети да бъде много по-голяма, чак до 9*10^127.


Именуване

Преди бяхме измислили някои имена за големи числа, но те бяха малко неудобни и непознати. Установихме, че ги съкращаваме и променяме, за да могат да бъдат произносими. Освен това те не покриват отрицателни показатели. Но не се притеснявайте, скалата на SI влезе в помощ, с кратки имена, с които хората вече са предимно запознати.

Скалата на SI назовава 10^-24 … 10^24. Е, не всяка степен на десет, а на всеки 10^3, което е достатъчно добре. Когато ги включим в кода, можем да генерираме:

10^-24   --    1 yocto
10^-23   --   10 yocto
10^-22   --  100 yocto
10^-21   --    1 zepto
10^-20   --   10 zepto
10^-19   --  100 zepto
10^-18   --     1 atto
10^-17   --    10 atto
10^-16   --   100 atto
10^-15   --    1 femto
10^-14   --   10 femto
10^-13   --  100 femto
10^-12   --     1 pico
10^-11   --    10 pico
10^-10   --   100 pico
10^-9    --     1 nano
10^-8    --    10 nano
10^-7    --   100 nano
10^-6    --    1 micro
10^-5    --   10 micro
10^-4    --  100 micro
10^-3    --    1 milli
10^-2    --    1 centi
10^-1    --     1 deci
1        --          1
10^1     --     1 deka
10^2     --    1 hecto
10^3     --     1 kilo
10^4     --    10 kilo
10^5     --   100 kilo
10^6     --     1 mega
10^7     --    10 mega
10^8     --   100 mega
10^9     --     1 giga
10^10    --    10 giga
10^11    --   100 giga
10^12    --     1 tera
10^13    --    10 tera
10^14    --   100 tera
10^15    --     1 peta
10^16    --    10 peta
10^17    --   100 peta
10^18    --      1 exa
10^19    --     10 exa
10^20    --    100 exa
10^21    --    1 zetta
10^22    --   10 zetta
10^23    --  100 zetta
10^24    --    1 yotta
10^25    --   10 yotta
10^26    --  100 yotta

Ако някога достигнем до деноминация, по-малка от yocto, можем да измислим имена за тях. Това е за далечното бъдеще. Засега има функция Amount::to_si_string(), която просто показва експонентното представяне за неименувани стойности.


Представяне на транзакционни суми

Преди използвахме u128 за представяне на „суми, подлежащи на транзакция“. Но как да се справим със сумите, когато нашето ново изброяване на деноминации позволява стойности, много по-големи от допусканите u128 (10^38), а също така те могат да бъдат отрицателни експоненти, т.е. десети, стотни, хилядни и т.н.?

Добре, тук нещата стават малко трудни.

Това, което направихме, е да променим sn_dbc::Amount от u128 псевдоним на:

pub type PowerOfTen = i8;
pub type AmountCounter = u32;

pub struct Amount {
    pub count: AmountCounter,
    pub unit: PowerOfTen,
}

забележка: Това може да бъде преименувано на „TransactableAmount“ в бъдеще.

С тази структура можем да представим парични суми като степен на десет (представляваща единична деноминация или монета) плюс брой, представляващ броя на тези монети.

AmountCounter е u32, така че можем да представим много над милиард брой на всяка деноминация. В момента го ограничаваме до точно 1 милиард (10^9).

Стигнахме до това число, като помислихме за типичните транзакции днес. Физическите лица редовно правят плащания в брой до няколко хиляди долара, купуват къща за милион долара и т.н. Но транзакция за 100 милиона $ е доста рядка. А милиардни доларови транзакции са още по редки - говорим за огромни корпорации и правителства. Искахме да избегнем принуждаването на хората да променят своята (умствена) ценова единица за редовни транзакции. Но когато стигнем до разлика от 10^9 единици, ние сме някак в различна финансова лига.

Друг начин да видим ситуацията е, че ако Сали изпраща 1 милиард щатски долара, тя вероятно не се интересува много, ако не може да използва увеличения от 1 долар. Сали може да преиздаде 1 милиард + 10, но не може да преиздаде 1 милиард + 1 (или 2, 3, 4…). Сали вероятно би могла да преживее това. Докато ако поставим границата например на 100 и тя може да преиздаде само 110, 120, но не и 101, 102, това може да е по-голям проблем за нея… и за повечето хора.

Сега не забравяйте, че броят на цифрите в сумата определя максималния брой необходими монети за смяна. Така че, използвайки 1 милиард като наш лимит, ние спаднахме от 40 монети за обмен до 9 (максимум). Много добре!

Бихме могли да намалим това допълнително. 1 милион = 10^6, или шест монети. Това може да е разумен избор. 1 милион все още е доста висока цифра за ежедневни транзакции. Бихме могли да продължим по-ниско, като компромисът е, че хората трябва да започнат да определят суми/цени, използвайки по-високи единици.

Така че тази функция counter_limit() е ръчка, която можем да завъртим, за да балансираме между ефективността на системата и детайлността на „суми, подлежащи на транзакция“. Тестването на производителността може/ще ни насочи тук, когато напреднем още.


Изчисляване със сума

Важно е да се отбележи, че Сумата все още използва само целочислена математика. Не са включени плаващи точки.

Монетният двор трябва да провери дали сума(входове) == сума(изходи). Реализирахме някои математически оператори: checked_sub(), checked_add() и checked_sum(). Те работят чрез преобразуване на суми в обща базова единица и след това добавяне или изваждане на броя на всяка. Резултатът от всяка операция е или Amount, или Error::AmountIncompatible. Сумите са несъвместими, ако единиците са твърде далеч една от друга, за да може броят да бъде представен в Amount::counter_max() (1 милиард).

Сумата изобщо не разкрива обикновените неотбелязани оператори Add, Sub, Sum, така че е невъзможно да се извършват операции без отметка. Също така връщаната стойност е резултат, а не опция, както при вградените типове.

Кодът на монетните дворове сега извиква Amount::checked_sum() вместо sum(). С тази проста промяна Монетният двор вече налага, че всички входове и изходи трябва да са съвместими, в противен случай преиздаването се проваля.

Възможно е също така да конвертирате сума в рационално число и обратно. Това е внедрено, но ще отиде в отделно хранилище, тъй като не е необходимо за монетния двор или клиентските операции.

Последици от използване на входове

„Ограничението на близостта“ от 10^9 за Суми се прилага както за входове, така и за изходи при повторно издаване.

Входовете и изходите обаче се сумират отделно. По този начин може да се окаже, че отделните вход и изход няма да бъдат съвместими сами по себе си, но сумата от входовете и сумата от изходите все още са равни една на друга. Ето един пример за това:

inputs:
    9*10^8, 1*10^8, 9*10^0, 1*10^0
outputs:
    1*10^9,1*10^1

$ calc 9*10^8+1*10^8+9*10^0+1*10^0
        1000000010
$ calc 1*10^9+1*10^1
        1000000010

Така че това преиздаване ще бъде успешно.

Въпреки това границата на близост ограничава входовете и изходите да не могат да бъдат твърде далеч един от друг, особено ако наложим ограничение за броя на входовете и изходите. По този начин може да се счита за подобно (най-общо) на ограничението на праха в биткойн, с изключение на това, че винаги е относително спрямо останалите суми в преиздаването, така че никъде няма фиксиран лимит, което е приятно. :wink:

Тази граница на близост трябва да стимулира хората да извършват сделки с други, използвайки най-често използваните деноминации. Защото монетният двор не би им позволил да платят за сода с 1 DBC плюс 1 Pico DBC (10^-12). Понастоящем не налагаме максимален брой входни DBC за преиздаване, но това също би било нещо, което трябва да се вземе предвид, което би направило невъзможно плащането със стотици или хиляди малки монети, вместо с няколко монети с разумен размер.


Заключителни мисли

Фактът, че нашият първоначален подход за деноминация изискваше голям брой монети, за да представим произволни суми, беше сериозен недостатък, който беше необходимо да се преодолее, за да може монетният двор за сляпо преиздаване да има шанс да бъде почти толкова ефективен, колкото и неслепения монетен двор.

Така новия дизайн е подобрение на оригиналната деноминационна система по отношение на ефективност, яснота и (може да се каже) простота на кода.

Въпреки че концепцията TransactableAmount на пръв поглед може да изглежда необичайна, тя трябва да бъде предимно прозрачна за потребителите. Първоначално всички суми на транзакции под 1 милиард DBC биха били просто изразими като цяло число (използвайки база 10^0). Само ако човек надхвърли тази сума или трябва да падне под 1 DBC, тогава човек трябва да избере друга единица, в която да изрази сумата и софтуерът на портфейла може да направи това автоматично.

Дълбоката делимост е чудесно свойство, което позволява малки микроплащания и затова е щастлив инцидент на дизайна ни. 128 цифри за делимост е огромно число и бихме могли да отидем много по-далеч с i16, ако желаем. За сравнение, биткойн има 8 десетични цифри на делимост и повечето криптовалути днес са подобни. 12 знака след десетичната запетая се считат за големи.

Границата на близост е нова концепция, която трябва да помогне за минимизиране на праха и увеличаване на заменяемостта, без да се изисква ограничение за прах с фиксиран размер. Това също е ръчка, която лесно можем да завъртим, за да настроим между ефективността и степента на пренасяните суми.

Всеки, който се интересува от повече подробности, може да прочете изходния код. amount.rs съдържа дълъг коментар/обяснение.

code:


Преводи:

:uk: English :ru: Russian ; :de: German ; :es: Spanish ; :fr: French

  • Подробна информация може да намерите както винаги във форума на международната общност: Safe Network Forum
  • Ако имате въпроси може да ги зададете във Facebook групата на българската Safe общност: https://www.facebook.com/groups/SafeNetworkBulgaria/
  • Ако искате да следите последните новини заповядайте във Facebook страницата на Safe Network България: https://www.facebook.com/SafeNetworkBulgaria/