1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
|
; Poseidon Library
;
; Copyright 2024 Provable Inc.
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
; Authors: Alessandro Coglio (www.alessandrocoglio.info)
; Eric McCarthy (bendyarm on GitHub)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(in-package "POSEIDON")
(include-book "kestrel/number-theory/prime" :dir :system)
(include-book "kestrel/prime-fields/prime-fields" :dir :system)
(include-book "kestrel/utilities/lists/all-len-equal-p" :dir :system)
(include-book "projects/pfcs/pfield-lib-ext" :dir :system)
(include-book "std/typed-lists/nat-listp" :dir :system)
(local (include-book "kestrel/prime-fields/prime-fields-rules" :dir :system))
(local (include-book "std/basic/ifix" :dir :system))
(local (include-book "std/basic/nfix" :dir :system))
(local (include-book "std/lists/len" :dir :system))
(local (include-book "std/lists/nthcdr" :dir :system))
(local (include-book "std/lists/repeat" :dir :system))
(local (include-book "std/lists/take" :dir :system))
(local (include-book "kestrel/built-ins/disable" :dir :system))
(local (acl2::disable-most-builtin-logic-defuns))
(local (acl2::disable-builtin-rewrite-rules-for-defaults))
(set-induction-depth-limit 0)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defxdoc+ poseidon-main-definition
:parents (poseidon)
:short "Main definition of the Poseidon hash function."
:long
(xdoc::topstring
(xdoc::p
"See @(see poseidon) for an overview.")
(xdoc::p
"Poseidon is parameterized over a number of aspects,
such as the round constants, the MDS matrix, etc.
We capture all these parameters in the @(tsee param) data structure,
which also includes parameters for aspects that
are not explicitly described in the Poseidon paper
but that nevertheless need to be made precise in the definition.
Perhaps these latter aspects are disambiguated
by the reference implementation of Poseidon
(which is also linked from the web site linked in @(see poseidon)),
but it stil makes mathematical sense to parameterize the definition
over those aspects.
These are all described in detail in @(tsee param).
The top-level functions of our specification of Poseidon
take a @(tsee param) as an input.")
(xdoc::p
"Poseidon uses a sponge construction, which is a more general concept.
In a sponge construction,
one can absorb any number of elements,
squeeze any number of elements,
and again absorb more elements and then squeeze them,
and so on.
We formalize this by explicating the state of the sponge in @(tsee sponge),
which consists of not only the current vector of elements,
but also an index within the vector
where elements are absorbed or squeezed next,
and an indication of whether we are absorbing or squeezing;
this is described in more detail in @(tsee sponge).
We define functions @(tsee absorb) and @(tsee squeeze)
to absorb and squeeze elements,
which take as input and return as output a sponge state,
besides the other natural inputs and outputs.
This is similar to some existing implementations of Poseidon.")
(xdoc::p
"The aforementioned @(tsee absorb) and @(tsee squeeze) functions
take or return multiple input or output elements.
If these functions were defined ``directly'',
they would be somewhat complicated because of the need to handle
a number of inputs or outputs that will start from the current index
and that may require one or more permutations and index wrap-arounds.
This is especially the case because, as described in @(tsee param),
we support several different ways to absorb and squeeze elements.
To keep things simpler,
we define functions @(tsee absorb1) and @(tsee squeeze1)
that absorb or squeeze a single input or output element:
these are much simpler to define and understand,
even with the several different ways to absorb and squeeze elements.
Then we define @(tsee absorb) and @(tsee squeeze)
by simply iterating @(tsee absorb1) and @(tsee squeeze1).")
(xdoc::p
"At the very top level, we define a function @(tsee hash)
that maps any number of inputs to any number of outputs.
This is defined by internally creating and using a sponge state.
Note that there is no need to include any explicit notion of padding,
which can be performed externally to Poseidon proper as defined here."))
:order-subtopics t
:default-parent t)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(fty::defprod param
:short "Fixtype of Poseidon parameters."
:long
(xdoc::topstring
(xdoc::p
"Our definition of Poseidon is parameterized
over the following aspects:")
(xdoc::ul
(xdoc::li
"A prime number @($p$), which defines the prime field @($\\mathbb{F}_p$).")
(xdoc::li
"The rate @($r$), i.e. the number of the elements in the state vector
against which inputs and outputs are absorbed or squeezed.
This is a positive integer.")
(xdoc::li
"The capacity @($c$), i.e. the number of the elements in the state vector
that participate in the permutations
but against which inputs and output are not absorbed or squeezed.
This is a positive integer.")
(xdoc::li
"The exponent @($\\alpha$) used in the S-boxes.
This is normally taken from a small set of integers,
all positive except for -1,
also based on the prime @($p$).
Mathematically, S-boxes can be defined with any integer @($\\alpha$),
and thus we allow any integer.
For negative integers, we map 0 to 0,
as done for the choice of -1 described in the paper.")
(xdoc::li
"The number @($R_f$) of full rounds before and after the partial rounds,
which is half of the total number of full rounds @($R_F$).
This should be a positive integer,
but mathematically we can define Poseidon even if this is 0
(i.e. no full rounds),
so we allow any natural number.")
(xdoc::li
"The number @($R_P$) of partial rounds.
This should be a positive integer,
but mathematically we can define Poseidon even if this is 0
(i.e. no partial rounds),
so we allow any natural number.")
(xdoc::li
"The constants to add to the state vector as part of the permutation.
These are organized as a list of lists of field elements.
Each element of the outer list corresponds to a round,
so the length of the outer list is @($R_F + R_P$) (i.e. @($2 R_f + R_P$)).
Each inner list has @($r + c$) elements,
which matches the length of the state vector.")
(xdoc::li
"The MDS matrix, which is organized as a list of lists of field elements.
(MDS stands for Maximum Distance Separable, but we do not describe here
how the matrix is constructed.)
This is a square matrix, so the length of the outer list is @($r + c$)
and each inner list has also length @($r + c$).
We can equivalently view this as a list of rows or columns;
if rows, we must view the state as a column vector;
if columns, we must the state as a row vector.
The multiplication between the matrix and the vector
gives the same result (viewed either as column or row vector).")
(xdoc::li
"The state is represented as a list in ACL2.
The state has two parts, corresponding to the @($r$) and @($c$) elements.
Looking at the ACL2 list, we could have
either the former before the latter
or the latter before the former:"
(xdoc::@{}
"+------------------+--------------+"
"| r elements | c elements |"
"+------------------+--------------+"
""
"+--------------+------------------+"
"| c elements | r elements |"
"+--------------+------------------+")
"So we include a boolean parameter that says whether, in the ACL2 list,
the rate elements come before the capacity elements or vice versa.")
(xdoc::li
"Regardless of the choice described just above, there is another choice,
namely the direction in which inputs or outputs
are absorbed or squeezed against the @($r$) elements of the state,
which also form an ACL2 list (sublist of the state vector).
We can either absorb or squeeze them with ascending list indices,
or with descending list indices.
In other words, in the illustrations above,
we either go rightward (if ascending) or leftward (if descending).
So we introduce a parameter for this choice.")
(xdoc::li
"In a partial round, the S-box is applied only to one element of the state.
The element is at one end of the state vector,
but it could be either the first or the last element in the ACL2 list.
So we introduce a parameter for this choice.
Mathematically, we could generalize this to
an arbitrary position within the state vector;
we could even generalize full and partial rounds
to sets of indices;
however, at this time this generality seems unnecessary.")))
((prime prime)
(rate pos)
(capacity pos)
(alpha int)
(full-rounds-half nat)
(partial-rounds nat)
(constants :reqfix (if (and (fe-list-listp constants prime)
(all-len-equal-p constants (+ rate capacity))
(equal (len constants) (+ (* 2 full-rounds-half)
partial-rounds)))
constants
(repeat (+ (* 2 full-rounds-half) partial-rounds)
(repeat (+ rate capacity) 0))))
(mds :reqfix (if (and (fe-list-listp mds prime)
(all-len-equal-p mds (+ rate capacity))
(equal (len mds) (+ rate capacity)))
mds
(repeat (+ rate capacity) (repeat (+ rate capacity) 0))))
(rate-then-capacity-p bool)
(ascending-p bool)
(partial-first-p bool))
:require (and (fe-list-listp constants prime)
(all-len-equal-p constants (+ rate capacity))
(equal (len constants) (+ (* 2 full-rounds-half)
partial-rounds))
(fe-list-listp mds prime)
(all-len-equal-p mds (+ rate capacity))
(equal (len mds) (+ rate capacity)))
:pred paramp
:prepwork
((local (in-theory (enable pfield::true-list-listp-when-fe-list-listp)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define param->capacity-then-rate-p ((param paramp))
:returns (yes/no booleanp)
:short "Negation of the @(tsee param->rate-then-capacity-p) parameter."
(not (param->rate-then-capacity-p param))
:hooks (:fix))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define param->descending-p ((param paramp))
:returns (yes/no booleanp)
:short "Negation of the @(tsee param->ascending-p) parameter."
(not (param->ascending-p param))
:hooks (:fix))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define param->partial-last-p ((param paramp))
:returns (yes/no booleanp)
:short "Negation of the @(tsee param->partial-first-p) parameter."
(not (param->partial-first-p param))
:hooks (:fix))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define param->size ((param paramp))
:returns (r+c posp)
:short "Size of the state vector, i.e. @($r + c$)."
(+ (param->rate param)
(param->capacity param))
:hooks (:fix))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define param->rounds ((param paramp))
:returns (rounds natp)
:short "Total number of rounds, i.e. @($2 R_f + R_P = R_F + R_P$)."
(+ (param->full-rounds-half param)
(param->partial-rounds param)
(param->full-rounds-half param))
:hooks (:fix))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defsection param-additional-theorems
:short "Additional theorems about the parameters in @(tsee param)."
(defrule posp-of-param->prime
(posp (param->prime param)))
(defrule all-len-equal-p-of-param->constants
(all-len-equal-p (param->constants param)
(param->size param))
:enable param->size)
(defrule all-len-equal-p-of-param->mds
(all-len-equal-p (param->mds param)
(param->size param))
:enable param->size)
(defrule len-of-param->constants
(equal (len (param->constants param))
(param->rounds param))
:enable param->rounds)
(defrule len-of-param->mds
(equal (len (param->mds param))
(param->size param))
:enable param->size)
(defrule param->rate-less-than-size
(< (param->rate param)
(param->size param))
:rule-classes :linear
:enable param->size)
(defrule param->capacity-less-than-size
(< (param->capacity param)
(param->size param))
:rule-classes :linear
:enable param->size)
(defrule param->size-equal-rate+capacity
(equal (param->size param)
(+ (param->rate param)
(param->capacity param)))
:rule-classes :linear
:enable param->size))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define add-round-constants ((stat (fe-listp stat prime))
(constants (fe-listp constants prime))
(prime primep))
:guard (equal (len constants) (len stat))
:returns (new-stat (fe-listp new-stat prime)
:hyp (posp prime)
:name fe-listp-of-add-round-constants)
:short "Add round constants to the state vector."
:long
(xdoc::topstring
(xdoc::p
"This is the @($\\mathit{AddRoundConstants}$) function in the paper.")
(xdoc::p
"Besides the state, it needs the constants for the round,
in number that matches the state vector.")
(xdoc::p
"This could be a more general ACL2 function
to add two vectors of field elements."))
(cond ((endp stat) nil)
(t (cons (add (car stat) (car constants) prime)
(add-round-constants (cdr stat) (cdr constants) prime))))
///
(defret len-of-add-round-constants
(equal (len new-stat)
(len stat))
:hints (("Goal" :induct t))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define pow-by-alpha ((elem (fep elem prime))
(alpha integerp)
(prime primep))
:returns (new-elem (fep new-elem prime)
:hyp (primep prime)
:name fep-of-pow-by-alpha)
:short "Raise a field element to the @($\\alpha$) power."
:long
(xdoc::topstring
(xdoc::p
"If the exponent is negative,
we map 0 to 0 and
we map non-zero elements to the inverses of their positive powers.
Note that raising a non-zero field element to a power
yields a non-zero element."))
(if (< alpha 0)
(if (= elem 0)
0
(inv (pow elem (- alpha) prime) prime))
(pow elem alpha prime))
:guard-hints (("Goal" :in-theory (enable pfield::pow-0-equiv))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define sub-words-full ((stat (fe-listp stat prime))
(alpha integerp)
(prime primep))
:returns (new-stat (fe-listp new-stat prime)
:hyp (primep prime)
:name fe-listp-of-sub-wordsfull)
:short "Apply the full S-box substitution to the state vector."
:long
(xdoc::topstring
(xdoc::p
"We raise to the @($\\alpha$) power every element of the state vector."))
(cond ((endp stat) nil)
(t (cons (pow-by-alpha (car stat) alpha prime)
(sub-words-full (cdr stat) alpha prime))))
///
(defret len-of-sub-words-full
(equal (len new-stat)
(len stat))
:hints (("Goal" :induct t))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define sub-words-partial ((stat (fe-listp stat prime))
(alpha integerp)
(partial-first-p booleanp)
(prime primep))
:returns (new-stat (fe-listp new-stat prime)
:hyp (and (primep prime)
(fe-listp stat prime))
:name fe-listp-of-sub-words-partial)
:short "Apply the partial S-box substitution to the state vector."
:long
(xdoc::topstring
(xdoc::p
"We raise to the @($\\alpha$) power either the first or the last element
of the state vector, depending on the @('partial-first-p') parameter.
If the state vector is empty, we return the empty state vector;
this does not happen in Poseidon because @($r + c > 0$),
but in this function definition we do not need that requirement."))
(if (consp stat)
(if partial-first-p
(cons (pow-by-alpha (car stat) alpha prime)
(cdr stat))
(append (butlast stat 1)
(list (pow-by-alpha (car (last stat)) alpha prime))))
nil)
///
(defret len-of-sub-words-partial
(equal (len new-stat)
(len stat))
:hints (("Goal" :in-theory (enable butlast fix)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define sub-words ((stat (fe-listp stat prime))
(alpha integerp)
(partial-first-p booleanp)
(prime primep)
(full-p booleanp))
:returns (new-stat (fe-listp new-stat prime)
:hyp (and (primep prime)
(fe-listp stat prime))
:name fe-listp-of-sub-words)
:short "Apply the S-box substitution to the state vector."
:long
(xdoc::topstring
(xdoc::p
"This is the @($\\mathit{SubWords}$) function in the paper.")
(xdoc::p
"The substitution is full or partial,
depending on the @('full-p') parameter."))
(if full-p
(sub-words-full stat alpha prime)
(sub-words-partial stat alpha partial-first-p prime))
///
(defret len-of-sub-words
(equal (len new-stat)
(len stat))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define dot-product ((row (fe-listp row prime))
(stat (fe-listp stat prime))
(prime primep))
:guard (equal (len stat) (len row))
:returns (elem (fep elem prime)
:hyp (posp prime)
:name fep-of-dot-product)
:short "Dot product of a matrix row and the state vector."
:long
(xdoc::topstring
(xdoc::p
"This views the state as a column vector.
However, as explained in @(tsee param),
we could equivalently view the matrix row as a matrix column
and the state as a row vector.")
(xdoc::p
"This could be a more general ACL2 function
to perform the dot product of two vectors of field elements."))
(cond ((endp row) 0)
(t (add (mul (car row) (car stat) prime)
(dot-product (cdr row) (cdr stat) prime)
prime)))
:verify-guards :after-returns)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define mix-layer ((rows (fe-list-listp rows prime))
(stat (fe-listp stat prime))
(prime primep))
:guard (all-len-equal-p rows (len stat))
:returns (elems (fe-listp elems prime)
:hyp (posp prime)
:name fe-listp-of-mix-layer)
:short "Multiply the MDS matrix and the state vector."
:long
(xdoc::topstring
(xdoc::p
"This is the @($\\mathit{MixLayer}$) function in the paper.")
(xdoc::p
"This views the matrix as a list of rows and the state as a column vector,
but as explained in @(tsee param) and mentioned in @(tsee dot-product)
we could equivalently view the matrix rows as matrix columns
and the state as a row vector."))
(cond ((endp rows) nil)
(t (cons (dot-product (car rows) stat prime)
(mix-layer (cdr rows) stat prime))))
:guard-hints
(("Goal" :in-theory (enable pfield::true-list-listp-when-fe-list-listp)))
///
(defret len-of-mix-layer
(equal (len elems)
(len rows))
:hints (("Goal" :induct t))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define round ((stat (fe-listp stat prime))
(constants (fe-listp constants prime))
(alpha integerp)
(partial-first-p booleanp)
(mds (fe-list-listp mds prime))
(prime primep)
(full-p booleanp))
:guard (and (equal (len constants) (len stat))
(all-len-equal-p mds (len stat)))
:returns (new-stat (fe-listp new-stat prime)
:hyp (primep prime)
:name fe-listp-of-round)
:short "Perform a round."
:long
(xdoc::topstring
(xdoc::p
"We add the round constants,
we apply the S-box substitution,
and we multiply by the MDS matrix.
The round may be full or partial,
which affects only the S-box substitution."))
(b* ((stat (add-round-constants stat constants prime))
(stat (sub-words stat alpha partial-first-p prime full-p))
(stat (mix-layer mds stat prime)))
stat)
:guard-hints
(("Goal" :in-theory (enable pfield::true-list-listp-when-fe-list-listp)))
///
(defret len-of-round
(equal (len new-stat)
(len stat))
:hyp (equal (len mds) (len stat))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define full-rounds ((stat (fe-listp stat prime))
(constants (fe-list-listp constants prime))
(alpha integerp)
(partial-first-p booleanp)
(mds (fe-list-listp mds prime))
(prime primep))
:guard (and (all-len-equal-p constants (len stat))
(all-len-equal-p mds (len stat))
(equal (len mds) (len stat)))
:returns (new-stat (fe-listp new-stat prime)
:hyp (and (primep prime)
(fe-listp stat prime))
:name fe-listp-of-full-rounds)
:short "Perform a sequence of full rounds."
:long
(xdoc::topstring
(xdoc::p
"The number of full rounds in the sequence is determined by
the length of the list of lists of constants passed as input."))
(b* (((when (endp constants)) stat)
(stat (round stat (car constants) alpha partial-first-p mds prime t)))
(full-rounds stat (cdr constants) alpha partial-first-p mds prime))
:guard-hints
(("Goal" :in-theory (enable pfield::true-list-listp-when-fe-list-listp)))
///
(defret len-of-full-rounds
(equal (len new-stat)
(len stat))
:hyp (equal (len mds) (len stat))
:hints (("Goal" :induct t))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define partial-rounds ((stat (fe-listp stat prime))
(constants (fe-list-listp constants prime))
(alpha integerp)
(partial-first-p booleanp)
(mds (fe-list-listp mds prime))
(prime primep))
:guard (and (all-len-equal-p constants (len stat))
(all-len-equal-p mds (len stat))
(equal (len mds) (len stat)))
:returns (new-stat (fe-listp new-stat prime)
:hyp (and (primep prime)
(fe-listp stat prime))
:name fe-listp-of-partial-rounds)
:short "Perform a sequence of partial rounds."
:long
(xdoc::topstring
(xdoc::p
"The number of partial rounds in the sequence is determined by
the length of the list of lists of constants passed as input."))
(b* (((when (endp constants)) stat)
(stat (round stat (car constants) alpha partial-first-p mds prime nil)))
(partial-rounds stat (cdr constants) alpha partial-first-p mds prime))
:guard-hints
(("Goal" :in-theory (enable pfield::true-list-listp-when-fe-list-listp)))
///
(defret len-of-partial-rounds
(equal (len new-stat)
(len stat))
:hyp (equal (len mds) (len stat))
:hints (("Goal" :induct t))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define all-rounds ((stat (fe-listp stat prime))
(constants (fe-list-listp constants prime))
(alpha integerp)
(partial-first-p booleanp)
(mds (fe-list-listp mds prime))
(full-rounds-half natp)
(partial-rounds natp)
(prime primep))
:guard (and (all-len-equal-p constants (len stat))
(equal (len constants) (+ (* 2 full-rounds-half) partial-rounds))
(all-len-equal-p mds (len stat))
(equal (len mds) (len stat)))
:returns (new-stat (fe-listp new-stat prime)
:hyp (and (primep prime)
(fe-listp stat prime))
:name fe-listp-of-all-rounds)
:short "Perform all the rounds in a permutation."
:long
(xdoc::topstring
(xdoc::p
"We pass the number of initial and final full rounds @($R_f$)
and the number of partial rounds @($R_P$),
along with a list of lists of constants
where the outer list has length @($2 R_f + R_P$)."))
(b* ((full-rounds-first-constants (take full-rounds-half constants))
(partial-rounds-constants (take partial-rounds
(nthcdr full-rounds-half constants)))
(full-rounds-last-constants (nthcdr (+ full-rounds-half
partial-rounds)
constants))
(stat (full-rounds stat
full-rounds-first-constants
alpha
partial-first-p
mds
prime))
(stat (partial-rounds stat
partial-rounds-constants
alpha
partial-first-p
mds
prime))
(stat (full-rounds stat
full-rounds-last-constants
alpha
partial-first-p
mds
prime)))
stat)
:guard-hints
(("Goal" :in-theory (enable pfield::true-list-listp-when-fe-list-listp
nfix)))
///
(defret len-of-all-rounds
(equal (len new-stat)
(len stat))
:hyp (equal (len mds) (len stat)))
(defret nat-listp-of-all-rounds
(nat-listp new-stat)
:hyp (and (primep prime)
(fe-listp stat prime))
:hints (("Goal"
:use fe-listp-of-all-rounds
:in-theory (e/d (pfield::nat-listp-when-fe-listp)
(all-rounds fe-listp-of-all-rounds))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define permute ((stat (fe-listp stat (param->prime param)))
(param paramp))
:guard (equal (len stat) (param->size param))
:returns (new-stat (fe-listp new-stat (param->prime param))
:hyp (fe-listp stat (param->prime param)))
:short "Permutation."
:long
(xdoc::topstring
(xdoc::p
"This is a wrapper of @(tsee all-rounds)
that provides a more compact interface,
consisting of a state vector and the parameters,
whose components are passed to @(tsee all-rounds)."))
(all-rounds stat
(param->constants param)
(param->alpha param)
(param->partial-first-p param)
(param->mds param)
(param->full-rounds-half param)
(param->partial-rounds param)
(param->prime param))
:guard-hints (("Goal" :in-theory (enable param->rounds)))
///
(defret len-of-permute
(equal (len new-stat)
(len stat))
:hyp (and (equal (len stat) (param->size param))
(paramp param))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(fty::deftagsum mode
:short "Fixtype of sponge modes."
:long
(xdoc::topstring
(xdoc::p
"The sponge is either absorbing or squeezing."))
(:absorb ())
(:squeeze ())
:pred modep)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(fty::defprod sponge
:short "Fixtype of sponge states."
:long
(xdoc::topstring
(xdoc::p
"This consists of a state vector,
an absorbing or squeezing mode,
and an index.
The index points to an element of the sublist of the state vector
that consists of the @($r$) elements against which
inputs are absorbed or outputs are squeezed:
the index indicates the next element absorbed or squeezed.")
(xdoc::p
"Here we just say that the state vector is a list of natural numbers,
because we do not have the prime that defines the prime field.
Requirements on the state vector,
and on the absorbing or squeezing index,
are expressed in @(tsee sponge-validp),
since they involve the parameters."))
((stat nat-list)
(mode mode)
(index nat))
:pred spongep)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define sponge-validp ((sponge spongep) (param paramp))
:returns (yes/no booleanp)
:short "Check if a sponge state is valid with respect to given parameters."
:long
(xdoc::topstring
(xdoc::p
"The state vector must consist of prime field elements,
and must have length equal to @($r + c$).
Furthermore, the index must not exceed @($r$).
We allow the index to be @($r$),
which happens just after we have absorbed or squeezed
all of the @($r$) elements of the state vector.
If one more element is absorbed and squeezed,
a permutation is performed and the index is reset to 0
(see @(tsee absorb1) and @(tsee squeeze1)),
but in case there is no next element absorbed or squeezed,
we avoid the permutation by letting the index be @($r$)."))
(and (fe-listp (sponge->stat sponge) (param->prime param))
(equal (len (sponge->stat sponge)) (param->size param))
(<= (sponge->index sponge)
(param->rate param)))
:hooks (:fix))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define init-sponge ((size natp))
:returns (sponge spongep)
:short "Initial sponge state."
:long
(xdoc::topstring
(xdoc::p
"The state vector consists of all zeros;
we pass @($r+ c$) to determine the size.
The mode is absorbing.
The index is 0."))
(make-sponge :stat (repeat size 0)
:mode (mode-absorb)
:index 0)
///
(defrule sponge-validp-of-init-sponge
(sponge-validp (init-sponge (param->size param)) param)
:enable sponge-validp))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define absorb1 ((input (fep input (param->prime param)))
(sponge spongep)
(param paramp))
:guard (sponge-validp sponge param)
:returns (new-sponge spongep)
:short "Absorb one element into the sponge."
:long
(xdoc::topstring
(xdoc::p
"If the index is @($r$) or the mode is squeezing,
we permute the state and reset the index to 0.")
(xdoc::p
"If the parameters say that indices are increasing in the list,
the index of the sponge is also
the index in the sublist of @($r$) elements.
Otherwise, we flip the index.")
(xdoc::p
"Given the index in the sublist of @($r$) elements,
we obtain the index in the stat list as follows.
If the @($r$) elements come before the @($c$) elements,
it is the same index.
Otherwise, we add @($c$) to the index.")
(xdoc::p
"The resulting index is the one of
the state element to be combined with the input.
The combination is field addition,
whose result replaces the element in the state.")
(xdoc::p
"We return an updated sponge state
with the updated state vector,
the next index,
and the aborbing mode
(unchanged if the sponge was already absorbing).
Note that the index in the sponge state is always increasing,
regardless of the parameter about
the ascending or descending indices in the state vector list."))
(b* (((sponge sponge) sponge)
((param param) param)
((mv stat index)
(if (or (equal sponge.index param.rate)
(mode-case sponge.mode :squeeze))
(mv (permute sponge.stat param) 0)
(mv sponge.stat sponge.index)))
(list-index-in-rate (if param.ascending-p
index
(- (1- param.rate) index)))
(list-index-in-stat (if param.rate-then-capacity-p
list-index-in-rate
(+ param.capacity list-index-in-rate)))
(stat (update-nth list-index-in-stat
(add (nth list-index-in-stat stat)
input
param.prime)
stat)))
(make-sponge :stat stat :mode (mode-absorb) :index (1+ index)))
:guard-hints (("Goal" :in-theory (enable permute
sponge-validp
fix
nfix)))
///
(more-returns
(new-sponge (sponge-validp new-sponge param)
:hyp (sponge-validp sponge param)
:name sponge-validp-of-absorb1
:hints (("Goal" :in-theory (enable permute
sponge-validp
max
fix
nfix))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define squeeze1 ((sponge spongep) (param paramp))
:guard (sponge-validp sponge param)
:returns (mv (output (fep output (param->prime param))
:hyp (sponge-validp sponge param)
:name fep-of-squeeze1.output
:hints (("Goal" :in-theory (enable permute
sponge-validp
nfix
fix))))
(new-sponge spongep))
:short "Squeeze one element from the sponge."
:long
(xdoc::topstring
(xdoc::p
"This is quite analogous to @(tsee absorb1)
(see that function's documentation),
with the roles of the modes swapped.
The main difference is that the state vector is unchanged,
and instead we return the element of the state pointed to by the index."))
(b* (((sponge sponge) sponge)
((param param) param)
((mv stat index)
(if (or (equal sponge.index param.rate)
(mode-case sponge.mode :absorb))
(mv (permute sponge.stat param) 0)
(mv sponge.stat sponge.index)))
(list-index-in-rate (if param.ascending-p
index
(- (1- param.rate) index)))
(list-index-in-stat (if param.rate-then-capacity-p
list-index-in-rate
(+ param.capacity list-index-in-rate)))
(output (nth list-index-in-stat stat)))
(mv output
(make-sponge :stat stat :mode (mode-squeeze) :index (1+ index))))
:guard-hints (("Goal" :in-theory (enable permute
sponge-validp
fix)))
///
(more-returns
(new-sponge (sponge-validp new-sponge param)
:hyp (sponge-validp sponge param)
:name sponge-validp-of-squeeze1
:hints (("Goal" :in-theory (enable permute
sponge-validp))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define absorb ((inputs (fe-listp inputs (param->prime param)))
(sponge spongep)
(param paramp))
:guard (sponge-validp sponge param)
:returns (new-sponge spongep)
:short "Absorb any number of elements into the sponge."
:long
(xdoc::topstring
(xdoc::p
"We use @(tsee absorb1) on each input,
threading the sponge state through the sequence."))
(b* (((when (endp inputs)) (sponge-fix sponge))
(sponge (absorb1 (car inputs) sponge param)))
(absorb (cdr inputs) sponge param))
///
(more-returns
(new-sponge (sponge-validp new-sponge param)
:hyp (sponge-validp sponge param)
:name sponge-validp-of-absorb
:hints (("Goal" :induct t)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define squeeze ((count natp) (sponge spongep) (param paramp))
:guard (sponge-validp sponge param)
:returns (mv (outputs (fe-listp outputs (param->prime param))
:hyp (sponge-validp sponge param)
:name fe-listp-of-squeeze.outputs)
(new-sponge spongep))
:short "Squeeze any number of elements from the sponge."
:long
(xdoc::topstring
(xdoc::p
"We use @(tsee squeeze1) for each output,
whose count is passed to this ACL2 function,
threading the sponge state through the sequence."))
(b* (((when (zp count)) (mv nil (sponge-fix sponge)))
((mv output sponge) (squeeze1 sponge param))
((mv outputs sponge) (squeeze (1- count) sponge param)))
(mv (cons output outputs) sponge))
///
(more-returns
(new-sponge (sponge-validp new-sponge param)
:hyp (sponge-validp sponge param)
:name sponge-validp-of-squeeze
:hints (("Goal" :induct t))))
(defret len-of-squeeze.outputs
(equal (len outputs)
(nfix count))
:hints (("Goal" :induct t :in-theory (enable nfix fix)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define hash ((inputs (fe-listp inputs (param->prime param)))
(param paramp)
(count natp))
:returns (outputs (fe-listp outputs (param->prime param))
:name fe-listp-of-hash)
:short "Hash any number of inputs to any number of outputs."
:long
(xdoc::topstring
(xdoc::p
"We initialize the sponge,
we absorb all the inputs,
and we squeeze all the outputs.")
(xdoc::p
"Note that inputs that only differ in
one input having more trailing zeros than the other
and not exceeeding a multiple of the rate length
result in the same outputs.
For instance, if @($r$) is 10,
the inputs @('(17)'), @'(17 0)'), @('(17 0 0)'), etc.
(up to @('(17 0 0 0 0 0 0 0 0 0)') yield the same output,
because the initial state vector consists of all zeros,
and absorbing a 0 does not change the corresponding element.
Therefore, the ACL2 function defined here
should not be normally used as a collision-resistant hash;
instead, a caller of this function should use padding,
or other techniques like domain separation,
to supply input strings that do not lead
to the aforementioned collisions.
The exact nature of these techniques is application-dependent;
Poseidon itself does not appear to prescribe particular techniques."))
(b* ((sponge (init-sponge (param->size param)))
(sponge (absorb inputs sponge param))
((mv outputs &) (squeeze count sponge param)))
outputs)
///
(more-returns
(outputs true-listp
:rule-classes :type-prescription
:hints (("Goal"
:use fe-listp-of-hash
:in-theory (disable fe-listp-of-hash)))))
(defret len-of-hash
(equal (len outputs)
(nfix count)))
(defret consp-of-hash
(equal (consp outputs)
(< 0 (nfix count)))
:hints (("Goal"
:use len-of-hash
:in-theory (disable len-of-hash hash))))
(in-theory (disable consp-of-hash)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define hashp ((inputs (fe-listp inputs (param->prime param)))
(param paramp)
(count natp))
:guard (and (<= (len inputs) (param->size param))
(<= count (param->size param)))
:returns (outputs (fe-listp outputs (param->prime param))
:name fe-listp-of-hashp
:hyp (and (paramp param)
(fe-listp inputs (param->prime param))
(<= (len inputs) (param->size param))
(<= count (param->size param)))
:hints (("Goal" :in-theory (enable nfix))))
:short "Hash according to just the Poseidon permutation."
:long
(xdoc::topstring
(xdoc::p
"Some applications do not use the Poseidon sponge,
but instead just use the Poseidon permutation.
That is, given @($r + c$) or fewer field elements,
which fit into a Poseidon state vector,
we can apply @(tsee permute),
obtain an output state vector of @($r + c$) field elements,
and select possibly all or a prefix of them.
Since a single field element may consist of hundreds of bits,
just a few field elements (even just one)
may well suffice as inputs and outputs for certain applications.")
(xdoc::p
"This ACL2 function formalizes this hash based on just the permutation;
the @('p') in the name of @('hashp') conveys that.
The function has guards limiting the number of inputs and outputs.
If there are fewer than @($r + c$) inputs,
we pad them with zeros,
in line with the full Poseidon sponge.
Poseidon itself prescribes no padding,
which is application-dependent,
and can be applied prior to calling this ACL2 function;
if exactly @($r + c$) field elements are passed to this function,
this function does not add any padding zeros.")
(xdoc::p
"Since @(tsee permute) takes as input
the full Poseidon parameters @(tsee param),
this @('hashp') function also takes them as input.
However, as in @(tsee permute), not all of the parameters are needed,
namely the ones related to the sponge like @('ascending-p').
Additionally, the permutation only depends on @(tsee param->size),
not on the specifics of rate @($r$) and capacity @($c$) (just their sum).
We may consider changing @(tsee permute) and this function
to only take the necessary parameters at some point."))
(b* ((stat (append inputs
(repeat (- (param->size param) (len inputs)) 0)))
(new-stat (permute stat param)))
(take count new-stat))
:guard-hints (("Goal" :in-theory (enable natp)))
///
(more-returns
(outputs true-listp
:rule-classes :type-prescription))
(defret len-of-hashp
(equal (len outputs)
(nfix count))))
|