| 1 | // Copyright (c) 2025 blackshirt. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | // |
| 5 | // This file contains an experimental port of a Rust reference implementation of nonce-misuse |
| 6 | // resistant and key-committing authenticated encryption scheme called ChaCha20-Poly1305-PSIV, |
| 7 | // It backed by `chacha20` stream cipher and `poly1305` message authentication code module. |
| 8 | // Its originally described by Michiel Verbauwhede and the teams on his papers. |
| 9 | // See the detail on the [A Robust Variant of ChaCha20-Poly1305](https://eprint.iacr.org/2025/222). |
| 10 | module chacha20poly1305 |
| 11 | |
| 12 | import crypto.cipher |
| 13 | import encoding.binary |
| 14 | import crypto.internal.subtle |
| 15 | import x.crypto.chacha20 |
| 16 | import x.crypto.poly1305 |
| 17 | |
| 18 | // new_psiv creates a new Chacha20Poly1305RE with PSIV construct to operate on. |
| 19 | @[direct_array_access] |
| 20 | pub fn new_psiv(key []u8) !&Chacha20Poly1305RE { |
| 21 | if key.len != key_size { |
| 22 | return error('new_psiv: bad key size') |
| 23 | } |
| 24 | // derives and initializes the new key for later purposes |
| 25 | pol_key := fk_k(key) |
| 26 | mac_key := fm_k(key) |
| 27 | enc_key := fe_k(key) |
| 28 | |
| 29 | mut s := chacha20.State{} |
| 30 | mut x64 := [64]u8{} |
| 31 | unsafe { vmemcpy(x64, pol_key[0], 36) } |
| 32 | unpack_into_state(mut s, x64) |
| 33 | ws := chacha20_core(s) |
| 34 | |
| 35 | // For poly1305 mac, we only take a first 32-bytes of the state as a key |
| 36 | mut poly1305_key := []u8{len: 32} |
| 37 | pack32_from_state(mut poly1305_key, ws) |
| 38 | po := poly1305.new(poly1305_key)! |
| 39 | |
| 40 | // set the values |
| 41 | c := &Chacha20Poly1305RE{ |
| 42 | mac_key: mac_key |
| 43 | enc_key: enc_key |
| 44 | po: po |
| 45 | } |
| 46 | return c |
| 47 | } |
| 48 | |
| 49 | // psiv_encrypt encrypts plaintext with provided key, nonce and additional data ad. |
| 50 | // It returns a ciphertext plus message authentication code (mac) contained |
| 51 | // within the end of ciphertext |
| 52 | @[direct_array_access] |
| 53 | pub fn psiv_encrypt(plaintext []u8, key []u8, nonce []u8, ad []u8) ![]u8 { |
| 54 | mut c := new_psiv(key)! |
| 55 | out := c.encrypt(plaintext, nonce, ad)! |
| 56 | unsafe { c.free() } |
| 57 | return out |
| 58 | } |
| 59 | |
| 60 | // psiv_decrypt decrypts the ciphertext with provided key, nonce and additional data in ad. |
| 61 | // It also tries to validate message authentication code within ciphertext compared with |
| 62 | // calculated tag. It returns successfully decrypted message or error on fails. |
| 63 | @[direct_array_access] |
| 64 | pub fn psiv_decrypt(ciphertext []u8, key []u8, nonce []u8, ad []u8) ![]u8 { |
| 65 | mut c := new_psiv(key)! |
| 66 | out := c.decrypt(ciphertext, nonce, ad)! |
| 67 | unsafe { c.free() } |
| 68 | return out |
| 69 | } |
| 70 | |
| 71 | // Chacha20Poly1305RE is a Chacha20Poly1305 opaque with nonce-misuse resistent |
| 72 | // and key-commiting AEAD scheme with PSIV construct. |
| 73 | @[noinit] |
| 74 | pub struct Chacha20Poly1305RE implements cipher.AEAD { |
| 75 | mut: |
| 76 | // flag that marked this instance should not be used again, set on .free call |
| 77 | done bool |
| 78 | // underlying derived keys, set on instance creation with new_psiv. |
| 79 | mac_key [36]u8 |
| 80 | enc_key [36]u8 |
| 81 | po &poly1305.Poly1305 = unsafe { nil } |
| 82 | } |
| 83 | |
| 84 | // free releases resources taken by c. Dont use c after `.free` call. |
| 85 | @[unsafe] |
| 86 | pub fn (mut c Chacha20Poly1305RE) free() { |
| 87 | // if it already marked as done, just return |
| 88 | if c.done { |
| 89 | return |
| 90 | } |
| 91 | unsafe { |
| 92 | // we reset derived keys |
| 93 | vmemset(c.mac_key, 0, c.mac_key.len) |
| 94 | vmemset(c.enc_key, 0, c.enc_key.len) |
| 95 | c.po = nil |
| 96 | } |
| 97 | // mark this instance as done, no longer usable |
| 98 | c.done = true |
| 99 | } |
| 100 | |
| 101 | // nonce_size return the size of the nonce of underlying c. |
| 102 | // Currently, it only support for standard 12-bytes nonce. |
| 103 | pub fn (c &Chacha20Poly1305RE) nonce_size() int { |
| 104 | return nonce_size |
| 105 | } |
| 106 | |
| 107 | // overhead returns difference between the lengths of a plaintext and its ciphertext. |
| 108 | // Its normally returns a tag size produced by this scheme. |
| 109 | pub fn (c &Chacha20Poly1305RE) overhead() int { |
| 110 | return tag_size |
| 111 | } |
| 112 | |
| 113 | // encrypt encrypts and authenticates the provided plaintext along with a nonce, and |
| 114 | // to be authenticated additional data in `ad`. It returns a ciphertext with message authenticated |
| 115 | // code stored within the end of ciphertext. |
| 116 | @[direct_array_access] |
| 117 | pub fn (c Chacha20Poly1305RE) encrypt(plaintext []u8, nonce []u8, ad []u8) ![]u8 { |
| 118 | if c.done { |
| 119 | return error('Chacha20Poly1305RE.encrypt: instance marked as done, no longer usable') |
| 120 | } |
| 121 | if nonce.len != nonce_size { |
| 122 | return error('Chacha20Poly1305RE.encrypt: bad nonce length, only support 12-bytes nonce') |
| 123 | } |
| 124 | |
| 125 | // clone the initial poly1305 state and updates it with additional data ad |
| 126 | mut po_ad := c.po.clone() |
| 127 | update_with_padding(mut po_ad, ad) |
| 128 | // make a clone of updated poly1305 |
| 129 | mut po_ad_clone := po_ad.clone() |
| 130 | |
| 131 | // setup output buffer |
| 132 | mut out := []u8{len: plaintext.len + tag_size} |
| 133 | // write out an authentication tag into the last tag_size bytes of output |
| 134 | psiv_gen_tag(mut out[plaintext.len..], mut po_ad_clone, plaintext, ad.len, c.mac_key, nonce) |
| 135 | // write out authenticated encrypted plaintext into the first plaintext.len bytes of output |
| 136 | psiv_encrypt_internal(mut out[0..plaintext.len], plaintext, c.enc_key, out[plaintext.len..], |
| 137 | nonce)! |
| 138 | |
| 139 | // return the result |
| 140 | return out |
| 141 | } |
| 142 | |
| 143 | // decrypt decrypts the ciphertext with provided key, nonce and additional data in ad. |
| 144 | // It also tries to validate message authenticated code within ciphertext compared with |
| 145 | // calculated tag. It returns successfully decrypted message or error on fails. |
| 146 | @[direct_array_access] |
| 147 | pub fn (c Chacha20Poly1305RE) decrypt(ciphertext []u8, nonce []u8, ad []u8) ![]u8 { |
| 148 | if c.done { |
| 149 | return error('Chacha20Poly1305RE.decrypt: instance marked as done, no longer usable') |
| 150 | } |
| 151 | if ciphertext.len < tag_size { |
| 152 | return error('Chacha20Poly1305RE.decrypt: insufficient ciphertext length') |
| 153 | } |
| 154 | if nonce.len != nonce_size { |
| 155 | return error('Chacha20Poly1305RE.decrypt: invalid nonce length provided') |
| 156 | } |
| 157 | enc := ciphertext[0..ciphertext.len - c.overhead()] |
| 158 | tag := ciphertext[ciphertext.len - c.overhead()..] |
| 159 | |
| 160 | // updates a clone of poly1305 with additional data |
| 161 | mut po_with_ad := c.po.clone() |
| 162 | update_with_padding(mut po_with_ad, ad) |
| 163 | mut poad_clone := po_with_ad.clone() |
| 164 | |
| 165 | // generates authenticated encrypted plaintext with associated mac |
| 166 | mut out := []u8{len: enc.len} |
| 167 | psiv_encrypt_internal(mut out, enc, c.enc_key, tag, nonce)! |
| 168 | |
| 169 | mut mac := []u8{len: tag_size} |
| 170 | psiv_gen_tag(mut mac, mut poad_clone, out, ad.len, c.mac_key, nonce) |
| 171 | |
| 172 | // check if authentication tag was matching or error on fails. |
| 173 | if subtle.constant_time_compare(mac, tag) != 1 { |
| 174 | unsafe { |
| 175 | out.free() |
| 176 | mac.free() |
| 177 | } |
| 178 | return error('unmatching tag') |
| 179 | } |
| 180 | // return the decrypted ciphertext |
| 181 | return out |
| 182 | } |
| 183 | |
| 184 | // The AEAD_CHACHA20_POLY1305 PSIV construct helpers |
| 185 | // |
| 186 | |
| 187 | // psiv_encrypt_internal is an internal encryption routine used by the core of psiv construct |
| 188 | // for encrypting (or decrypting) message. |
| 189 | @[direct_array_access] |
| 190 | fn psiv_encrypt_internal(mut dst []u8, plaintext []u8, dkey [36]u8, tag []u8, nonce []u8) ! { |
| 191 | // loads the counter from the first 8-bytes of the tag input |
| 192 | mut ctr := binary.little_endian_u64(tag[0..8]) |
| 193 | |
| 194 | // setup some temporary vars |
| 195 | mut tc := []u8{len: 8} // counter buffer |
| 196 | mut s := chacha20.State{} |
| 197 | mut b64 := [64]u8{} // state buffer |
| 198 | mut tt := merge_drv_key(dkey, nonce, tag[0..8], tag[8..16]) |
| 199 | |
| 200 | mut j := 0 |
| 201 | mut n := 0 |
| 202 | |
| 203 | // process for every bytes on plaintext input |
| 204 | for plaintext[n..].len > 0 { |
| 205 | // how many block of bytes available to process on |
| 206 | want_len := if plaintext[n..].len < 64 { plaintext[n..].len } else { 64 } |
| 207 | // loads current counter |
| 208 | binary.little_endian_put_u64(mut tc, ctr) |
| 209 | |
| 210 | // updates derived keys with current counter, scrambled with chacha20_core and |
| 211 | // puts state into b64 buffer |
| 212 | unsafe { vmemcpy(tt[48], tc.data, tc.len) } |
| 213 | unpack_into_state(mut s, tt) |
| 214 | ws := chacha20_core(s) |
| 215 | pack64_from_state(mut b64, ws) |
| 216 | |
| 217 | // xor every bytes of plaintext with bytes on b64, stores result in dst |
| 218 | for i in 0 .. want_len { |
| 219 | dst[j] = plaintext[j] ^ b64[i] |
| 220 | j++ |
| 221 | } |
| 222 | // updates current counter and returns error on overflow. |
| 223 | ctr += 1 |
| 224 | if ctr == 0 { |
| 225 | return error('counter overflowing') |
| 226 | } |
| 227 | n += want_len |
| 228 | } |
| 229 | // explicitly reset (release) temporary allocated resources and return the result. |
| 230 | unsafe { |
| 231 | tc.free() |
| 232 | s.reset() |
| 233 | vmemset(b64, 0, b64.len) |
| 234 | vmemset(tt, 0, tt.len) |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | // psiv_gen_tag computes a tag from the key, nonce, and Poly1305 tag of the associated data |
| 239 | // and plaintext using the ChaCha20 permutation with the feed-forward, truncating the output. |
| 240 | @[direct_array_access] |
| 241 | fn psiv_gen_tag(mut out []u8, mut po poly1305.Poly1305, input []u8, ad_len int, mac_key [36]u8, nonce []u8) { |
| 242 | // updates poly1305 mac by input message, associated data length and input length. |
| 243 | update_with_padding(mut po, input) |
| 244 | po.update(length_to_block(ad_len, input.len)) |
| 245 | |
| 246 | // produces 16-bytes of mac from current poly1305 state. |
| 247 | po.finish(mut out) |
| 248 | |
| 249 | // The tag was produced from derived key scrambled with chacha20 quarter round routine, |
| 250 | // and then truncating the output into 16-bytes tag. |
| 251 | drv_key := merge_drv_key(mac_key, nonce, out[0..8], out[8..16]) |
| 252 | mut x := chacha20.State{} |
| 253 | unpack_into_state(mut x, drv_key) |
| 254 | ws := chacha20_core(x) |
| 255 | |
| 256 | // truncating state output into tag_sized bytes. As a note, we reuse buffer previously allocated |
| 257 | // to store the result. |
| 258 | pack16_from_state(mut out, ws) |
| 259 | |
| 260 | // explicitly releases (reset) temporary allocated resources |
| 261 | unsafe { |
| 262 | vmemset(drv_key, 0, drv_key.len) |
| 263 | ws.reset() |
| 264 | x.reset() |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | // update_with_padding updates poly1305 mac with data, padding the tail block if necessary. |
| 269 | @[direct_array_access; inline] |
| 270 | fn update_with_padding(mut po poly1305.Poly1305, data []u8) { |
| 271 | po.update(data) |
| 272 | rem := data.len % tag_size |
| 273 | if rem != 0 { |
| 274 | block := []u8{len: tag_size} |
| 275 | po.update(block[..tag_size - rem]) |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | // merge_drv_key merges provided bytes into 64-bytes key |
| 280 | @[direct_array_access; inline] |
| 281 | fn merge_drv_key(dkey [36]u8, nonce []u8, tag_ctr []u8, tag_rest []u8) [64]u8 { |
| 282 | mut x64 := [64]u8{} |
| 283 | |
| 284 | // 0..36 |
| 285 | for i := 0; i < dkey.len; i++ { |
| 286 | x64[i] = dkey[i] |
| 287 | } |
| 288 | // 36..48 |
| 289 | for i := 0; i < nonce.len; i++ { |
| 290 | x64[36 + i] = nonce[i] |
| 291 | } |
| 292 | // 48..56 |
| 293 | for i := 0; i < tag_ctr.len; i++ { |
| 294 | x64[i + 48] = tag_ctr[i] |
| 295 | } |
| 296 | // 56..64 |
| 297 | for i := 0; i < tag_rest.len; i++ { |
| 298 | x64[i + 56] = tag_rest[i] |
| 299 | } |
| 300 | |
| 301 | return x64 |
| 302 | } |
| 303 | |
| 304 | // fk_k maps and transforms 32-bytes of key into 36-bytes of new key used to |
| 305 | // derive a poly1305 construction. |
| 306 | // See the papers doc on the 3.3 Additional Details part, on page 12-13 |
| 307 | @[direct_array_access; inline] |
| 308 | fn fk_k(key []u8) [36]u8 { |
| 309 | // fk(K) = K1 ∥ K2 ∥ K3 ∥ 03 ∥ K5 ∥ K6 ∥ K7 ∥ 0c ∥ K9 ∥ K10 ∥ K11 ∥ 30 |
| 310 | // ∥ K4 ∥ K8 ∥ K12 ∥ c0 ∥ K13 ∥ K14 ∥ · · · ∥ K32 |
| 311 | // with 0-based index |
| 312 | // K0 ∥ K1 ∥ K2 ∥ 03 ∥ K4 ∥ K5 ∥ K6 ∥ 0c ∥ K8 ∥ K9 ∥ K10 ∥ 30 |
| 313 | // ∥ K3 ∥ K7 ∥ K11 ∥ c0 ∥ K12 ∥ K13 ∥ · · · ∥ K31 |
| 314 | mut x := [36]u8{} |
| 315 | // 0 .. 4 |
| 316 | for i := 0; i < 3; i++ { |
| 317 | x[i] = key[i] |
| 318 | } |
| 319 | x[3] = u8(0x03) |
| 320 | |
| 321 | // 4 .. 8 |
| 322 | for i := 4; i < 7; i++ { |
| 323 | x[i] = key[i] |
| 324 | } |
| 325 | x[7] = 0x0c |
| 326 | |
| 327 | // 8 .. 12 |
| 328 | for i := 8; i < 11; i++ { |
| 329 | x[i] = key[i] |
| 330 | } |
| 331 | x[11] = 0x30 |
| 332 | |
| 333 | // 12 .. 16 |
| 334 | x[12] = key[3] |
| 335 | x[13] = key[7] |
| 336 | x[14] = key[11] |
| 337 | x[15] = 0xc0 |
| 338 | |
| 339 | // 16 .. 36 |
| 340 | for i := 16; i < 36; i++ { |
| 341 | x[i] = key[i - 4] |
| 342 | } |
| 343 | |
| 344 | return x |
| 345 | } |
| 346 | |
| 347 | // fm_k maps and transforms 32-bytes of key into 36-bytes of message authentication key. |
| 348 | // It later used for psiv tag generation. |
| 349 | @[direct_array_access; inline] |
| 350 | fn fm_k(key []u8) [36]u8 { |
| 351 | // fm(K) = K1 ∥ K2 ∥ K3 ∥ 05 ∥ K5 ∥ K6 ∥ K7 ∥ 0a ∥ K9 ∥ K10 ∥ K11 ∥ 50 ∥ |
| 352 | // K4 ∥ K8 ∥ K12 ∥ a0 ∥ K13 ∥ K14 ∥ · · · ∥ K32 , |
| 353 | // Or, with 0-based index |
| 354 | // fm(K) = K0 ∥ K1 ∥ K2 ∥ 05 ∥ K4 ∥ K5 ∥ K6 ∥ 0a ∥ K8 ∥ K9 ∥ K10 ∥ 50 ∥ |
| 355 | // K3 ∥ K7 ∥ K11 ∥ a0 ∥ K12 ∥ K13 ∥ · · · ∥ K31 |
| 356 | mut x := [36]u8{} |
| 357 | // 0 .. 4 |
| 358 | for i := 0; i < 3; i++ { |
| 359 | x[i] = key[i] |
| 360 | } |
| 361 | x[3] = u8(0x05) |
| 362 | |
| 363 | // 4 .. 8 |
| 364 | for i := 4; i < 7; i++ { |
| 365 | x[i] = key[i] |
| 366 | } |
| 367 | x[7] = 0x0a |
| 368 | |
| 369 | // 8 .. 12 |
| 370 | for i := 8; i < 11; i++ { |
| 371 | x[i] = key[i] |
| 372 | } |
| 373 | x[11] = 0x50 |
| 374 | |
| 375 | // 12 .. 16 |
| 376 | x[12] = key[3] |
| 377 | x[13] = key[7] |
| 378 | x[14] = key[11] |
| 379 | x[15] = 0xa0 |
| 380 | |
| 381 | // 16 .. 36 |
| 382 | for i := 16; i < 36; i++ { |
| 383 | x[i] = key[i - 4] |
| 384 | } |
| 385 | |
| 386 | return x |
| 387 | } |
| 388 | |
| 389 | // fe_k maps and transforms 32-bytes of key into 36-bytes of new encryption key |
| 390 | @[direct_array_access; inline] |
| 391 | fn fe_k(key []u8) [36]u8 { |
| 392 | // fe(K) = K1 ∥ K2 ∥ K3 ∥ 06 ∥ K5 ∥ K6 ∥ K7 ∥ 09 ∥ K9 ∥ K10 ∥ K11 ∥ 60 ∥ |
| 393 | // K4 ∥ K8 ∥ K12 ∥ 90 ∥ K13 ∥ K14 ∥ · · · ∥ K32 |
| 394 | // Or, with 0-based index |
| 395 | // fe(K) = K0 ∥ K1 ∥ K2 ∥ 06 ∥ K4 ∥ K5 ∥ K6 ∥ 09 ∥ K8 ∥ K9 ∥ K10 ∥ 60 ∥ |
| 396 | // K3 ∥ K7 ∥ K11 ∥ 90 ∥ K12 ∥ K13 ∥ · · · ∥ K31 |
| 397 | mut x := [36]u8{} |
| 398 | // 0 .. 4 |
| 399 | for i := 0; i < 3; i++ { |
| 400 | x[i] = key[i] |
| 401 | } |
| 402 | x[3] = u8(0x06) |
| 403 | |
| 404 | // 4 .. 8 |
| 405 | for i := 4; i < 7; i++ { |
| 406 | x[i] = key[i] |
| 407 | } |
| 408 | x[7] = 0x09 |
| 409 | |
| 410 | // 8 .. 12 |
| 411 | for i := 8; i < 11; i++ { |
| 412 | x[i] = key[i] |
| 413 | } |
| 414 | x[11] = 0x60 |
| 415 | |
| 416 | // 12 .. 16 |
| 417 | x[12] = key[3] |
| 418 | x[13] = key[7] |
| 419 | x[14] = key[11] |
| 420 | x[15] = 0x90 |
| 421 | |
| 422 | // 16 .. 36 |
| 423 | for i := 16; i < 36; i++ { |
| 424 | x[i] = key[i - 4] |
| 425 | } |
| 426 | |
| 427 | return x |
| 428 | } |
| 429 | |
| 430 | // unpack_into_state deserializes (in little-endian form) 64-bytes of data in x into state s. |
| 431 | @[direct_array_access; inline] |
| 432 | fn unpack_into_state(mut s chacha20.State, x [64]u8) { |
| 433 | for i := 0; i < 16; i++ { |
| 434 | s[i] = u32(x[i * 4]) | (u32(x[i * 4 + 1]) << u32(8)) | (u32(x[i * 4 + 2]) << u32(16)) | (u32(x[ |
| 435 | i * 4 + 3]) << u32(24)) |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | // pack64_from_state serializes state s into 64-bytes output in little-endian form. |
| 440 | @[direct_array_access; inline] |
| 441 | fn pack64_from_state(mut out [64]u8, s chacha20.State) { |
| 442 | mut j := 0 |
| 443 | for v in s { |
| 444 | out[j] = u8(v) |
| 445 | out[j + 1] = u8(v >> u32(8)) |
| 446 | out[j + 2] = u8(v >> u32(16)) |
| 447 | out[j + 3] = u8(v >> u32(24)) |
| 448 | j += 4 |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | // pack32_from_state serializes only a half of state s into 32-bytes output in little-endian form. |
| 453 | @[direct_array_access; inline] |
| 454 | fn pack32_from_state(mut out []u8, s chacha20.State) { |
| 455 | mut j := 0 |
| 456 | for i in 0 .. 8 { |
| 457 | out[j] = u8(s[i]) |
| 458 | out[j + 1] = u8(s[i] >> u32(8)) |
| 459 | out[j + 2] = u8(s[i] >> u32(16)) |
| 460 | out[j + 3] = u8(s[i] >> u32(24)) |
| 461 | j += 4 |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | // pack16_from_state serializes the first quartet of state s into 16-bytes output in little-endian form. |
| 466 | @[direct_array_access; inline] |
| 467 | fn pack16_from_state(mut out []u8, s chacha20.State) { |
| 468 | mut j := 0 |
| 469 | for i in 0 .. 4 { |
| 470 | out[j] = u8(s[i]) |
| 471 | out[j + 1] = u8(s[i] >> u32(8)) |
| 472 | out[j + 2] = u8(s[i] >> u32(16)) |
| 473 | out[j + 3] = u8(s[i] >> u32(24)) |
| 474 | j += 4 |
| 475 | } |
| 476 | } |
| 477 | |
| 478 | // chacha20_core performs chacha20 quarter round on the state s. |
| 479 | // It returns a copy of updated state after quarter round. |
| 480 | @[direct_array_access; inline] |
| 481 | fn chacha20_core(s chacha20.State) chacha20.State { |
| 482 | mut ws := s.clone() |
| 483 | ws.qround(10) |
| 484 | for i := 0; i < 16; i++ { |
| 485 | ws[i] += s[i] |
| 486 | } |
| 487 | return ws |
| 488 | } |
| 489 | |
| 490 | // length_to_block transforms two's length in len1 and len2 into 16-bytes block |
| 491 | @[inline] |
| 492 | fn length_to_block(len1 int, len2 int) []u8 { |
| 493 | mut block := []u8{len: 16} |
| 494 | binary.little_endian_put_u64(mut block[0..8], u64(len1)) |
| 495 | binary.little_endian_put_u64(mut block[8..16], u64(len2)) |
| 496 | |
| 497 | return block |
| 498 | } |
| 499 | |