Ресурсы: техническое описание TLS, LaTeX - в картинки (img), криптографическая библиотека Arduino, шифр "Кузнечик" на ассемблере AMD64/AVX и ARM64
Кстати, в продолжение записки про геометрическую алгебру Евклида и разность квадратов – воскресное чтение манускриптов (давно не было). Как известно, основная часть трудов Евклида дошла до нас лишь в средневековых “копиях копий”. То есть, на манускриптах. Один из важнейших таких манускриптов – Vat.gr.190 из Ватиканской апостольской библиотеки, датируемый девятым веком нашей эры и содержащий тексты “Элементов” (“Начал”). Фрагмент с записью формулировки Предложения 5 из второй книги на древнегреческом приведён на скриншоте ниже.

За исключением комментариев на полях и между строк (схолии), это как раз тот текст, который упоминается в исходной записке (плюс пара строк начала доказательства; сама формулировка заканчивается словом τετραγώνῳ во второй строке снизу на скриншоте, а схолии легко распознать, – не не прочитать, – благодаря другим чернилам и почерку).
А вот чертёж из манускрипта, соответствующий этому Предложению, почему-то подкачал – см. следующий скриншот.

Здесь и точка Γ не делит прямую линию на равные части, и Δ прижата не к той границе. Казалось бы, для геометрии это не так уж важно, смысл не меняется, но всё же.
Комментировать »
Древнегреческий подход к математике включал в себя геометрическую алгебру. Основные инструменты этой алгебры описаны, – с доказательствами, – во второй книге “Элементов” (“Начал”) Евклида. Очень хороший пример – Предложение 2.5 (то есть, пятое Предложение второй книги). Там записано следующее:
“Если прямую линию разделить на равные и неравные [отрезки], тогда прямоугольник, сформированный равной и неравной сторонами, плюс квадрат на стороне между [ними], равны квадрату на половине”.
“На половине” – тут имеется в виду квадрат на “равной” части исходного отрезка. Рисунок дан ниже – из него должно быть понятно.

“Прямая линия” у Евклида оканчивается точками, по определению. Сейчас сказали бы, что это отрезок. Но в древнегреческой математике – речь не об отрезке, а о некоторой величине, с которой связаны свои операции. Подход напоминает древние шумерские задачи. И там, и тут – для величин-отрезков вводится своё умножение: два отрезка дают площадь – величину нового типа. Обратите внимание, что операции, вообще говоря, различаются: умножение отрезков позволяет получить площадь, но это не то же самое, что и умножение натуральных чисел (“повторное сложение”, как говорят сейчас). Поэтому получается геометрическая алгебра.
Что именно здесь записано у Евклида? Разделим линию AB точкой C на равные части (половины: AC == CB), и точкой D на неравные (D, например, лежит на отрезке CB, см. рис.). Тогда утверждается, что AD*DB + CD*CD = CB*CB.
Воспользуемся современными алгоритмами и перепишем выражение: CD*CD – CB*CB == AD*DB. Дальше (см. рис.), по построению: AD == CB + CD; DB == CB – CD; следовательно, CD^2 – CB^2 == (CB+CD)*(CB-CD).
То есть, это геометрический вариант известного алгебраического соотношения – разности квадратов. Аналогичные параллели есть и у всех прочих Предложений второй книги “Элементов”.
Комментировать »
Кстати, в продолжение недавней заметки про то, что тип float – это не про числа, тем более, не про действительные числа. С точки зрения рекомендаций разработчику ПО.
Вообще, если получается, то никаких float и тому подобных инструментов лучше не использовать. Совсем. Тем более, в воплощении различных sin, cos и прочих. Заменять нужно таблицами целых (в смыcле int) значений и целочисленными, со строго фиксированной справа точкой, арифметическими операциями. Особенно, если речь о программировании систем управления и микроконтроллеров.
Если же не получается совсем отказаться, то есть эффективный способ правильно думать про float. Нужно переменные с типом данных float понимать как алгоритмы, и сравнение таких переменных интерпретировать как сравнение алгоритмов. Это помогает избежать многих ошибок (см. ниже).
Например, во float нет дистрибутивности. В алгоритмической интерпретации это означает, что выражения L := b*(c + d) и L := b*c + b*d – присваивают переменной L разные алгоритмы. И действительно, запись, в которой сперва вычисляется сумма (c + d), а потом результат умножается на b, это другой алгоритм, нежели вариант, когда сперва b умножается на d и b умножается на с, а потом вычисляется сумма результатов (обратите, кстати, внимание, что тут ещё и порядок играет важную роль: сначала b*c или сначала b*d? если с точки зерния параллельных вычислений эти операции могут быть выполнены “независимо” разными потоками, то с точки зрения компилятора, имеющего дело с вполне себе последовательной записью, всё может выглядеть сильно иначе – но это явно тема для другой записки).
Если не упускать этот алгоритмический момент из виду, то оступиться становится сложнее. Так, алгоритмическое восприятие float позволяет отбросить сомнения, что в двух описанных выше случаях из L можно достать разное битовое значение при одних и тех же входных переменных – алгоритмы-то там разные. Естественно, сравнивать алгоритмы сложно, но тут понятие об алгоритме – это лишь средство обобщения, мыслительный гаджет, но такой гаджет, который верно работает.
Лирическое отступление. Это раньше к компьютерными вычислениями подходили с нужной тщательностью и пониманием того, что такое преобразование погрешностей. Сейчас, оказывается, времена ИИ и LLM. Поэтому сказку сильно сократили. К сожалению, уже на практике полагают, что во float – действительные числа, “просто с погрешностью” (“просто”, да). Соответственно, получается, что конкретный математический аппарат, служащий основой проектирования алгоритмов ПО, оперирует действительными числами, синусами и косинусами – здесь всё со всем пересекается, всё максимально гладкое, а поэтому выполняются нужные равенства и теоремы существования. Потом математический аппарат прямо переносят в программный код. После чего, в один не самый ожидаемый момент, вычисления ломаются и предположим, аппарат космический улетает совсем не туда, куда предписывала гладкая и непрерывная модель с синусами и дистрибутивностью.
Комментировать »
Историческая перспектива шумерской математики радикально отличается от перспективы математики древнегреческой: если для древнегреческой до нас дошло немало общетеоретических трудов, но зато все в средневековых пересказах, то от древних шумерских математиков общетеоретических документов не найдено совсем, однако есть немало практических и, видимо, учебных материалов, которые сохранились на глиняных табличках. Эти таблички датируют вторым и третьим тысячелетием до н.э. То есть, это прямые свидетельства, а не пересказ. Например, считается, что Евклид работал около 300 года до н.э. А это на две с лишним тысячи лет позже, чем шумерские таблички. При этом среди сколь-нибудь полных записей “Элементов” Евклида самый древний известный экземпляр – это девятый век. Но то девятый век нашей эры, то есть, ещё более тысячи лет спустя. (Фрагменты “Элементов”, естественно, есть и намного старше.)
Евклид для современной математики гораздо важнее, чем задачи с глиняных табличек. Но, так или иначе, есть и у табличек преимущества: едва ли не в каждой современной книге, где приводится рассказ про квадратные уравнения, рассказ этот начинается с того, что квадратные уравнения решать умели ещё древние шумеры, от которых соответствующая математическая теория перешла к древним вавилонянам. Всё это, как минимум, 3600 лет назад. На древних глиняных табличках есть разборы задач, позволяющие посмотреть, насколько те методы отличались от современных.
Вообще, на табличках до нас дошли некоторые пошаговые алгоритмы, которые всегда показаны на численных примерах. То есть, дан детальный и максимально конкретный разбор метода решения задачи в числах. Но как именно сами методы были разработаны – неизвестно, потому что теоретических работ не найдено. Да, разумно будет предположить, что за такой разработкой стояло более абстрактное понимание: вряд ли решение задач, приводящих к квадратному уравнению, было получено перебором алгоритмов. В сохранившихся задачах квадратные уравнения точно есть (см. ниже), но нет, например, абстрактных сведений о корнях таких уравнений и отношениях между корнями.
Разберём один такой конкретный пример математической задачи с глиняной таблички YBC 6967/P255041. Табличка датируется 1900-1600 до н.э., этот период называют “старовавилонским”. Фотографии четырёх её сторон приведены ниже. Небольшая часть клинописных знаков находится на боковушках таблички, но для наших целей это не важно: в этот раз мы не будем расшифровывать сами надписи по отдельным клинописным знакам – главное, что на табличке нет чертежа, а только текст (но осталось место, на котором мог быть быть чертёж).

(Источник: Yale Peabody Museum, YPM BC 021031; картинка есть в большем разрешении.)
Прежде чем читать перевод текста, нужно согласовать некоторые базовые термины шумеро-вавилонской математики. По сравнению с современным сеттингом, есть ряд важных особенностей.
Во-первых, древние шумерские задачи часто ставятся для пар специальных чисел, которые называют “обратными”. Такие числа используются и в типовых задачах с “квадратными уравнениями”, и на табличках с таблицами умножения. Соответствующие клинописные термины обозначают igi-bi и igi. А именно, это такие парные числа, произведение которых равно степени числа 60. 60 является основанием системы счисления, поэтому обратными будут: 4 и 15, 3 и 20, 6 и 600 и т.д. Это система с плавающей точкой, поэтом 60 тоже может обозначаться единицей (откуда и термин “обратные”: это ведь и есть обратные числа, в том смысле, что их произведение обозначается цифрой 1). Данный подход используется и в задаче на рассматриваемой табличке.
Почему, всё же, 60 – это единица? Потому что система счисления позиционная и шестидесятеричная. Например, в десятичной системе обратным к 2 можно назвать 5, потому что 2*5 это 10. Но десять, вроде бы, не единица? Да, если говорить о числе, но не совсем так, если говорить о системе счисления. Если десятичная точка плавающая, то отличить единицу от 10 или от 100 будет сложно. Если мы перемещаем точку вправо, то из 5 получается 0.5, а это уже точно 1/2 – то есть, обратное к числу 2 в привычном смысле алгебры умножения.
Шестидесятеричная система более удобна для точных расчётов, чем десятичная – подробнее про шестидесятеричную систему можно прочитать в отдельной заметке, а здесь все числа, для удобства, будут даны в десятичной записи.
Во-вторых, шумеро-вавилонская арифметическая система использует разные типы операции умножения рациональных чисел. Помимо привычного сейчас “пошагового” умножения “через повторное сложение” используется “геометрический” вариант, в котором два числа соответствуют длинам сторон прямоугольника (квадрата, в случае нашей задачи), а произведение – это площадь прямоугольника (“поверхность”), которая рассматривается именно как площадь, в двумерном евклидовом смысле. Именно этот, второй механизм используется в рассматриваемой задаче. Вот только тут геометрическая интерпретация лишь подразумевается, поскольку чертежей не приводится.
Казалось бы, какая разница, как определять умножение натуральных чисел? Результат же будет одинаковым, разве нет? Но это не так, результат не будет одинаковым. И не только в современной математике, но и в шумеро-вавилонской. Одинаковым, возможно, будет число, но вовсе не сам результат, который имеет тип. Это прямо влияет на сочетание используемых операций. В шумеро-вавилонском методе ещё и используются преобразования, которые, из-за их геометрической сути, просто невозможны для абстрактных чисел: например, ниже мы “присоединяем” одно число к другому – а это означает “склеивание” прямоугольников, когда сумма – это результрующий прямоугольник. То ли для древних вавилонян числа не были достаточно абстрактными понятиями, то ли это следы дидактического приёма, а уровень абстракции, напротив, был гораздо выше – точно не известно: опять же, всё потому, что не найдено теоретических работ.
Так или иначе, но геометрическая интерпретация прямо используется в операциях “присоединения” и “разбиения”. То есть, подразумеваемого объединения или разделения именно геометрических фигур на плоскости. Поэтому шумерские методы решения квадратных уравнений сейчас сплошь и рядом иллюстрируют чертежами. В конце этой заметки тоже будут чертежи – они, действительно, облегчают понимание. Но необходимо учитывать, что в самой шумеро-вавилонской “математике глиняных табличек” геометрические свойства интерпретировались именно через числа (напротив, в древнегреческой математике – это совсем не так). И числа “одномерного типа” у шемер превращаются в числа “двумерного типа”, когда отрезки образуют стороны многоугольника. Обычно, это прямоугольник. Для этого есть специальная операция. Превращение двух “чисел-отрезков” в одно “число-площадь” дальше будем называть “восстановлением”: а именно – 15 и 4 восстанавливаются в единицу (то есть, в 60).
Вернёмся к табличке. Набранный на ней текст содержит формулировку задачи с пошаговым объяснением того, как эту задачу решать.
Упрощённый перевод, где в скобках даны мои комментарии (адаптировано из Jens Høyrup, Algebra in Cuneiform, 2013):
Из пары обратных чисел (то есть, те самые igi-bi и igi), одно превышает другое на 7.
Чему равны эти обратные числа?
Возьми 7, на которое одно число другое превышает, и разбей на две части: 3½. (Здесь “разбей” – нужно интерпретировать как деление отрезка пополам.)
Восстанови два 3½, результат: 12¼. (А это и есть геометрическая операция получения квадрата на двух равных отрезках, задающих стороны.)
Присоедини 12¼ к исходной площади, равной единице: 72¼. (Обратите внимание: единица здесь – есть первая степень 60 в терминах шумеро-вавилонской системы счисления, то есть, 60 + 12¼ == 72¼.)
Чему равно 72¼? Это 8½. (“Равно” здесь – это обратное геометрическое преобразование, от квадрата, как фигуры, к стороне: то есть, сторона квадрата с площадью 72¼).
Теперь отметь 8½ и соответствующее 8½. (В смысле сторон квадрата.)
3½, элемент площади, отсоедини и присоедини на место. (8½ – 3½ == 5; 8 + 3½ == 12; заметьте ещё раз: (8½)^2 – (3½)^2 == 60.)
Первое число – 12, второе – 5.
12 есть одна часть пары, 5 – вторая. (Те самые igi-bi и igi.)
Попробуем не рисовать чертёж, а сразу понять, какая тут задача, в числах. Первая строка условия говорит, что произведение некоего числа на сумму этого же числа и 7 равно 60. Собственно, если обозначить искомое число x и записать всё в формулах современной алгебры, то и получаем квадратное уравнение.
x(x+7) == 60 x^2 + 7*x == 60 x^2 + 7*x - 60 == 0
Как такую задачу решает школьник сейчас? Очень просто – что называется, не задумываясь:
x^2 + 7*x - 60 == 0, D == 7^2 - 4(-60) == 289, (x_1, x_2) == (-7 +/- √289)/2 = (5, -12). Ответ -12 - не подходит (кстати, почему?); выбираем ответ 5. У нас прямоугольник, одна сторона равна 5, вторая, по условию, 5 + 7 == 12. Ответ: 12 и 5.
Но на исходной табличке нет дискриминанта, там, в современных обозначениях, записано несколько другое преобразование:
Исходная площадь 60, то есть, "единица", которую обозначим q. Пусть p - разность сторон (второе число на 7 больше, то есть, p == 7). x(x + p) == q Тогда p/2 это половина разности (7/2). (x + p/2)^2 == q + (p/2)^2 - то есть, строим больший квадрат, его площадь равна 72¼ (см. правую часть).
Дальше, по табличке, – “геометрически” извлекаем квадратный корень из левой части: то есть, определяем, что отрезок x + p/2 равен стороне квадрата с площадью 72¼. А именно: 8½.
Если переписать в современных обозначениях, то получим то, что принято называть выделением полного квадрата. Именно так и выводится формула для решения квадратного уравнения:
(x + p/2) == sqrt(q + (p/2)^2), x == -p/2 + sqrt(q + (p/2)^2).
Здесь p, q это коэффициенты из x^2 + p*x + q == 0.
Выходит, квадратные уравнения в образовательных целях решают, как минимум, 3600 лет. Cкорее всего – ещё дольше. Это тем более удивительно, что утилитарное применение, – так сказать, в быту, – для квадратного уравнения найти непросто и сейчас (попробуйте найти), что уж там говорить про старовавилонские города и фермы. Пример на табличке больше похож на синтетический элемент некоторой эзотерической практики, чем на задачу, скажем, для земелемера. А вот внутри математики, конечно, квадратные уравнения просто незаменимы. Так что теоретический аспект в шумеро-вавилонской математике должен был существовать, но его следы, почему-то, потеряны. Может быть, записей теории было очень мало, и они выполнялись не на глиняных табличках.
Особенно интересно, что в древнем вавилонском царстве, судя по всему, уже были специально обученные инженеры-информатики, которые проводили ответственные вычислительные операции, используя достаточно универсальные алгоритмы. Считается, что класс подобных задач в древневавилонских методических материалах не требовал чертежа: древние инженеры должны были разбираться с геометрической алгеброй без диаграмм, несмотря на то, что тут современное понятие площади непосредственно служит для воплощения интерпретации числа. Описанное выше уравнение более “квадратное” как раз потому, что квадрат числа – это, буквально, квадрат, как многоугольник с соответствующей площадью. Математические таблички с чертежами, впрочем, тоже имеются в музеях. Но их мало.
Теперь посмотрим на чертёж к задаче.

Условие соответствует верхней диаграмме. Остальные – объяснение метода решения.
Комментировать »
Дистрибутивность умножения относительно сложения означает, что a*(b + c) = a*b + a*c. В действительных числах, по определению, умножение дистрибутивно относительно сложения. Запишем это на Pyhton и посмотрим, что напечатает простая программа.
import math q = 11*(math.sqrt(5) + math.sqrt(17)) # q = a*(b + c) p = 11*math.sqrt(5) + 11*math.sqrt(17) # p = a*b + a*c print(p == q) # q == p => (q - p) == 0 q = 34*(math.sqrt(5) + math.sqrt(17)) p = 34*math.sqrt(5) + 34*math.sqrt(17) print(p == q) # ???
Запускаем (Python 3.11.2) и смотрим:
True False
В коде, в первом случае, написано:
p = 11*(√5 + √17), q = 11*√5 + 11*√17.
Значения p, q сравниваются. Программа выводит True – значения равны. Что и следовало ожидать, если бы это были действительные числа: по определению, q и p – это одно и то же число.
Во втором случае написано всё то же самое, алгебраически, но другой множитель:
p = 34*(√5 + √17), q = 34*√5 + 34*√17.
Удивительно, но результат сравнения p и q теперь False – числа не равны.
Да, конечно, причина в представлении типа float в компьютерной памяти: для 34 порядок округления сыграл свою роль – результаты разошлись в одном разряде. Обычно полагают, что этот пример лишь показывает ограничения битового представления float (и других типов). Но вообще-то основной вывод тут должен быть другим: для float не выполняется дистрибутивность умножения. То есть, операции с float – это операции с float, а не операции с числами, тем более, с действительными.
Компьютеры не работают с действительными числами. Потому что это невозможно. Да, натуральные, целые, рациональные – это подмножества действительных (с которыми подмножествами компьютеры тоже не работают, кстати). Но если вы случайным образом бросите точку на числовую прямую, то попадёте в иррациональное число. Запись этого числа в виде десятичной дроби – бесконечный процесс, который, впрочем, может быть формально определён – получится алгоритм вычисления конкретной записи числа (вспомним формулы для π, например).
Однако действительных чисел, которые для записи требуют выполнения бесконечного процесса, несравнимо больше, чем всех возможных записей алгоритмов, потому что множество записей алгоритмов – счётно. То есть, если вы действительно поверили в вещественные числа (каламбур), то возможных задач для решения на компьютере оказывается несравнимо больше, чем задач, которые можно было бы попытаться решить.
Да, символьные вычисления, с успехом выполняемые компьютерами, позволяют работать с “иррациональностями”. Но символьные вычисления происходят в других математических структурах (в других кольцах, если хотите строго) и не работают с десятичной записью действительных чисел. То есть, если записывать √2 как символ “√2”, – в том смысле, что это обозначение числа, квадрат которого равен двум, – то тут проблем нет. Но совсем другое дело – преобразование десятичной записи.
Почему это важно? Потому что алгоритм, построенный “в действительных числах” с дистрибутивностью, не будет правильно работать на реальном компьютере. Во float возникает плохое “ветвление”: например, там, где по определению действительных чисел должен быть всегда нуль, внутри float получаем нуль в одних точках, ненулевой результат – в других. Скажем, если вернуться к примеру выше, будем вычитать q из p. Ещё актуальный пример: представьте, что программа какой-нибудь нейросети обрабатывает миллиарды коэффициентов, но разработчик принял, что эта программа “работает в действительных числах” и поэтому разработчик не то что не учитывает, но даже и не рассматривает возникающие искажения.
Естественно, в хороших практических разработках это учитывается. Существенная часть практических алгоритмов в том же “машинном обучении” (Machine Learning – ML) как раз относятся к преобразованию подобных погрешностей. Корректная работа с погрешностями вообще очень важна при вычислительной обработке экспериментальных данных. Но почему-то всё равно приходится постоянно встречать утверждения, что “ML работает в действительных числах”.
Комментировать »
(Это существенно дополненная версия статьи, которую я недавно публиковал на “Хабре”. Дальше используются клинописные знаки из Unicode. Если в тексте на вашем устройстве клинопись не отображается, то это из-за отсутствия шрифтов; добавьте шрифты с клинописным блоком, cuneiform; например – Noto.)
На древних глиняных табличках из Месопотамии встречаются математические тексты с достаточно сложными вычислениями. Кроме прочего, для записи чисел использовалась шестидесятеричная система, которая имеет существенное «компьютерное» преимущество перед современной десятичной. Древняя шумеро-вавилонская система счисления это позиционная система по основанию шестьдесят: каждой позиции соответствует количество единиц, умноженное на степень 60, значения позиций – суммируются. Обычный способ. Шумеро-вавилонская клинописная цифра 42 выглядит так: 𒐏𒐖. И это именно цифра, которой может соответствовать и число 42, и 2520, и 151200 и так далее (всё в десятичной системе), по степеням 60. То есть, 42, как число, – это 42 единицы, для 60^0 (в нулевой степени). А десятичное 151200 – это 42 * 60^2. И так далее. Доли представляются по обратным степеням основания, например: 42 * 1/60, 42 * 1/(60^2) == 42 * 1/(3600).
Поскольку основание 60, то нужно 59 цифр. Откуда и цифра 42 (конечно, 42 тут – это условное обозначение, поэтому к нему даже можно не приписывать указание на десятичную систему).
В клинописной записи отдельных цифр вертикальные “палки” обозначают единицы, если их количество не превышает 9, а “галки” – обозначают десятки, что, впрочем, не делает систему десятеричной. Так, 𒐏𒐖 – это четыре “галки” и две “палки”, соответственно: 10 * 4 + 2 * 1. (Кстати, с какой стороны тут правильно писать количество знаков, а с какой – вес? Это ещё один пример того, что порядок записи важен, и логическая часть практически всегда некоммутативная.)
Вообще, древнейшние шумерские системы счисления использовали для записи цифр круги и “полуовалы” – примеры есть в отдельной записке. “Галки” и “палки” появились позднее, когда система стала логически стройнее.

(Очень древняя табличка. Credit: Metropolitan Museum of Art.)
На иллюстрации выше – пример таблички (3100–2900 B.C.) со знаками, соответствующими кругам и “полуовалам” по способу построения. Видимо, это учёт кувшинов и мехов – нарисованы рядом. Точная интерпретация значений цифр на самых древних табличках трудна, так как система ещё не имела строгой типизации: в самых древних шумерских вариантах интерпретация цифр зависела ещё и от того, что именно подсчитывается. Использовался ли знак для обозначения мер зерновых, для записи количества кувшинов масла или площади земельного надела – числовое значение могло быть разным. Это в точности современный Javascript, неявно преобразующий строку в числовой тип.
Что касается Javascript, то это эквивалентные операции. Не сомневайтесь. Многие неотъемлемые части информационных технологий очень древние. Предположим, неявное преобразование типов приводит ASCII-код записи “42” в значение целочисленного типа 42. Это означает, что последовательность байтов 0x3432, превращается в 0x2A. Но парой байтов были обозначены ASCII-символы! У древнейших шумер были бы, предположим, кувшины с пивом, а не ASCII-символы. Не удивительно, что эффект позднее исправлен (но не в Javascript).
Протоклинописных кругов и “полуовалов” всё ещё нет в стандартном Unicode. Но есть предложение по добавлению нужных символов.

На этой иллюстрации – фрагмент таблицы начертаний древних шумерских цифр, которые предлагается добавить в соответствующий блок Unicode. Из состава таблицы нетрудно сделать вывод, что композиций протоклинописных знаков, встречающихся на табличках, гораздо больше, чем простой круг и не менее простой “полуовал”. Ещё больше вариантов образуется в результате интерпретации исходной записи. Скорее всего, недостаток детализации и отсутствие арифметической строгости беспокоил и древних пользователей. Поэтому способ записи модифицировали, заменив круглые цифры на системы “галок” и “палок”. Получилась, так сказать, более привычная шестидесятеричная клинописная система. Способ записи стал стройнее, а шумеро-вавлионские цифры – стали более доступными для современного взгляда. Эта система более привычна и в смысле Unicode.
Шумеро-вавилонская система счисления имеет целый ряд особенностей. Например, способ записи не подразумевал явного обозначения степени конкретной позиции: сколько там весит единица – 60, 60^3 – всё определялось контекстом. Таким образом, это древнейшая система с плавающей точкой! И она на несколько тысяч лет старше всем привычных float, double, как и современных стандартов “плавающей точки” вообще. Впрочем, если в современных стандартах бывает два нуля, – вообще говоря, не равных друг другу, – то в шумеро-вавилонской системе настоящего нуля, как такового, вообще не было. В более поздних вариантах появился знак для заполнения пустых разрядов – пара диагональных клиньев, – но он использовался только для разрядов, находящихся между другими цифрами, а не для обозначения порядка вообще, то есть, не использовался на конце записи, как в случае 1000.
Выбор числа 60 в качестве основания имеет арифметическое обоснование. У шестидесятеричной системы большое преимущество, если сравнивать, например, с десятеричной, а тем более – с двоичной, восьмеричной и шестнадцатеричной. Всё потому, что число шестьдесят – “очень непростое”: 60 = (2 * 2) * 3 * 5. В результате, арифметика записи по основанию шестьдесят оказывается удобной для отображения долей целых – можно самые ходовые доли записать точно: 1/2, 1/3, 1/4, 1/5, 1/6, 1/10, 1/12, 1/15, 1/20, 1/30, ведь знаменатели здесь являются делителями 60. “Точная запись” же означает, что для обозначения не придётся использовать бесконечную запись, как 0.333(3) для 1/3 в десятеричном случае. О точности более подробно рассказано ниже. Прежде чем перейти к дробям, нужно получше разобраться с шумерскими клинописными цифрами.
Итак, цифрами служили комбинации клинописных знаков. Мы уже назвали их “галки” и “палки”, что соответствует их начертанию и в Unicode, и на табличках. 𒌋 – это “галка”, которая соответствует десяти единицам; 𒐕 – это “палка”, которая соответствует единице. Единицы в количестве менее десяти записывались в виде плотного набора “палок”: 𒐛 – это семь. 𒌋𒌋𒐗 – это цифра, которой, если считать цифры по порядку, соответствует число 23, записанное десятично. “23”, как цифра, – не обязательно обозначает число 23. “Галки” повторялись кратно десяти единицам: 𒌋𒌋 – двадцать. Группироваться наборы “галок” и “палок” могли произвольно, что может затруднить разбор. Мы будем использовать запись, когда единицы переходят на вторую “строку”, если их больше четырёх, а десятки – группируются вертикально: 𒐏𒐝 – это цифра 49. Можно считать, что число 49 это порядковый номер сорок девятой цифры.
Возьмём снова цифру 23: 𒌋𒌋𒐗. В зависимости от позиции и от контекста эта цифра может обозначать единицы, то есть нулевую степень основания (60^0 == 1), или единицы при 60^1, или единицы при 60^2 = 3600. Соответственно, 𒌋𒌋𒐗 (“23”) в позиции единиц – это число 23 в десятичной системе. 𒌋𒌋𒐗 в позиции 60^1 это 23 * 60 = 1380 (в десятичной). А для второй степени: 23 * 60^2 = 82800 (десятичная).

(Цифра 42)
На коллаже выше – клинописная цифра “42” шумеро-вавилонской системы счисления. Вверху – современная запись (Unicode + шрифт, поддерживающий клинопись). Ниже – два варианта записи той же цифры на табличках: P493016 (середина коллажа) и P257557 (внизу). Обе таблички датируются 1900-1600 B.C. При этом на нижней табличке использован способ группировки десятков в цифрах, который отличается и от варианта на табличке в центре, и от варианта Unicode. Но, думаю, теперь нетрудно догадаться, где там что.
Позиции в записи чисел разделялись пробелами, что не всегда однозначно, из-за того, что нуля не было. 𒐖 𒌋𒐗 это, например, 2*60^1 + 13 = 133. Почему “например”? Потому что это система с плавающей точкой. 𒐖 𒌋𒐗 с тем же успехом можно интерпретировать как 2 + 13/60. Здесь 13/60 = 13*(1/60) – это интерпретация 𒌋𒐗 как 13 “обратных” долей, по степени 60^1. То есть, плавающая точка переплывает вправо на одно знакоместо. Такой произвольный сдвиг точки очень удобен с точки зрения вычислений по таблицам значений, мы в этом убедимся на примерах далее. И если данная особенность сильно помогает при практических вычислениях, то она же мешает при считывании результата спустя всего лишь три-четыре тысячи лет.
Выходит, что с шумерскими цифрами всё достаточно просто: это различать клинья на реальных табличках трудно, но не схему записи.
Использование шумерской шестидесятеричной системы счисления в точности являлось прикладным случаем того, что сейчас принято называть “компьютерными науками” (computer science). Поэтому очень полезен сравнительный взгляд, учитывающий другие позиционные системы счисления, используемые в информатике и в тех самых компьютерных науках сейчас.
Старинная шутка гласит, что типов людей всего 10 – одни уже знают двоичную систему счисления, а другие – ещё нет. Система счисления – это способ записи чисел. Разные системы хорошо подходят для разных целей. Так, двоичная система подходит не только для записи шуток (0b0010 == 2), но и служит неплохим математическим фундаментом “железячных” компьютерных вычислений: компьютеры, как известно, не считают, а переключают транзисторные “флип-флопы”. Успешная интерпретация таких переключений возможна только потому, что есть двоичная система счисления.
Использование систем счисления с разными основаниями является привычной практикой: кроме двоичной и десятичной, распространены восьмеричная и шестнадцатеричная. Восьмеричная система, конечно, встречается нынче реже, чем шестнадцатеричная, но всё равно постоянно присутствует рядом с вами, если вы сетевой инженер или разработчик системного ПО. Например, один из штатных способов записи IP-адреса, при вызове тех или иных утилит, использует восьмеричную систему, вот так: 010.010.010.010 – это будет 8.8.8.8 в привычной форме по основанию 10 (десять). Согласно древнему соглашению, октет IPv4-адреса, начинающийся с нуля, интерпретируется как записанный в восьмеричной системе. На глиняных табличках из Месопотамии об этом ничего не сказано, но, из-за базовых unix-библиотек, такое преобразование сейчас касается не только октетов.
А вот шестнадцатеричная система вряд ли нуждается в примерах: этот вариант встречается в практике программирования почти так же часто, как и система десятичная. И не только в программировании. На стороне веб-фронтендеров в шестнадцатеричной системе повсеместно записываются цвета внутри каскадных таблиц стилей (CSS). По основанию 16 очень удобно записывать байтовые маски: запись получается не такой длинной, как в двоичной, а один октет укладывается в две цифры.
В древние времена, когда компьютеры были редкими и очень большими, данные в память вводили вручную, нажимая кнопки-биты и перещёлкивая тумблер записи. Если ваш компьютер работал на байтовых ячейках, – то есть, в один октет, восемь бит, – то опытный оператор мог каждую шестнадцатеричную цифру заданного значения моментально “выкинуть” на четырёх пальцах руки: каждый палец – один бит. Если бит установлен, то и палец выставляется вперёд, для нажатия на кнопку системы набора. 0xF – четыре пальца. 0x8 – указательный или мезинец, в зависимости от того, “тупоконечные” у вас пальцы или “остроконечные”. Не в смысле формы, а в смысле того, с какой стороны старший бит байта в представлении вашего компьютера. И так далее – прочие варианты “распальцовок” читателю предлагается отработать самостоятельно. Нынче навык сей доступен разве что редкому динозавру-технарю, да и не так полезен, однако верная распальцовка всё ещё может быть эффективно использована в беседе с молодыми DevOps, для пущего их убеждения. Заметьте, что две руки – это байт, а большим пальцем можно ловко перещёлкнуть тумблер записи, перейдя к следующему адресу. Хотя, тумблеров на компьютерах больше нет.
Не факт, что старшинство указательного пальца, взятое по модулю “хиральности” (правой/левой руки), оказало влияние на развитие систем счисления. Но вполне возможно, что принцип счёта по трём фалангам каждого из четырёх пальцев при помощи пятого прямо связан с двенадцатеричными системами, которые мы здесь не рассматриваем. Впрочем, как раз 12 * 5 == 60.
Выше мы заметили, что основание 60 позволяет едва ли не каждую привычную на практике долю записать точно. Благодаря этой возможности шестидесятеричная система превосходит десятеричную и другие упомянутые системы. Разберёмся, что именно тут имеется в виду.
Всякую позиционную систему счисления можно использовать для записи дробей: возьмём обратные степени основания {-1, -2, -3…} и станем записывать дробную часть, отделив её от целой каким-нибудь знаком. Точкой, например. Это уже было проделано выше. Для десяти – получаем всем привычные десятичные дроби: 0.4242. Точно так же будут работать и двоичная, и восьмеричная, и шестнадцатеричная система. Нет разницы. Только вот с записью долей таким способом возникнет сложность иного рода: запись окажется “бесконечной вправо” (ну, если записывать числа и расставлять веса справа налево; “бесконечная влево” запись – это p-адические числа, другая тема, пусть и неожиданно близкая).
Так, 16 – это 2^4. То есть, основание шестнадцатеричной системы, при всём богатстве выбора, какое-то слишком “простое”, и проблемы начнутся с нечётными знаменателями в долях вида a/b.
Например, 1/5 == 0x0.33(3) – всё в шестнадцатеричной системе, а тройка здесь – в периоде (обозначается скобками). “В периоде” – это значит, что развернутая запись никогда не окончится. Однако в десятичной записи 1/5 == 0.2 – и точка. “Периода” нет, запись терминируется тут же. И это одно и то же рациональное число – одна пятая.
Да, формально, и 0x0.33(3), благодаря возможности обозначить “периодические цифры”, это точная конечная запись для 1/5. Да, к 0.2, если уж быть максимально строгим, должен быть приписан бесконечный хвост нулей – 0.2(0), но его принято не указывать (да вообще мало кто помнит, что он там есть, но его не видно; как и про второе представление с бесконечно повторяющимися девятками, для десятичной системы). Это всё так по определению, а требуется по тем же причинам, по которым в десятеричной (десятичной) системе 0.9(9) == 1.
Казалось бы, сложно ли записать (3) как период? Всего лишь скобки. Однако проблема в том, что длина периодической части может быть произвольной, не обязательно лишь одна цифра повторяется.
Заметьте, что 0x0.3 * 0x5 == 0x0.F. Аналогично тому, как 0.3 * 3 == 0.9. Но почему в десятичной системе 1/5 = 0.2? Потому что 10 == 2*5. Соответственно, все числа, представимые в виде 2^n * 5^m, будут регулярными в десятичной системе: “обратные” к ним можно записать в виде конечной дроби. Шестнадцатеричная же система имеет основание 2^4, а число 5 нельзя представить в виде 2^n, отсюда и дробная часть в периоде.
По тем же причинам 1/7 = 0x0.249(249). Шестнадцатеричная система. Три цифры – период.
А вот для 1/(7^11) период шестнадцатеричной записи после запятой составит 847425747 цифр (цифры записи будут шестнадцатеричные, но их количество – записано в десятичной системе, проверьте на калькуляторе). Так что проблема точной записи, даже если позволяется вводить дополнительную структуру и указывать период в скобках, – остаётся. Кстати, поскольку в разложении 10 есть 2, то найти столь же иллюстративный обратный пример – число, которое “хорошо” представляется в шестнадцатеричной системе, но “плохо” в десятичной, – не выйдет.
Будем обозначать клинописные цифры десятичной записью соответствующего числа, разделять цифры будем запятой “,”, а точку-разделитель целой и дробной части (мантиссы) – обозначим точкой с запятой (“;”). Это общепринятый сейчас способ. Тогда 1/7 в шестидесятеричной шумеро-вавилонской системе это 0; 8,34,17(,8,34,17) или клинописью:
𒐜 𒌍𒐘 𒌋𒐛 𒐜 𒌍𒐘 𒌋𒐛… (тут цифры повторяются с периодом три).
А что ещё за “обратные” числа? Например, в десятеричной системе деление на два эквивалентно умножению на пять с последующим сдвигом десятичной точки. Элементарный пример: 7/2 = (7 * 5)/10 = 35/10 = 3.5. В шестидесятеричной системе основание делится не только на 2 и 5, но ещё и на 3, и в шестидесятеричной системе регулярные числа – это все те, которые представимы в виде 2^n * 3^l * 5^m. Здесь, как мы разобрались выше, много делителей – [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60], – поэтому сдвиг плавающей точки предоставляет больше возможностей для практических вычислений по таблицам.
В древней шумеро-вавилонской практической арифметике деление одного числа на другое выполняется с помощью специальных таблиц, записанных на глиняных табличках. Чтобы найти отношение A/B, древний инженер-информатик берёт “таблицу обратных” и находит число, обратное к B. Обратите внимание, что это не совсем те привычные обратные по умножению, не совсем B^(-1) – обратное здесь берётся по основанию системы счисления. Так, в десятеричном примере выше (про деление 7/2), обратное к 2 – это 5. Почему? Потому что 5 * 2 = 10, то есть 2/10 при умножении на 5 дают единицу.
Снова вспомним, что у нас плавающая точка. Цифра 1 в нулевом разряде, если сдвинуть точку вправо на одну позицию, даст запись числа 10, на две позиции – 100, и т.д. Поэтому, определив обратное 5 в десятеричном примере, остаётся просто посмотреть в таблицу умножения для 5, найти там 5 * 7 == 35, выписать ответ и не забыть сдвинуть десятичную точку: 3.5. Так что именно плавающая точка позволяет нормировать имеющиеся числа, приводя их к удобной для вычислений форме записи. Шестидесятеричная система действует так же, но у неё больше возможностей для точных вычислений, чем у десятеричной. Умение работать с таблицами обратных и с таблицами умножения очень актуально, ведь в древней Месопотамии нет даже механических арифмометров (ну, скорее всего, их либо нет, либо они слишком дорого стоят). А вот глиняные таблички умножения и взятия обратных для шестидесятеричной системы – есть.
Разделим 10,30 на 1,15. Это уже шумерские цифры (см. нотацию выше). То есть, 10,30 == 630 в десятичной, а 1,15 == 75 в десятичной. Найдём обратный к 1,15 – это 48, потому что 1,15 * 48 == 1, а именно – 3600 в десятичной записи; выше мы разобрались, что в шумерской системе нет строгой разбивки по позициям, поэтому единица для 3600 соответствует позиции с весом 60^2. Найдём результат умножения 48 на 10,30. Это 8,24 (нуля у нас нет – не забывайте). Как получилось 8,24? Скорее всего, для шумерского инженера-информатика это табличный результат – то есть, он должен быть уже записан на глиняной табличке умножения. Если же не записан, то инженер-информатик быстро вычислит его в уме, поскольку хорошо владет шестидесятеричной системой.
Посудите сами: 30 – это половина от “единицы”, то есть, половина от основания системы, от 60. Соответственно, умножать на 30 просто – работает так же, как умножение на 5 в десятеричной системе: умножаем на 1 и берём половину. Но 1, конечно, это 60 в десятеричной. То есть, нужно взять половину от значения цифры 48 – записываем цифру 24, всё в соответствии с таблицей умножения. И не забываем, что позиция плавающей точки только что сдвинулась вправо, ведь при умножении 48 на единицу, мы взяли эту единицу в позиции с весом 60, но исходная цифра 48 – это количество единиц в позиции с весом тоже единица (60^0). Естественно, если корректно отслеживать положение плавающей точки, то метод работает для любой позиции цифры 48. Так что – обычное быстрое умножение, сдвигами.
Теперь цифра 10 из 10,30. 10 – это одна шестая от основания системы, тоже удобно. Умножаем на единицу и берём одну шестую. Одна шестая от 48 – это 8. Поэтому и цифра будет 8, но она должна стоять в правильной позиции – то есть, на одну позицию левее. Перенос плавающей точки здесь соответствует “переносу единиц” в привычном умножении столбиком в десятеричной системе, но из-за большого количества делителей основания системы 60, схема оказывается более эффективной.
Итак, получили 8,24, в шумерских цифрах. Обратите внимание, что сейчас, после умножения “на обратный”, 8 находится в позиции с весом 60^2, а 24 – в позиции с весом 60. Поставим “плавующую точку” на место, сдвинув, как положено, на две позиции левее, и получим верный результат: 8; 24.
То же самое в клинописных цифрах, как это делал бы инженер-информатик в Месопотамии несколько тысяч лет назад, засечками на глине:
𒌋 𒌍 / 𒐕 𒌋𒐙 (здесь “слеш” – знак деления),
обратный: 𒐏𒐜, умножаем по таблице: 𒐏𒐜 * 𒌋 𒌍.
Результат: 𒐜 𒌋𒌋𒐘 – и через те самые несколько тысяч лет придётся догадываться, где же здесь стоит плавающая точка.
Всё это так хорошо работает потому, что 48 == 2^3 * 6, 10 == 2 * 5, 30 == 5 * 6 и т.д. Это всё регулярные числа, которые записываются точно в шеcтидесятеричной системе. Чуть выше мы нашли, что 1/7 в шумеро-вавилонской системе нельзя представить точно, потому что запись будет бесконечной: 𒐜 𒌍𒐘 𒌋𒐛 𒐜 𒌍𒐘 𒌋𒐛… Так происходит потому, что 7 – взаимно простое с основанием 60.
Знали ли об этом древние шумеры и вавилоняне? Скорее всего, да, знали, но трактовали это явление иначе, чем сейчас: для древнего шумерского инженера-информатика рациональное число 1/7 было бы не “рациональным”, а “невозможным”, в том смысле, что такую долю невозможно записать точно в шестидесятеричной системе, поэтому в точных таблицах нет обратного значения. Непонятно, как считать, поэтому 1/7 не должна встречаться на практике. Конечно, если 1/7 всё же встретилась, то инженеру-информатику пришлось бы воспользоваться таблицами “приближённых” значений, подобрав подходящий интервал. Такие таблицы тоже имелись, но их наверняка старались избегать, ведь иначе шестидесятеричная система оказывается избыточной.
Комментировать »
Повышение уровня абстракции – полезный и важный элемент процесса обдумывания, так как позволяет задействовать механизм “разрешения противоречий”. Если такой механизм есть, конечно.
Возьмём пару цитат и ещё одну, не менее замечательную:
“Нас невозможно сбить с пути, нам всё равно, куда идти”
М. М. Жванецкий(?), (есть вариант “никому не сбить”, не важно).
“У самурая нет цели – только Путь”
“Хагакурэ”, Ямамото Цунэтомо. (Иногда напоминаю коллегам эту важную максиму.)
Это почти об одном и том же. В первой цитате – всё равно, куда идти, но идти необходимо, иначе исчезает содержательная часть. Именно ход создаёт путь, который, по определению, тут не может вести к конкретной цели. Цели нет. Только Путь. Как и у самурая из второй цитаты. Почти.
“Всё равно куда” перекликается с одной из самых знаменитых цитат из “Алисы в Стране чудес”:
”
– Скажите, пожалуйста, куда мне отсюда идти?
– А куда ты хочешь попасть? – ответил Кот.
– Мне все равно… – сказала Алиса.
– Тогда все равно, куда и идти, – заметил Кот.
– …только бы попасть куда-нибудь, – пояснила Алиса.
– Куда-нибудь ты обязательно попадешь, – сказал Кот. – Нужно только достаточно долго идти.
“
Л. Кэрролл.
Эта цитата содержит машинерю, позволяющую детально разобрать две предыдущих цитаты. Всё равно, куда идти. Но если идти достаточно долго, то куда-нибудь попадёшь. Долгий ход, как сейчас говорят, “определяет”. Если недостаточно долго идти, то не получится дойти даже куда-нибудь (настолько размытое пространство). Как понять, что идёшь уже достаточно долго? Нужна точка отсчёта, иначе всякий раз может оказаться, что ход недостаточно долгий и никуда не приводит. Где предельный переход? Возможен ли он? У самурая нет этих проблем, из-за глубинных свойств его пути.

Занятно, что, согласно “Алисе в Стране чудес”, даже когда путь отделяется от цели, он обязательно приводит куда-нибудь. Это здесь чисто категорийное свойство пути – приводить куда-то. Стрелка.
К сожалению, такое определение недостаточно абстрактно: выходит, что если начать просто “идти” и “идти”, то это ещё не путь. В “невозможно сбить с пути”, не только путь появляется раньше цели, но и сказано, что куда-то мы должны идти. Да, всё равно – куда, но ведь – куда-то! Тем не менее, попытка отделить свойство, создающее путь, от цели, через “невозможность сбить с пути”, это абстракция достаточно высокого уровня, чтобы начать строить теорию категорий. Удивительно.

Тут-то и можно заметить преимущество положения самурая из самурайской цитаты, по сравнению с ситуацией “всё равно куда”. Во “всё равно куда” – цель неявно проступает, если попытаться задуматься и начать искать способ разрешения противоречий: как определить, что всё равно, куда? Ведь “куда” подразумевает некоторое направление пути, почти что цель. Для “куда” нужно ввести дополнительные понятия, чтобы отличать одно “куда” от “другого”. А про самурая нельзя сказать, что ему всё равно, куда идти. У самурая отсутствие цели постулируется, так что “куда” – просто не возникает. Все “куда” поднимаются для самурая в Путь (см. иллюстрацию).
Дзен самурая – дзен более высокого уровня, на фоне которого “всё равно куда” выглядит некоторым мельтешением: суетятся, бегают куда-то, всё равно, куда – лишь бы бегать. Самурай не суетится. У самурая нет цели. Только путь.
Комментарии (1) »
Шумеро-вавилонские записи чисел на глиняных табличках не всегда использовали знаки “клин” (“палка”) и “галка”. Более древний вариант основан на круге и “полуовале” – см. иллюстрации.

(Image: Cuneiform Digital Library Initiative)
Выше – очень старая табличка, которую датируют 3200-3000 годами до нашей эры. Отпечатки выполняли при помощи цилиндрического стилуса. Расположив стилус вертикально и вдавив его в табличку – получаем круг; тот же стилус под наклоном – даёт в отпечатке “полуовал”. Использовались стилусы двух диаметров. Круг меньшего диаметра – это десять. “Полуовал” меньшего диаметра – единица. Круг большего диаметра – три тысячи шестьсот (3600 = 60^2). “Полуовал” большего диаметра – шестьдесят. Отпечатки меньших кругов могли вкладываться в большие отпечатки: получалось, видимо, 36000 (3600 * 10) и 600 (60 * 10).

(Image: Cuneiform Digital Library Initiative)
Другая табличка с таким же принципом записи цифр, датируемая 2600-2500 годом.
Вот это и есть настоящие “круглые цифры”.
Комментировать »
Dual EC DRBG (“Сдвоенный детерминированный генератор случайных битов на эллиптической кривой”) – нашумевшая схема генератора псевдослучайных чисел, в которой встроен (потенциальный) математический бэкдор. Несмотря на сразу же возникшие подозрения о бэкдоре, эта схема была без проблем стандартизована NIST в 2006-2007 годах и достаточно широко использовалась. Соответствующий стандарт позже официально отозван NIST.
В криптографии постоянно требуются случайные числа. Получение действительно случайных чисел сопряжено с большими проблемами, которые начинаются с того момента, что всякая попытка строго определить и гарантировать случайность значений неминуемо сталкивается с философскими трудностями, корни которых находятся в области интерпретации реальности. Поэтому на практике гарантировать случайность невозможно, но есть различные модели и допущения, позволяющие приблизиться к “строгой случайности” с точки зрения вычислительных возможностей. (Да, есть “квантовые” предложения – но они сугубо теоретические, и тоже подразумевают некоторую модель – модель “квантовой механики”.) Естественно, только из этого не следует вывод о том, что все практические случайные значения предсказуемы, но зато следует другой вывод – о допустимости использования алгоритмических генераторов псевдослучайных чисел: алгоритмов, выдающих такие последовательностей значений, которые вычислительно неотличимы от истинно случайных. (Оставим понятие “истинно случайный” – за скобками, отметив, что, – по современным представлениям, – генератор “истинно случайных” значений должен быть исключительно аппаратным.)
Важнейшей особенностью криптографических генераторов (псевдо)случайных чисел является то, что выдача генератора детерминирована – то есть, выдаваемые значения определяются внутренним состоянием. Именно этот аспект служит фундаментом для построения бекдора в Dual EC DRBG. Криптографические генераторы псевдослучайных чисел имеют важнейшее значение не только для теоретической, но и для прикладной криптографии – это краеугольный камень всех практических систем криптографической защиты.
Несмотря на то, что с точки зрения теории криптографии схема, о которой идёт речь, предоставляет потенциальный бэкдор, её свойства можно трактовать и как инструмент “депонирования” ключей – то есть, при реализации конкретного экземпляра генератора выбираются такие параметры, что уполномоченная сторона может его “взламывать”. Однако уже сам факт того, что возможно провести в статус стандарта такую схему генератора, которая содержит механизм построения бэкдора на уровне алгоритма и описание этого бэкдора опубликовано на момент стандартизации, имеет большое историческое значение. Ещё более показательна и интересна сугубо математическая часть данного бэкдора. Настоящая статья посвящена именно математике бэкдора и в деталях объясняет то, почему он работает.
По сути, бэкдор в Dual_EC_DRBG – это реализация протокола Диффи-Хеллмана (DH) на эллиптической кривой: секретный ключ находится у стороны, контролирующей бэкдор через параметры протокола, что позволяет этой стороне получать внутреннее состояние генератора псевдослучайных чисел, наблюдая его выдачу. Знание внутреннего состояния приводит к раскрытию всей последующей выдачи генератора. При этом, математически, пользователь Dual EC DRBG неявно выполняет обмен DH с контролирующей параметры генератора стороной. Это важное свойство штатных схем построения “надёжных бэкдоров”: доступ к бэкдору должен быть только у той сторны, которая знает секретный параметр – секретный ключ. Есть и другое важное свойство: если секретный параметр не был раскрыт, то строго доказать, что бэкдор действительно встроен в конкретную реализацию – нельзя. Это не отменяет возможности тсрогого описания для механизма такого бэкдора.
Итак, в Dual EC DRBG, без знания секретного параметра раскрыть внутреннее состояние генератора вычислительно трудно, потому что базовые функции протокола – односторонние. Поэтому, если оставить за скобками секретный параметр, то протокол полностью соответствует общепринятой схеме.
Посмотрим на типовую схему построения программного генератора псевдослучайных чисел. Такие генераторы можно строить на базе двух односторонних функций и цепочки внутренних состояний. Одна из функций переводит текущее внутреннее состояние в следующее, а вторая – извлекает значение из текущего состояния и выводит его в сторону пользователя.

На схеме: Sn – внутренние состояния генератора; φ() – функция, преобразующая состояние Sn в Sn+1; ξ() – функция, преобразующая состояние Sn в выдачу генератора Bn на данном шаге. Биты пошаговой выдачи Bn могут конкатенироваться для получения псевдослучайной последовательности нужной длины (на схеме: RND[…]) – это типовой способ прикладного использования генератора.
Криптографические генераторы псевдослучайных чисел, помимо общих “статистических” требований к неотличимости выдачи от случайной, имеют ряд особенностей. Прежде всего – выдача должна быть необратимой. А именно: состояния Sn являются секретными параметрами, поскольку позволяют раскрыть будущую выдачу генератора. При этом выдача генератора (Bn) на каждом шаге – публична. Из этого нетрудно сделать вывод, что функции φ и ξ должны быть односторонними (однонаправленными – по значению сложно определить аргумент): если это не так, то по публично доступным данным (Bn) легко вычислить состояние генератора. В чём и состоит логический смысл описываемого бэкдора.
Почему односторонней должна быть и функция φ, которая переводит текущее внутреннее состояние в следующее? Это нужно для того, чтобы по утекшей информации о внутреннем состоянии на каком-то шаге было вычислительно сложно восстановить предыдущую выдачу генератора. Одно из базовых требований к криптографически стойким генераторам псевдослучайных чисел состоит в минимизации возможностей по раскрытию данных. Например, если есть бэкдор, позволяющий обратить ξ, то, при условии обратимости φ, взяв любую точку можно раскрыть сколько угодно данных – и предыдущие, и следующие. При этом обратимость ξ может являться следствием не бэкдора, а обычной уязвимости, в том числе, уязвимости реализации алгоритма. Предположим, эта уязвимость ξ срабатывает лишь на каких-от редких данных: соответственно, если подобрать такие данные удалось, но φ осталась необратимой, атакующий сможет вычислить только следующие состояния генератора и все предыдущие секреты останутся защищены.
Математической особенностью описываемого бэкдора является то, что он вовсе и не позволяет обратить функции ξ и φ – они остаются односторонними, но бэкдор открывает возможность простого вычисления следующего внутреннего состояния генератора по известной выдаче ξ. Это возможно потому, что алгоритм содержит дополнительную структуру, связывающую функции ξ и φ.
Псевдослучайная выдача в криптографических протоколах постоянно используется в открытом виде. Например, в открытом виде передаются векторы инициализации для схем зашифрования (GCM и пр.), псевдослучайные векторы в сообщениях TLS (поля ClientRandom и ServerRandom) и др. Поэтому нетрудно извлечь значения из трафика, сопоставить их с выдачей генератора псевдослучайных чисел, раскрыть внутреннее состояние генератора и получить последующие биты выдачи, которые, например, были использованы тем же приложением для получения секретных ключей шифров, защищающих трафик – это позволит восстановить секретные ключи и расшифровать трафик в пассивном режиме.
Dual EC DRBG работает на эллиптической кривой (над конечным полем), а в качестве односторонних функций использует умножение точки эллиптической кривой на скаляр: x∘P. Далее умножение на скаляр обозначается “блобом” (∘). Стойкость к обращению здесь основана на задаче дискретного логарифмирования: то есть, по значению Q = x∘P – вычислительно трудно найти x.
Вспомним, что умножение на скаляр – обычное для эллиптической криптографии последовательное сложение точки кривой с самой собой. Сложение – операция, которая введена на точках кривой. А именно: точкой кривой называется пара (X, Y) значений “координат”, соответствующих уравнению кривой. Здесь X и Y – это элементы подлежащего конечного поля, которое входит в параметры криптосистемы. В данном конкретном случае (например, для кривой P-256) используемое конечное поле – это вычеты, то есть “остатки” по модулю простого числа. Сложение точек P + Q = R позволяет по паре координат точки Q (XQ, YQ) и паре координат точки P (XP, YP) Получить координату точки R (XR, YR). Скаляр – это целое число. Умножение на скаляр 3 означает, что точка складывается сама с сбой в трёх экземплярах: 3∘P = P + P + P (плюс – это сложение точек). По такому сложению точки всякой эллиптической кривой всегда образуют группу (по определению эллиптической кривой).
В алгоритме Dual EC DRBG используется две точки кривой: P и Q. Точка P – задаёт последовательность внутренних состояний. Внутреннее состояние Sn в Dual EC DRBG – целое число, которое соответствует координате X точки кривой, полученной умножением P на значение предыдущего состояния, как на скаляр. Вторая точка, Q – задаёт “ответвления”, то есть, выдачу генератора по каждому из состояний, и используется в качестве основания на каждом шаге. Ниже представлена упрощённая схема Dual EC DRBG.

На этой схеме: Sn – внутреннее состояние; для получения следующего состояния из текущего – точка P умножается на значение состояния (скаляр: Sn∘P), а координата X получившейся точки – выводится в качестве нового состояния генератора; для вывода случайных значений – вторая точка, то есть – точка Q, умножается на состояние (Sn∘Q) и выводится координата X получившейся точки. То есть, используется две одинаковых функции с разным основанием: P и Q. Раз стойкость этих функций основана на дискретном логарифмировании, то они односторонние, как и требуется.
Математический смысл бэкдора не нарушает стойкость конкретных операций с точками P и Q, он несколько хитрее и строится на в соотношении между точками P и Q. Допустим, атакующей стороне известно такое значение δ, что P = δ∘Q. Выдача генератора – это X-координата точки Sn∘Q. Атакующий находит подходящую Y-координату, подставив значение в уравнение кривой (точек с подходящими координатами будет две, алгоритм знак координаты Y не различает, но выбор точки, очевидно, не представляет труда). Таким образом атакующий легко восстанавливает точку кривой, подходящую для выдачи генератора. Далее – умножаем на δ.
δ∘(Sn∘Q) = Sn∘(δ∘Q) = Sn∘P (1)
Рассмотрим формулу (1) подробнее. Почему она работает? Потому что скаляры – это целые числа. Из-за коммутативности группы точек кривой к скалярам применимы арифметические свойства целых чисел. В алгебре такая конструкция называется ℤ-модулем. Всякая коммутативная группа является ℤ-модулем. (Некоторые алгебраисты из-за этого даже не считают коммутативные группы “настоящими” группами.) Применительно к эллиптической кривой: 3∘P = P + P + P, а 5∘P = P + P + P + P + P. Но тогда (3+2)∘P = 5∘P = P + P + P + P + P, что следует из свойств групповой операции – просто поставим скобки: (P + P) + (P + P + P), получив, таким образом, две точки (P + P) = 2∘P и (P + P + P) = 3∘P. 3∘P + 2∘P = 5∘P. Обратите внимание, что здесь знак “плюс” используется в двух значениях: и для обозначения сложения точек кривой, и для обозначения привычного сложения в целых числах (3+2). А раз схема работает для сложения целых чисел, то она обязательно работает и для умножения целых чисел, потому что умножение в целых числах можно построить через сложение (собственно, при корректном преобразовании 0 и 1, сложение и умножение в целых числах просто могут быть переведены одно в другое, как операции). Но тогда и (3*2)∘P = (P + P) + (P + P) + (P + P) = 3∘(2∘P) = 6∘P. Что и используется в формуле (1), вместе с коммутативностью умножения в целых числах: 2 * 3 = 3 * 2.
Таким образом, атакующая сторона, которая знает секретный скаляр δ, получила значение следующего состояния генератора, вычислив Χ-координату точки Sn∘P (см. схему). Формула (1) вообще очень похожа на реализацию протокола Диффи-Хеллмана (DH) на эллиптической кривой. То есть, пользователь генератора псведослучайных чисел, можно сказать, обменялся с атакующей стороной открытыми параметрами Диффи-Хеллмана. А именно: открытый параметр атакующей стороны, статический ключ, зашит в константы протокола – P = δ∘Q, где секретный ключ – δ; открытый параметр DH пользователя – это динамическая выдача основного алгоритма – Sn∘Q, где секретный ключ Sn. “Открытые параметры DH” пользователя атакующая сторона наблюдает в трафике. Важное отличие от практического DH состоит в том, что “общий секрет” тут не должен становиться “общим” с атакующей стороной.
Итак, для внедрения бэкдора нужно выбрать такие P и Q, что P = δ∘Q. Полученные точки – это параметры конкретной реализации алгоритма, но они могут быть закреплены в стандарте (что и было сделано). Но в спецификации Dual EC DRBG для кривой P-256 в качестве точки P строго указана базовая точка группы кривой, которая используется в спецификации P-256. То есть, произвольно выбрать P нельзя. Оказывается, в том случае, если одна из точек P или Q заранее строго задана, то определить нужное значение δ можно при помощи вычисления мультипликативного обратного по модулю порядка группы точек. Важно, чтобы порядок был простым числом. Но это стандартная практика для прикладной криптографии. Например, для кривой P-256 – соответствующий порядок простой.
Чтобы получить бэкдор, нужно определить δ из P = δ∘Q. Может показаться, что если точка P зафиксирована, – соответственно, выбрать эту точку умножением какой-то точки Q на произвольный скаляр нельзя, – то требуется решить сложную задачу отыскания дискретного логарифма. Но это не так, поскольку мы всё равно можем выбрать произвольную точку Q. Чтобы согласовать точки, возьмём произвольное значение ε в интервале от 2 до порядка группы, генерируемой P, а потом возьмём δ = ε^(-1) по модулю порядка. Пусть порядок P – то есть, количество точек в используемой группе, – это простое число n. Тогда нужно найти ε * δ = 1 (mod n). (Например, 2 – обратный по умножению элемент к 4 по модулю 7, так как 2 * 4 = 1 (mod 7).) Задача нахождения мультипликативного обратного по модулю простого числа здесь вычислительно несложная. Определив δ = ε^(-1), в качестве точки Q выберем ε∘P. Тогда: δ∘Q = δ∘(ε∘P) = (ε^(-1)*ε)∘P = P. Следовательно, мы нашли такое δ, что P = δ∘Q.
То есть, если можно выбрать оба параметра – точки P и Q, – то выбираем так, что P = δ∘Q, а если одна из точек зафиксирована – выбираем δ = ε^(-1) по модулю (простого) порядка группы точек, это всегда можно сделать из-за особенностей спецификации: подлежащие группы имеют простой порядок. (Не забывайте, что в формулах выше используется два умножения – умножение точки на скаляр и умножение целых чисел (δ = ε^(-1); 1 = ε^(-1)*ε). Это работает потому, что скаляры – целые числа, но по модулю порядка группы.)
В Dual EC DRBG битовый вывод генератора, – то есть, X-координата Sn∘Q, – урезается: из него удаляются 16 старших битов. Это означает, что прямо использовать результат для вычисления координат исходной точки нельзя. Но 16 бит можно быстро перебрать, проверяя, для всех значений подряд, лежит ли на кривой точка с соответствующей X-координатой. Вычисление значений по уравнению кривой тоже не составляет проблемы – уравнение известно, а используемые там операции обязательно быстрые.
Естественно, полученная перебором точка может оказаться неверной. То есть, точка не будет являться Sn∘Q. На этом шаге “через бэкдор” у атакующего нет никакого способа проверить, что точки совпали. Но это не сильно затрудняет атаку. Значения секретного состояния нужно вычислить для всех возможных точек, которые соответствуют сокращённому битовому значению, а результат по каждой точке – сопоставить с дальнейшим анализом трафика. Например, если выдача генератора используется для получения секретного ключа, то выбрать его верное значение можно при помощи пробного расшифрования. В любом случае, анализ 2^16 числовых значений при помощи перебора не представляет здесь вычислительной проблемы.
В ранней версии стандарта NIST на Dual EC DRBG реализация использовала подмешивание дополнительной маски на каждом шаге вычисления псевдослучайных чисел. Это делало описанный бэкдор нерабочим. Однако стандарт был быстро обновлён, точка подмешивания дополнительной маски перенесена, и использование бэкдора стало снова возможным. Поэтому данная особенность здесь не рассматривается.
Проблема алгоритма Dual EC DRBG, как криптографического генератора псевдослучайных чисел, помимо низкой производительности, в том, что внутри его конструкции есть жесткая структура, зависящая от внешних параметров. Из-за алгебраических свойств эллиптических кривых, в практической реализации – точки P и Q всегда связаны. Да, иногда, если специально постараться, они могут быть получены способом, дающим некоторую гарантию того, что связующий скаляр никому не известен. Либо, P и Q может генерировать конкретный пользователь, в качестве параметра для своей локальной версии генератора. Стандарт NIST разрешал такой вариант, но не рекомендовал его, а для соответствия строгим требованиям FIPS допускались только параметры из спецификации.
(Это расширенная версия статьи, которую я недавно опубликовал на “Хабре”.)
Комментировать »
Немного древних чисел. На скриншоте ниже – глиняная табличка из Месопотамии Plimpton 322, которую датируют 1900-1600 годом до нашей эры.

Image: CDLI / Rare Book and Manuscript Library, Columbia University, New York, New York, USA
Табличка содержит древнюю тригонометрическую таблицу (судя по всему, да, так и есть – тригонометрическую), числа в которой записаны в шумерской системе. Шумерская система счисления – это занятная позиционная система по основанию 60, со своими тонкостями.
Попробуем прочитать числа в середине таблички (примерно – в середине: найти исходное положение фрагмента нетрудно, если воспользоваться характерным пятном).

Здесь в нижней части изображения я (примерно, опять же) обозначил знаки, которыми записаны цифры. Разгадка – ниже. Шумерские цифры (ещё не числа) записываются при помощи двух типов символов: вертикальные “галки” – обозначают десятки (так что система, в чём-то, ещё и десятичная); вертикальные “палки” – единицы, которые, если их меньше десяти, объединяются в тесные блоки. Значение для конкретной цифры – получается суммированием значений составляющих знаков. Всё это нетрудно разглядеть на табличке. Специального нуля тут нет – его роль играют пробелы и контекст.
Итак, если записать в принятом формате, обозначая шумерские цифры числами [01..59] в десятичной записи и разделяя позиции точкой, получатся числа, которые представлены ниже (два числа, слева направо, строка с зелёными подсказками).
38.11 59.01
Чтобы понять, как такие значения получаются – посчитайте “галки” и “палки” на картинке: например, три “галки” слева – это 30. Остальное должно сложиться само. Но это шестидесятеричная запись. Её, впрочем, нетрудно перевести в десятичную (учитывайте, что это позиционная система, поэтому цифровые значения умножаются на 60 в степени номера позиции, начиная с нуля; в этом примере – позиций только две).
38*60 + 11 = 2291 59*60 + 1 = 3541
Шумерские цифры из Месопотамии, вместе со способом записи, образуют древнейшую из известных позиционных систем. Не очень понятно, что произошло с нулём: на самых древних табличках нуль не обнаруживается, но на более поздних появился некоторый вариант, записываемый двумя диагональными палочками, который, конечно, не совсем решил проблему неоднозначной интерпретации. Впрочем, может, у древних шумеро-вавилонских инженеров тут никакой неоднозначной интерпретации и не возникало.
Комментировать »
Часто попадается популярная иллюстрация к протоколу Диффи-Хеллмана, использующая аналогию “смешивания красок”: то есть, стороны, пытающиеся получить общий секрет, смешивают исходную краску, которая известна всем, каждая со своей секретной краской, а потом обмениваются результатами по открытому каналу и смешивают со своим секретом уже результат второй стороны (см. ниже), получая, таким образом, одинаковый “цвет”. Например, в английской “Википедии” сейчас дана именно такая иллюстрация.

(Source: Wikimedia)
Оказывается, такая картинка – не самый плохой способ объяснить что-то содержательное про протокол DH “на пальцах”, пусть некоторые требования и остаются за кадром. Например, эта схема не позволяет показать, почему атакующая сторона не может “перебрать все краски” (естественно, в практическом случае с числами).
Посмотрим на основные моменты, которые подразумевает имеющаяся картинка. Во-первых, результат смешивания красок должно быть трудно обратить – важнейшее свойство для DH, который строится на “однонаправленных” функциях (об этом написано на исходной картинке). То есть, сторона, наблюдающая обмен, не может простым способом разделить краски. Вспомните, что исходная краска, – которая соответствует генератору в “настоящем” DH, – известна всем, в том числе, стороне, пытающейся взломать обмен. По условию – выделить эту краску из смешанных “цветов” оказывается вычислительно невозможно. Во-вторых, операция смешивания красок должна быть “коммутативной” (условно!) – то есть, результат не отличается от порядка подмешивания ингредиентов сторонами. Это не менее важный математический момент “настоящего” протокола, который позволяет обобщить его и перенести с классических вычетов на совсем другие структуры.
Вообще, “коммутативность” тут именно в кавычках, поскольку математическая структура несколько сложнее. Запишем операцию в виде формулы, где “*” соответствует смешиванию красок и действует слева: α * G – означает, что секретная краска α смешана с краской G (чтобы как-то обосновать “левое и правое” действие – считаем, что колер α добавлен в банку к краске G). Тогда: β * (α * G) == α * (β * G) – это то, что происходит в протоколе с красками. То есть, сторона A получает результат смешивания (β * G) и замешивает свою краску α: α * (β * G). Сторона B – наоборот. Казалось бы, должно выполняться (α * β) == (β * α), тогда работает схема. Именно так устроено в классическом DH, но с показателями степенй: (G^β)^α == (G^α)^β. Однако для протокола на красках, вообще говоря, (α * β) == (β * α) хоть и очевидно работает в “локальном” случае, но не применимо в иллюстрируемой схеме – ведь ни у стороны A, ни у стороны B нет готового состава (α * β) – они не могут его приготовить по условию “неразделимости” красок. Вариант (α * β) * G провернуть не получится, по условиям задачи: краски – это не натуральные числа. Поэтому-то “коммутативность” тут – это не настоящая коммутативность (без кавычек). Требуется именно “одинаковость” действия и краски α, и краски β на уже смешанные и подходящие краски. Это можно переписать в других обозначениях: A := α * G (подмешали α в G); B := β * G (подмешали β в G); α * B == β * A – потребовали выполнения базового свойства, необходимого для работы протокола DH на красках: β переводит A в тот же цвет, в который α переводит B.
И это вовсе не беспричинно въедливое разбирательство. Любой химик вам подтвердит, что в химии – далеко не всегда порядок подмешивания ингредиентов не влияет на результат. Формулы веществ это хорошо, но иногда нарушение порядка смешивания может так повлиять, что лабораторию придётся ремонтировать или даже строить новую.
Дело не только в химии: именно обобщение структурной особенности, которая переводит две разных “краски” строго в одну при действии разными элементами (другими “красками”) как раз и позволяет максимально обобщить и сам протокол DH, переписав его в терминах того, что в математике называется “действием группы”. Это, впрочем, отдельная история.
С “красочной” иллюстрацией к DH связан и ещё один занимательный момент. Хорошо, разобрать смешанный “цвет” на составляющие краски трудно – это по условию задачи. Но что мешает атакующему перебрать все доступные краски, подмешивая их к исходной? Результаты смешивания сравниваются с переданными в открытом виде “цветами” (они соответствуют открытым ключам DH) – если цвет совпал, то угадана секретная краска. Естественный вариант защиты: красок слишком много – перебор окажется неприемлемо долгим, а вот сторонам протокола легко выбрать одну секретную краску. Это, опять же, важный математический момент протокола: краски выбрать может быть легко, – вот тысячи, допустим, банок стоят на складских полках, – но реальный DH работает не на красках и там тоже требуется, чтобы был вычислительно простой метод равновероятного случайного выбора нужного числа (секретной “краски”). Да, в классическом варианте с целыми числами – выбрать число случайно и равновероятно не трудно. Вот только тут возникает другая проблема.
Классический вариант DH требует возведения в степень. Показатель степени является секретным ключом. Чтобы атакующая сторона не могла перебрать все показатели за обозримое время, значение должно быть большим. Но как быть стороне протокола, вычисляющей открытый параметр при помощи того же возведения в степень? Последовательно умножать генератор столько же раз, сколько и атакующий – выглядит несколько абсурдно. Хорошо, что знание показателя степени позволяет использовать быстрые методы, построенные на возведении генератора в квадрат и домножении на генератор в “нечётных” случаях: например, чтобы возвести 2 в пятую степень нужно дважды возвести 2 в квадрат и домножить на 2 – (2^2)^2 * 2, это три умножения, а не четыре, как для 2*2*2*2*2. В красках этого нет, но для любого практического воплощения протокола DH данное свойство “удвоения со сложением” необходимо – иначе легитимные стороны протокола оказываются в том же положении, что и атакующая сторона.
Комментировать »
Кратко этот сайт характеризуется так: здесь можно узнать про технологический прогресс, Интернет, математику, криптографию, авиацию, компьютеры, авиационные компьютеры, вооружения, роботов, вооружение роботов, армии мира, астрономию, космические исследования. И иногда о чём-то ещё (