package mldsa
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
Pure OCaml implementation of FIPS 204 ML-DSA
Install
dune-project
Dependency
Authors
Maintainers
Sources
v0.2.0.tar.gz
md5=8134ec4c6a35345a91223c34d5a133db
sha512=617282d33c5fb909be5b9b2e4b2c1830447c80206ad1e8f3ddb56459c40e9ff810489fa3ec927ffaa61148f826d81e71520816badce7bbebfde08a61eeea0b85
doc/src/mldsa/mldsa_engine.ml.html
Source file mldsa_engine.ml
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 966type error = | Invalid_length of { what : string; expected : int; actual : int } | Invalid_encoding of string | Context_too_long of int | Signing_failed let pp_error formatter = function | Invalid_length { what; expected; actual } -> Format.fprintf formatter "%s has length %d, expected %d" what actual expected | Invalid_encoding message -> Format.pp_print_string formatter message | Context_too_long length -> Format.fprintf formatter "context has length %d, expected at most 255" length | Signing_failed -> Format.pp_print_string formatter "ML-DSA rejection sampling exceeded the FIPS 204 iteration limit" (* FIPS 204 IntegerToBytes(value, 2), the nonce encoding of ExpandA, ExpandS and ExpandMask. Every nonce the engine derives is below 2^16; reject anything else rather than truncate it into a repeated nonce. *) let u16_le value = if value < 0 || value > 0xffff then invalid_arg (Printf.sprintf "ML-DSA: nonce %d does not fit in 16 bits" value); let bytes = Bytes.create 2 in Bytes.unsafe_set bytes 0 (Char.unsafe_chr (value land 0xff)); Bytes.unsafe_set bytes 1 (Char.unsafe_chr (value lsr 8)); Bytes.unsafe_to_string bytes module type PARAMETERS = sig val name : string val k : int val l : int val eta : int val tau : int val gamma1 : int val gamma2 : int val omega : int val c_tilde_bytes : int end module type S = sig type nonrec error = error = | Invalid_length of { what : string; expected : int; actual : int } | Invalid_encoding of string | Context_too_long of int | Signing_failed val pp_error : Format.formatter -> error -> unit type signing_key type verification_key type signature val seed_size : int val signing_key_size : int val verification_key_size : int val signature_size : int val generate : random:(int -> string) -> unit -> signing_key * verification_key val signing_key_of_seed : string -> (signing_key, error) result val signing_key_to_seed : signing_key -> string option val signing_key_of_octets : string -> (signing_key, error) result val signing_key_to_octets : signing_key -> string val verification_key_of_signing_key : signing_key -> verification_key val verification_key_of_octets : string -> (verification_key, error) result val verification_key_to_octets : verification_key -> string val signature_of_octets : string -> (signature, error) result val signature_to_octets : signature -> string val sign : ?context:string -> random:(int -> string) -> signing_key -> message:string -> (signature, error) result val sign_deterministic : ?context:string -> signing_key -> message:string -> (signature, error) result val verify : ?context:string -> verification_key -> message:string -> signature -> bool end module type INTERNAL = sig include S val sign_internal_for_testing : signing_key -> formatted_message:string -> randomness:string -> (signature, error) result val sign_mu_for_testing : signing_key -> mu:string -> randomness:string -> (signature, error) result val verify_internal_for_testing : verification_key -> formatted_message:string -> signature -> bool val verify_mu_for_testing : verification_key -> mu:string -> signature -> bool val use_hint_for_testing : int -> int -> int val encode_signature_for_testing : c_tilde:string -> z:int array array -> hint:int array array -> string end module Make (P : PARAMETERS) : INTERNAL = struct type nonrec error = error = | Invalid_length of { what : string; expected : int; actual : int } | Invalid_encoding of string | Context_too_long of int | Signing_failed let pp_error = pp_error let q = 8_380_417 let n = 256 let d = 13 let beta = P.tau * P.eta let seed_size = 32 let tr_bytes = 64 let eta_bits = if P.eta = 2 then 3 else 4 let eta_packed_bytes = n * eta_bits / 8 let t0_packed_bytes = 416 let t1_packed_bytes = 320 let z_bits = if P.gamma1 = 1 lsl 17 then 18 else 20 let z_packed_bytes = n * z_bits / 8 let w1_bits = if P.gamma2 = (q - 1) / 88 then 6 else 4 let verification_key_size = seed_size + (P.k * t1_packed_bytes) let signing_key_size = (2 * seed_size) + tr_bytes + ((P.l + P.k) * eta_packed_bytes) + (P.k * t0_packed_bytes) let signature_size = P.c_tilde_bytes + (P.l * z_packed_bytes) + P.omega + P.k type poly = int array type polyvec = poly array type expanded_signing_key = { rho : string; key : string; tr : string; s1 : polyvec; s2 : polyvec; t0 : polyvec; } type verification_key = string type signing_key = { seed : string option; expanded_octets : string; expanded : expanded_signing_key; verification_key : verification_key; } type signature = string let invalid_length what expected value = Error (Invalid_length { what; expected; actual = String.length value }) let get_u8 string index = Char.code (String.unsafe_get string index) let set_u8 bytes index value = Bytes.unsafe_set bytes index (Char.unsafe_chr value) let sub string offset length = String.sub string offset length let norm value = let value = value mod q in if value < 0 then value + q else value let center value = let value = norm value in if value > (q - 1) / 2 then value - q else value let add_mod left right = norm (left + right) let sub_mod left right = norm (left - right) let mul_mod left right = Int64.(to_int (rem (mul (of_int (norm left)) (of_int (norm right))) (of_int q))) let pow_mod base exponent = let rec loop accumulator base exponent = if exponent = 0 then accumulator else let accumulator = if exponent land 1 = 1 then mul_mod accumulator base else accumulator in loop accumulator (mul_mod base base) (exponent lsr 1) in loop 1 base exponent let bit_reverse_8 value = let result = ref 0 in for bit = 0 to 7 do result := (!result lsl 1) lor ((value lsr bit) land 1) done; !result let zetas = Array.init n (fun index -> pow_mod 1753 (bit_reverse_8 index)) let inverse_n = pow_mod n (q - 2) let ntt polynomial = let result = Array.map norm polynomial in let index = ref 0 in let length = ref 128 in while !length > 0 do let start = ref 0 in while !start < n do incr index; let zeta = zetas.(!index) in for j = !start to !start + !length - 1 do let t = mul_mod zeta result.(j + !length) in let value = result.(j) in result.(j) <- add_mod value t; result.(j + !length) <- sub_mod value t done; start := !start + (2 * !length) done; length := !length / 2 done; result let inverse_ntt polynomial = let result = Array.map norm polynomial in let index = ref n in let length = ref 1 in while !length < n do let start = ref 0 in while !start < n do decr index; let zeta = norm (-zetas.(!index)) in for j = !start to !start + !length - 1 do let left = result.(j) in let right = result.(j + !length) in result.(j) <- add_mod left right; result.(j + !length) <- mul_mod zeta (sub_mod left right) done; start := !start + (2 * !length) done; length := !length * 2 done; Array.map (mul_mod inverse_n) result let pointwise left right = Array.init n (fun i -> mul_mod left.(i) right.(i)) let matrix_vector_ntt matrix vector = Array.init P.k (fun row -> let accumulator = Array.make n 0 in for column = 0 to P.l - 1 do let product = pointwise matrix.(row).(column) vector.(column) in for coefficient = 0 to n - 1 do accumulator.(coefficient) <- add_mod accumulator.(coefficient) product.(coefficient) done done; accumulator) let poly_product_ntt challenge vector = Array.map (fun polynomial -> pointwise challenge polynomial) vector let pack_codes ~bits codes = let output = Bytes.make (Array.length codes * bits / 8) '\000' in let accumulator = ref 0 in let available = ref 0 in let position = ref 0 in Array.iter (fun value -> accumulator := !accumulator lor (value lsl !available); available := !available + bits; while !available >= 8 do set_u8 output !position (!accumulator land 0xff); incr position; accumulator := !accumulator lsr 8; available := !available - 8 done) codes; Bytes.unsafe_to_string output let unpack_codes ~bits input = let count = String.length input * 8 / bits in let mask = (1 lsl bits) - 1 in let output = Array.make count 0 in let accumulator = ref 0 in let available = ref 0 in let position = ref 0 in for index = 0 to count - 1 do while !available < bits do accumulator := !accumulator lor (get_u8 input !position lsl !available); incr position; available := !available + 8 done; output.(index) <- !accumulator land mask; accumulator := !accumulator lsr bits; available := !available - bits done; output let pack_eta polynomial = pack_codes ~bits:eta_bits (Array.map (fun value -> P.eta - value) polynomial) let unpack_eta input = let codes = unpack_codes ~bits:eta_bits input in if Array.exists (fun value -> value > 2 * P.eta) codes then Error (Invalid_encoding "ML-DSA signing key has a non-canonical eta encoding") else Ok (Array.map (fun value -> P.eta - value) codes) let pack_t0 polynomial = pack_codes ~bits:13 (Array.map (fun value -> (1 lsl 12) - value) polynomial) let unpack_t0 input = Array.map (fun value -> (1 lsl 12) - value) (unpack_codes ~bits:13 input) let pack_t1 polynomial = pack_codes ~bits:10 polynomial let unpack_t1 input = unpack_codes ~bits:10 input let pack_z polynomial = pack_codes ~bits:z_bits (Array.map (fun value -> P.gamma1 - value) polynomial) let unpack_z input = Array.map (fun value -> P.gamma1 - value) (unpack_codes ~bits:z_bits input) let pack_w1 polynomial = pack_codes ~bits:w1_bits polynomial let rec uniform_polynomial seed nonce output_length = let stream = Mldsa_keccak.shake128 ~output_length (seed ^ u16_le nonce) in let coefficients = Array.make n 0 in let count = ref 0 in let position = ref 0 in while !count < n && !position + 2 < String.length stream do let value = (get_u8 stream !position) lor (get_u8 stream (!position + 1) lsl 8) lor ((get_u8 stream (!position + 2) land 0x7f) lsl 16) in position := !position + 3; if value < q then begin coefficients.(!count) <- value; incr count end done; if !count = n then coefficients else uniform_polynomial seed nonce (2 * output_length) let rec eta_polynomial seed nonce output_length = let stream = Mldsa_keccak.shake256 ~output_length (seed ^ u16_le nonce) in let coefficients = Array.make n 0 in let count = ref 0 in let accept value = if !count < n then if P.eta = 2 then begin if value < 15 then begin let reduced = value - (((205 * value) lsr 10) * 5) in coefficients.(!count) <- 2 - reduced; incr count end end else if value < 9 then begin coefficients.(!count) <- 4 - value; incr count end in for position = 0 to String.length stream - 1 do let byte = get_u8 stream position in accept (byte land 0x0f); accept (byte lsr 4) done; if !count = n then coefficients else eta_polynomial seed nonce (2 * output_length) let mask_polynomial seed nonce = let stream = Mldsa_keccak.shake256 ~output_length:z_packed_bytes (seed ^ u16_le nonce) in unpack_z stream let rec challenge_polynomial seed output_length = let stream = Mldsa_keccak.shake256 ~output_length seed in let signs = ref 0L in for index = 0 to 7 do signs := Int64.logor !signs (Int64.shift_left (Int64.of_int (get_u8 stream index)) (8 * index)) done; let position = ref 8 in let coefficients = Array.make n 0 in let complete = ref true in for index = n - P.tau to n - 1 do if !complete then begin let selected = ref (-1) in while !selected < 0 && !complete do if !position >= String.length stream then complete := false else begin let candidate = get_u8 stream !position in incr position; if candidate <= index then selected := candidate end done; if !complete then begin coefficients.(index) <- coefficients.(!selected); coefficients.(!selected) <- if Int64.logand !signs 1L = 0L then 1 else -1; signs := Int64.shift_right_logical !signs 1 end end done; if !complete then coefficients else challenge_polynomial seed (2 * output_length) let expand_matrix rho = Array.init P.k (fun row -> Array.init P.l (fun column -> uniform_polynomial rho ((row lsl 8) + column) 840)) let expand_secret seed = let s1 = Array.init P.l (fun index -> eta_polynomial seed index 272) in let s2 = Array.init P.k (fun index -> eta_polynomial seed (P.l + index) 272) in s1, s2 let power2round value = let value = norm value in let high = (value + (1 lsl (d - 1)) - 1) lsr d in high, value - (high lsl d) let decompose value = let value = norm value in let alpha = 2 * P.gamma2 in let modulus = (q - 1) / alpha in let high = (value + (alpha / 2) - 1) / alpha in if high = modulus then 0, value - q else high, value - (high * alpha) (* FIPS 204 Algorithm 40 moves the high bits only for a hint of 1. Decoded hints are always 0 or 1, but test for 1 rather than for nonzero. *) let use_hint value hint = let high, low = decompose value in if hint <> 1 then high else let modulus = (q - 1) / (2 * P.gamma2) in if low > 0 then (high + 1) mod modulus else (high + modulus - 1) mod modulus let make_hint low high = if low > P.gamma2 || low < -P.gamma2 || (low = -P.gamma2 && high <> 0) then 1 else 0 (* Scan complete vectors before deciding whether to reject a signing candidate. This does not make ML-DSA signing constant-time -- the FIPS rejection loop still has a variable number of attempts -- but avoids exposing the position of the first coefficient that exceeds a bound. *) let norm_violation_flag polynomial bound = let violation = ref 0 in for index = 0 to Array.length polynomial - 1 do let exceeds = if abs (center polynomial.(index)) >= bound then 1 else 0 in violation := !violation lor exceeds done; !violation let vector_norm_violation_flag vector bound = let violation = ref 0 in for index = 0 to Array.length vector - 1 do violation := !violation lor norm_violation_flag vector.(index) bound done; !violation let encode_verification_key rho t1 = rho ^ String.concat "" (Array.to_list (Array.map pack_t1 t1)) let decode_verification_key octets = if String.length octets <> verification_key_size then invalid_length "ML-DSA verification key" verification_key_size octets else let rho = sub octets 0 seed_size in let t1 = Array.init P.k (fun index -> unpack_t1 (sub octets (seed_size + (index * t1_packed_bytes)) t1_packed_bytes)) in Ok (rho, t1) let encode_expanded_signing_key expanded = String.concat "" [ expanded.rho; expanded.key; expanded.tr; String.concat "" (Array.to_list (Array.map pack_eta expanded.s1)); String.concat "" (Array.to_list (Array.map pack_eta expanded.s2)); String.concat "" (Array.to_list (Array.map pack_t0 expanded.t0)) ] let decode_expanded_signing_key octets = if String.length octets <> signing_key_size then invalid_length "ML-DSA signing key" signing_key_size octets else let rho = sub octets 0 seed_size in let key = sub octets seed_size seed_size in let tr = sub octets (2 * seed_size) tr_bytes in let position = ref ((2 * seed_size) + tr_bytes) in let decode_eta_vector length = let output = Array.make length [||] in let error = ref None in for index = 0 to length - 1 do let encoded = sub octets !position eta_packed_bytes in position := !position + eta_packed_bytes; match unpack_eta encoded with | Ok polynomial -> output.(index) <- polynomial | Error value -> error := Some value done; match !error with None -> Ok output | Some value -> Error value in match decode_eta_vector P.l with | Error _ as error -> error | Ok s1 -> begin match decode_eta_vector P.k with | Error _ as error -> error | Ok s2 -> let t0 = Array.init P.k (fun index -> let encoded = sub octets !position t0_packed_bytes in position := !position + t0_packed_bytes; let _ = index in unpack_t0 encoded) in Ok { rho; key; tr; s1; s2; t0 } end let compute_public_parts expanded = let matrix = expand_matrix expanded.rho in let s1_ntt = Array.map ntt expanded.s1 in let product = matrix_vector_ntt matrix s1_ntt in let t = Array.mapi (fun row polynomial -> let polynomial = inverse_ntt polynomial in Array.init n (fun index -> add_mod polynomial.(index) expanded.s2.(row).(index))) product in let t1 = Array.make P.k [||] in let t0 = Array.make P.k [||] in Array.iteri (fun row polynomial -> let high = Array.make n 0 in let low = Array.make n 0 in for index = 0 to n - 1 do let high_value, low_value = power2round polynomial.(index) in high.(index) <- high_value; low.(index) <- low_value done; t1.(row) <- high; t0.(row) <- low) t; t1, t0 let equal_string left right = if String.length left <> String.length right then false else let difference = ref 0 in for index = 0 to String.length left - 1 do difference := !difference lor (get_u8 left index lxor get_u8 right index) done; !difference = 0 let equal_polyvec left right = Array.length left = Array.length right && let difference = ref 0 in for row = 0 to Array.length left - 1 do for index = 0 to n - 1 do difference := !difference lor (left.(row).(index) lxor right.(row).(index)) done done; !difference = 0 let build_signing_key ?seed expanded = let t1, expected_t0 = compute_public_parts expanded in let verification_key = encode_verification_key expanded.rho t1 in let expected_tr = Mldsa_keccak.shake256 ~output_length:tr_bytes verification_key in if not (equal_string expanded.tr expected_tr) then Error (Invalid_encoding "ML-DSA signing key has an inconsistent public-key hash") else if not (equal_polyvec expanded.t0 expected_t0) then Error (Invalid_encoding "ML-DSA signing key has an inconsistent t0 vector") else Ok { seed; expanded_octets = encode_expanded_signing_key expanded; expanded; verification_key } let keypair_from_seed seed = let expanded_seed = Mldsa_keccak.shake256 ~output_length:128 (seed ^ String.make 1 (Char.unsafe_chr P.k) ^ String.make 1 (Char.unsafe_chr P.l)) in let rho = sub expanded_seed 0 seed_size in let rho_prime = sub expanded_seed seed_size 64 in let key = sub expanded_seed 96 seed_size in let s1, s2 = expand_secret rho_prime in let provisional = { rho; key; tr = String.make tr_bytes '\000'; s1; s2; t0 = Array.make P.k [||] } in let t1, t0 = compute_public_parts provisional in let verification_key = encode_verification_key rho t1 in let tr = Mldsa_keccak.shake256 ~output_length:tr_bytes verification_key in let expanded = { provisional with tr; t0 } in { seed = Some seed; expanded_octets = encode_expanded_signing_key expanded; expanded; verification_key } let signing_key_of_seed seed = if String.length seed <> seed_size then invalid_length "ML-DSA seed" seed_size seed else Ok (keypair_from_seed seed) let signing_key_to_seed key = Option.map (fun seed -> String.sub seed 0 seed_size) key.seed let signing_key_of_octets octets = match decode_expanded_signing_key octets with | Error _ as error -> error | Ok expanded -> build_signing_key expanded let signing_key_to_octets key = String.sub key.expanded_octets 0 signing_key_size let verification_key_of_signing_key key = key.verification_key let verification_key_of_octets octets = match decode_verification_key octets with | Error _ as error -> error | Ok _ -> Ok (String.sub octets 0 verification_key_size) let verification_key_to_octets key = String.sub key 0 verification_key_size type decoded_signature = { c_tilde : string; z : polyvec; hint : polyvec; } let decode_signature octets = if String.length octets <> signature_size then invalid_length "ML-DSA signature" signature_size octets else let c_tilde = sub octets 0 P.c_tilde_bytes in let z_offset = P.c_tilde_bytes in let z = Array.init P.l (fun index -> unpack_z (sub octets (z_offset + (index * z_packed_bytes)) z_packed_bytes)) in let hint_offset = z_offset + (P.l * z_packed_bytes) in let hint = Array.init P.k (fun _ -> Array.make n 0) in let previous = ref 0 in let valid = ref true in for row = 0 to P.k - 1 do let endpoint = get_u8 octets (hint_offset + P.omega + row) in if endpoint < !previous || endpoint > P.omega then valid := false; if !valid then begin for index = !previous to endpoint - 1 do let coefficient = get_u8 octets (hint_offset + index) in if index > !previous && coefficient <= get_u8 octets (hint_offset + index - 1) then valid := false else hint.(row).(coefficient) <- 1 done; previous := endpoint end done; for index = !previous to P.omega - 1 do if get_u8 octets (hint_offset + index) <> 0 then valid := false done; if not !valid then Error (Invalid_encoding "ML-DSA signature has a non-canonical hint encoding") else Ok { c_tilde; z; hint } let signature_of_octets octets = match decode_signature octets with | Error _ as error -> error | Ok _ -> Ok (String.sub octets 0 signature_size) let signature_to_octets signature = String.sub signature 0 signature_size let encode_signature c_tilde z hint = let output = Bytes.make signature_size '\000' in Bytes.blit_string c_tilde 0 output 0 P.c_tilde_bytes; let position = ref P.c_tilde_bytes in Array.iter (fun polynomial -> let encoded = pack_z polynomial in Bytes.blit_string encoded 0 output !position z_packed_bytes; position := !position + z_packed_bytes) z; let hint_offset = !position in let count = ref 0 in for row = 0 to P.k - 1 do for coefficient = 0 to n - 1 do if hint.(row).(coefficient) <> 0 then begin (* [set_u8] does not check bounds, and position [omega] starts the per-row counts. The signer never gets here with more than omega hints; fail loudly rather than write past the hint area. *) if !count = P.omega then invalid_arg (P.name ^ ": a signature holds at most omega hints"); set_u8 output (hint_offset + !count) coefficient; incr count end done; set_u8 output (hint_offset + P.omega + row) !count done; Bytes.unsafe_to_string output let formatted_prefix context = String.make 1 '\000' ^ String.make 1 (Char.unsafe_chr (String.length context)) ^ context let sign_mu_with_randomness key ~mu randomness = if String.length mu <> 64 then invalid_length "ML-DSA message representative" 64 mu else if String.length randomness <> 32 then invalid_length "ML-DSA signing randomness" 32 randomness else let expanded = key.expanded in let rho_prime = Mldsa_keccak.shake256 ~output_length:64 (expanded.key ^ randomness ^ mu) in let matrix = expand_matrix expanded.rho in let s1_ntt = Array.map ntt expanded.s1 in let s2_ntt = Array.map ntt expanded.s2 in let t0_ntt = Array.map ntt expanded.t0 in let rec attempt iteration = if iteration >= 821 then Error Signing_failed else let kappa = iteration * P.l in let y = Array.init P.l (fun row -> mask_polynomial rho_prime (kappa + row)) in let w_ntt = matrix_vector_ntt matrix (Array.map ntt y) in let w = Array.map inverse_ntt w_ntt in let w1 = Array.make P.k [||] in let w0 = Array.make P.k [||] in for row = 0 to P.k - 1 do let high = Array.make n 0 in let low = Array.make n 0 in for index = 0 to n - 1 do let high_value, low_value = decompose w.(row).(index) in high.(index) <- high_value; low.(index) <- low_value done; w1.(row) <- high; w0.(row) <- low done; let encoded_w1 = String.concat "" (Array.to_list (Array.map pack_w1 w1)) in let c_tilde = Mldsa_keccak.shake256 ~output_length:P.c_tilde_bytes (mu ^ encoded_w1) in let challenge_ntt = ntt (challenge_polynomial c_tilde 136) in let cs1 = Array.map (fun polynomial -> Array.map center (inverse_ntt polynomial)) (poly_product_ntt challenge_ntt s1_ntt) in let z = Array.mapi (fun row polynomial -> Array.init n (fun index -> polynomial.(index) + cs1.(row).(index))) y in let rejection = ref (vector_norm_violation_flag z (P.gamma1 - beta)) in let cs2 = Array.map (fun polynomial -> Array.map center (inverse_ntt polynomial)) (poly_product_ntt challenge_ntt s2_ntt) in let r0 = Array.mapi (fun row polynomial -> Array.init n (fun index -> center (polynomial.(index) - cs2.(row).(index)))) w0 in rejection := !rejection lor vector_norm_violation_flag r0 (P.gamma2 - beta); let ct0 = Array.map (fun polynomial -> Array.map center (inverse_ntt polynomial)) (poly_product_ntt challenge_ntt t0_ntt) in rejection := !rejection lor vector_norm_violation_flag ct0 P.gamma2; let hints = Array.init P.k (fun _ -> Array.make n 0) in let hint_count = ref 0 in for row = 0 to P.k - 1 do for index = 0 to n - 1 do let low = center (r0.(row).(index) + ct0.(row).(index)) in let hint = make_hint low w1.(row).(index) in hints.(row).(index) <- hint; hint_count := !hint_count + hint done done; let too_many_hints = if !hint_count > P.omega then 1 else 0 in rejection := !rejection lor too_many_hints; if !rejection <> 0 then attempt (iteration + 1) else Ok (encode_signature c_tilde z hints) in attempt 0 let sign_formatted_with_randomness key ~formatted_message randomness = let mu = Mldsa_keccak.shake256 ~output_length:64 (key.expanded.tr ^ formatted_message) in sign_mu_with_randomness key ~mu randomness let sign_with_randomness ?(context = "") key ~message randomness = if String.length context > 255 then Error (Context_too_long (String.length context)) else sign_formatted_with_randomness key ~formatted_message:(formatted_prefix context ^ message) randomness let sign_internal_for_testing key ~formatted_message ~randomness = sign_formatted_with_randomness key ~formatted_message randomness let sign_mu_for_testing key ~mu ~randomness = sign_mu_with_randomness key ~mu randomness let require_random operation random length = let value = random length in if String.length value <> length then invalid_arg (Format.sprintf "%s.%s: randomness callback returned %d bytes, expected %d" P.name operation (String.length value) length); value let generate ~random () = let seed = require_random "generate" random seed_size in let signing_key = keypair_from_seed seed in signing_key, signing_key.verification_key let sign ?(context = "") ~random key ~message = if String.length context > 255 then Error (Context_too_long (String.length context)) else let randomness = require_random "sign" random 32 in sign_with_randomness ~context key ~message randomness let sign_deterministic ?context key ~message = sign_with_randomness ?context key ~message (String.make 32 '\000') let verify_mu verification_key ~mu signature = if String.length mu <> 64 then false else match decode_verification_key verification_key, decode_signature signature with | Ok (rho, t1), Ok decoded -> if vector_norm_violation_flag decoded.z (P.gamma1 - beta) <> 0 then false else let matrix = expand_matrix rho in let az = matrix_vector_ntt matrix (Array.map ntt decoded.z) in let challenge_ntt = ntt (challenge_polynomial decoded.c_tilde 136) in let shifted_t1 = Array.map (fun polynomial -> Array.map (fun value -> value lsl d) polynomial) t1 in let ct1 = poly_product_ntt challenge_ntt (Array.map ntt shifted_t1) in let w_approx = Array.mapi (fun row polynomial -> inverse_ntt (Array.init n (fun index -> sub_mod polynomial.(index) ct1.(row).(index)))) az in let reconstructed_w1 = Array.mapi (fun row polynomial -> Array.init n (fun index -> use_hint polynomial.(index) decoded.hint.(row).(index))) w_approx in let encoded_w1 = String.concat "" (Array.to_list (Array.map pack_w1 reconstructed_w1)) in let expected = Mldsa_keccak.shake256 ~output_length:P.c_tilde_bytes (mu ^ encoded_w1) in equal_string decoded.c_tilde expected | Error _, _ | _, Error _ -> false let verify_formatted verification_key ~formatted_message signature = let tr = Mldsa_keccak.shake256 ~output_length:tr_bytes verification_key in let mu = Mldsa_keccak.shake256 ~output_length:64 (tr ^ formatted_message) in verify_mu verification_key ~mu signature let verify ?(context = "") verification_key ~message signature = if String.length context > 255 then false else verify_formatted verification_key ~formatted_message:(formatted_prefix context ^ message) signature let verify_internal_for_testing verification_key ~formatted_message signature = verify_formatted verification_key ~formatted_message signature let verify_mu_for_testing verification_key ~mu signature = verify_mu verification_key ~mu signature let use_hint_for_testing = use_hint let encode_signature_for_testing ~c_tilde ~z ~hint = encode_signature c_tilde z hint end module Mldsa44 = Make (struct let name = "Mldsa44" let k = 4 let l = 4 let eta = 2 let tau = 39 let gamma1 = 1 lsl 17 let gamma2 = (8_380_417 - 1) / 88 let omega = 80 let c_tilde_bytes = 32 end) module Mldsa65 = Make (struct let name = "Mldsa65" let k = 6 let l = 5 let eta = 4 let tau = 49 let gamma1 = 1 lsl 19 let gamma2 = (8_380_417 - 1) / 32 let omega = 55 let c_tilde_bytes = 48 end) module Mldsa87 = Make (struct let name = "Mldsa87" let k = 8 let l = 7 let eta = 2 let tau = 60 let gamma1 = 1 lsl 19 let gamma2 = (8_380_417 - 1) / 32 let omega = 75 let c_tilde_bytes = 64 end)
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>