Patch 9.0.1481
Problem:    Decrypting with libsodium may fail if the library changes.
Solution:   Add parameters used to the encrypted file header. (Christian
            Brabandt, closes #12279)
Files:      runtime/doc/editing.txt, runtime/doc/options.txt, src/blowfish.c,
            src/proto/blowfish.pro, src/buffer.c, src/crypt.c,
            src/proto/crypt.pro, src/crypt_zip.c, src/proto/crypt_zip.pro,
            src/fileio.c, src/memline.c, src/option.c, src/optionstr.c,
            src/structs.h, src/testdir/test_crypt.vim


*** ../vim-9.0.1480/runtime/doc/editing.txt     2022-11-01 20:33:39.987400403 
+0000
--- runtime/doc/editing.txt     2023-04-23 17:25:13.904853193 +0100
***************
*** 1533,1541 ****
  
  You can use the 'cryptmethod' option to select the type of encryption, use one
  of these: >
!       :setlocal cm=zip        " weak method, backwards compatible
!       :setlocal cm=blowfish   " method with flaws
!       :setlocal cm=blowfish2  " medium strong method
  
  Do this before writing the file.  When reading an encrypted file it will be
  set automatically to the method used when that file was written.  You can
--- 1533,1542 ----
  
  You can use the 'cryptmethod' option to select the type of encryption, use one
  of these: >
!       :setlocal cm=zip          " weak method, backwards compatible
!       :setlocal cm=blowfish     " method with flaws, do not use
!       :setlocal cm=blowfish2    " medium strong method
!       :setlocal cm=xchacha20v2  " medium strong method using libsodium
  
  Do this before writing the file.  When reading an encrypted file it will be
  set automatically to the method used when that file was written.  You can
*** ../vim-9.0.1480/runtime/doc/options.txt     2023-03-10 16:34:27.568958670 
+0000
--- runtime/doc/options.txt     2023-04-23 17:30:13.368687777 +0100
***************
*** 2482,2493 ****
--- 2511,2524 ----
                                                        *pkzip*
           zip          PkZip compatible method.  A weak kind of encryption.
                        Backwards compatible with Vim 7.2 and older.
+                       Only use if you need to be backwards compatible.
                                                        *blowfish*
           blowfish     Blowfish method.  Medium strong encryption but it has
                        an implementation flaw.  Requires Vim 7.3 or later,
                        files can NOT be read by Vim 7.2 and older.  This adds
                        a "seed" to the file, every time you write the file
                        the encrypted bytes will be different.
+                       Obsolete, please do no longer use.
                                                        *blowfish2*
           blowfish2    Blowfish method.  Medium strong encryption.  Requires
                        Vim 7.4.401 or later, files can NOT be read by Vim 7.3
***************
*** 2509,2519 ****
                        enabled.
                        Encryption of undo files is not yet supported,
                        therefore no undo file will currently be written.
!                       CURRENTLY EXPERIMENTAL: Files written with this method
                        might have to be read back with the same version of
                        Vim if the binary format changes later.
  
!       You should use "blowfish2", also to re-encrypt older files.
  
        When reading an encrypted file 'cryptmethod' will be set automatically
        to the detected method of the file being read.  Thus if you write it
--- 2540,2560 ----
                        enabled.
                        Encryption of undo files is not yet supported,
                        therefore no undo file will currently be written.
!                       CAREFUL: Files written with this method might have to
!                       be read back with the same version of Vim if the
!                       binary format changes later.
!                       Obsolete, please do no longer use.
!          xchacha20v2  Same algorithm as with "xchacha20" that correctly
!                       stores the key derivation parameters together with the
!                       encrypted file.  Should work better in case the
!                       parameters in the libsodium library ever change.
!                       STILL EXPERIMENTAL: Files written with this method
                        might have to be read back with the same version of
                        Vim if the binary format changes later.
  
!       You should use "blowfish2", also to re-encrypt older files.  The
!       "xchacha20" method provides better encryption, but it does not work
!       with all versions of Vim.
  
        When reading an encrypted file 'cryptmethod' will be set automatically
        to the detected method of the file being read.  Thus if you write it
*** ../vim-9.0.1480/src/blowfish.c      2023-01-22 21:14:32.609863616 +0000
--- src/blowfish.c      2023-04-23 17:30:50.608667689 +0100
***************
*** 641,651 ****
      int
  crypt_blowfish_init(
      cryptstate_T      *state,
!     char_u*           key,
!     char_u*           salt,
!     int                       salt_len,
!     char_u*           seed,
!     int                       seed_len)
  {
      bf_state_T        *bfs = ALLOC_CLEAR_ONE(bf_state_T);
  
--- 641,648 ----
      int
  crypt_blowfish_init(
      cryptstate_T      *state,
!     char_u            *key,
!     crypt_arg_T               *arg)
  {
      bf_state_T        *bfs = ALLOC_CLEAR_ONE(bf_state_T);
  
***************
*** 660,667 ****
      if (blowfish_self_test() == FAIL)
        return FAIL;
  
!     bf_key_init(bfs, key, salt, salt_len);
!     bf_cfb_init(bfs, seed, seed_len);
  
      return OK;
  }
--- 657,664 ----
      if (blowfish_self_test() == FAIL)
        return FAIL;
  
!     bf_key_init(bfs, key, arg->cat_salt, arg->cat_salt_len);
!     bf_cfb_init(bfs, arg->cat_seed, arg->cat_seed_len);
  
      return OK;
  }
*** ../vim-9.0.1480/src/proto/blowfish.pro      2022-06-27 23:14:56.000000000 
+0100
--- src/proto/blowfish.pro      2023-04-23 17:44:53.661500073 +0100
***************
*** 1,6 ****
  /* blowfish.c */
  void crypt_blowfish_encode(cryptstate_T *state, char_u *from, size_t len, 
char_u *to, int last);
  void crypt_blowfish_decode(cryptstate_T *state, char_u *from, size_t len, 
char_u *to, int last);
! int crypt_blowfish_init(cryptstate_T *state, char_u *key, char_u *salt, int 
salt_len, char_u *seed, int seed_len);
  int blowfish_self_test(void);
  /* vim: set ft=c : */
--- 1,6 ----
  /* blowfish.c */
  void crypt_blowfish_encode(cryptstate_T *state, char_u *from, size_t len, 
char_u *to, int last);
  void crypt_blowfish_decode(cryptstate_T *state, char_u *from, size_t len, 
char_u *to, int last);
! int crypt_blowfish_init(cryptstate_T *state, char_u *key, crypt_arg_T *arg);
  int blowfish_self_test(void);
  /* vim: set ft=c : */
*** ../vim-9.0.1480/src/buffer.c        2023-04-15 13:17:22.875094544 +0100
--- src/buffer.c        2023-04-23 17:31:35.436643606 +0100
***************
*** 2362,2369 ****
  #endif
  #ifdef FEAT_CRYPT
  # ifdef FEAT_SODIUM
!     if ((buf->b_p_key != NULL) && (*buf->b_p_key != NUL) &&
!                               (crypt_get_method_nr(buf) == CRYPT_M_SOD))
        crypt_sodium_munlock(buf->b_p_key, STRLEN(buf->b_p_key));
  # endif
      clear_string_option(&buf->b_p_key);
--- 2362,2369 ----
  #endif
  #ifdef FEAT_CRYPT
  # ifdef FEAT_SODIUM
!     if (buf->b_p_key != NULL && *buf->b_p_key != NUL
!                          && crypt_method_is_sodium(crypt_get_method_nr(buf)))
        crypt_sodium_munlock(buf->b_p_key, STRLEN(buf->b_p_key));
  # endif
      clear_string_option(&buf->b_p_key);
*** ../vim-9.0.1480/src/crypt.c 2023-04-19 20:28:43.866094432 +0100
--- src/crypt.c 2023-04-23 17:39:02.856631290 +0100
***************
*** 34,39 ****
--- 34,41 ----
      char    *magic;   // magic bytes stored in file header
      int           salt_len;   // length of salt, or 0 when not using salt
      int           seed_len;   // length of seed, or 0 when not using seed
+     int           add_len;    // additional length in the header needed for 
storing
+                       // custom data
  #ifdef CRYPT_NOT_INPLACE
      int           works_inplace; // encryption/decryption can be done in-place
  #endif
***************
*** 44,50 ****
  
      // Function pointer for initializing encryption/decryption.
      int (* init_fn)(cryptstate_T *state, char_u *key,
!                     char_u *salt, int salt_len, char_u *seed, int seed_len);
  
      // Function pointers for encoding/decoding from one buffer into another.
      // Optional, however, these or the _buffer ones should be configured.
--- 46,52 ----
  
      // Function pointer for initializing encryption/decryption.
      int (* init_fn)(cryptstate_T *state, char_u *key,
!               crypt_arg_T *arg);
  
      // Function pointers for encoding/decoding from one buffer into another.
      // Optional, however, these or the _buffer ones should be configured.
***************
*** 73,81 ****
                                                        char_u *p2, int last);
  } cryptmethod_T;
  
! static int crypt_sodium_init_(cryptstate_T *state, char_u *key, char_u *salt, 
int salt_len, char_u *seed, int seed_len);
  static long crypt_sodium_buffer_decode(cryptstate_T *state, char_u *from, 
size_t len, char_u **buf_out, int last);
  static long crypt_sodium_buffer_encode(cryptstate_T *state, char_u *from, 
size_t len, char_u **buf_out, int last);
  
  // index is method_nr of cryptstate_T, CRYPT_M_*
  static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
--- 75,86 ----
                                                        char_u *p2, int last);
  } cryptmethod_T;
  
! static int crypt_sodium_init_(cryptstate_T *state, char_u *key, crypt_arg_T 
*arg);
  static long crypt_sodium_buffer_decode(cryptstate_T *state, char_u *from, 
size_t len, char_u **buf_out, int last);
  static long crypt_sodium_buffer_encode(cryptstate_T *state, char_u *from, 
size_t len, char_u **buf_out, int last);
+ #if defined(FEAT_EVAL) && defined(FEAT_SODIUM)
+ static void crypt_sodium_report_hash_params( unsigned long long opslimit, 
unsigned long long ops_def, size_t memlimit, size_t mem_def, int alg, int 
alg_def);
+ #endif
  
  // index is method_nr of cryptstate_T, CRYPT_M_*
  static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
***************
*** 85,90 ****
--- 90,96 ----
        "VimCrypt~01!",
        0,
        0,
+       0,
  #ifdef CRYPT_NOT_INPLACE
        TRUE,
  #endif
***************
*** 102,107 ****
--- 108,114 ----
        "VimCrypt~02!",
        8,
        8,
+       0,
  #ifdef CRYPT_NOT_INPLACE
        TRUE,
  #endif
***************
*** 119,124 ****
--- 126,132 ----
        "VimCrypt~03!",
        8,
        8,
+       0,
  #ifdef CRYPT_NOT_INPLACE
        TRUE,
  #endif
***************
*** 130,136 ****
        crypt_blowfish_encode, crypt_blowfish_decode,
      },
  
!     // XChaCha20 using libsodium
      {
        "xchacha20",
        "VimCrypt~04!",
--- 138,144 ----
        crypt_blowfish_encode, crypt_blowfish_decode,
      },
  
!     // XChaCha20 using libsodium; implementation issues
      {
        "xchacha20",
        "VimCrypt~04!",
***************
*** 140,145 ****
--- 148,176 ----
        16,
  #endif
        8,
+       0,
+ #ifdef CRYPT_NOT_INPLACE
+       FALSE,
+ #endif
+       FALSE,
+       NULL,
+       crypt_sodium_init_,
+       NULL, NULL,
+       crypt_sodium_buffer_encode, crypt_sodium_buffer_decode,
+       NULL, NULL,
+     },
+     // XChaCha20 using libsodium; stores parameters in header
+     {
+       "xchacha20v2",
+       "VimCrypt~05!",
+ #ifdef FEAT_SODIUM
+       crypto_pwhash_argon2id_SALTBYTES, // 16
+ #else
+       16,
+ #endif
+       8,
+       // sizeof(crypto_pwhash_OPSLIMIT_INTERACTIVE + 
crypto_pwhash_MEMLIMIT_INTERACTIVE + crypto_pwhash_ALG_DEFAULT)
+       20,
  #ifdef CRYPT_NOT_INPLACE
        FALSE,
  #endif
***************
*** 370,375 ****
--- 401,415 ----
  }
  
  /*
+  * Returns True for Sodium Encryption.
+  */
+     int
+ crypt_method_is_sodium(int method)
+ {
+     return method == CRYPT_M_SOD || method == CRYPT_M_SOD2;
+ }
+ 
+ /*
   * Return TRUE when the buffer uses an encryption method that encrypts the
   * whole undo file, not only the text.
   */
***************
*** 387,393 ****
  {
      return CRYPT_MAGIC_LEN
        + cryptmethods[method_nr].salt_len
!       + cryptmethods[method_nr].seed_len;
  }
  
  
--- 427,434 ----
  {
      return CRYPT_MAGIC_LEN
        + cryptmethods[method_nr].salt_len
!       + cryptmethods[method_nr].seed_len
!       + cryptmethods[method_nr].add_len;
  }
  
  
***************
*** 445,454 ****
  crypt_create(
      int               method_nr,
      char_u    *key,
!     char_u    *salt,
!     int               salt_len,
!     char_u    *seed,
!     int               seed_len)
  {
      cryptstate_T *state = ALLOC_ONE(cryptstate_T);
  
--- 486,492 ----
  crypt_create(
      int               method_nr,
      char_u    *key,
!     crypt_arg_T *crypt_arg)
  {
      cryptstate_T *state = ALLOC_ONE(cryptstate_T);
  
***************
*** 456,463 ****
        return state;
  
      state->method_nr = method_nr;
!     if (cryptmethods[method_nr].init_fn(
!       state, key, salt, salt_len, seed, seed_len) == FAIL)
      {
        vim_free(state);
        return NULL;
--- 494,500 ----
        return state;
  
      state->method_nr = method_nr;
!     if (cryptmethods[method_nr].init_fn(state, key, crypt_arg) == FAIL)
      {
        vim_free(state);
        return NULL;
***************
*** 476,492 ****
      char_u    *key,
      char_u    *header)
  {
!     char_u    *salt = NULL;
!     char_u    *seed = NULL;
!     int               salt_len = cryptmethods[method_nr].salt_len;
!     int               seed_len = cryptmethods[method_nr].seed_len;
! 
!     if (salt_len > 0)
!       salt = header + CRYPT_MAGIC_LEN;
!     if (seed_len > 0)
!       seed = header + CRYPT_MAGIC_LEN + salt_len;
  
!     return crypt_create(method_nr, key, salt, salt_len, seed, seed_len);
  }
  
  /*
--- 513,534 ----
      char_u    *key,
      char_u    *header)
  {
!     crypt_arg_T arg;
! 
!     CLEAR_FIELD(arg);
!     arg.cat_init_from_file = TRUE;
  
!     arg.cat_salt_len = cryptmethods[method_nr].salt_len;
!     arg.cat_seed_len = cryptmethods[method_nr].seed_len;
!     arg.cat_add_len = cryptmethods[method_nr].add_len;
!     if (arg.cat_salt_len > 0)
!       arg.cat_salt = header + CRYPT_MAGIC_LEN;
!     if (arg.cat_seed_len > 0)
!       arg.cat_seed = header + CRYPT_MAGIC_LEN + arg.cat_salt_len;
!     if (arg.cat_add_len > 0)
!       arg.cat_add = header + CRYPT_MAGIC_LEN + arg.cat_salt_len + 
arg.cat_seed_len;
! 
!     return crypt_create(method_nr, key, &arg);
  }
  
  /*
***************
*** 540,563 ****
      int           *header_len)
  {
      int           len = crypt_get_header_len(method_nr);
!     char_u  *salt = NULL;
!     char_u  *seed = NULL;
!     int           salt_len = cryptmethods[method_nr].salt_len;
!     int           seed_len = cryptmethods[method_nr].seed_len;
      cryptstate_T *state;
  
      *header_len = len;
      *header = alloc(len);
      if (*header == NULL)
        return NULL;
  
      mch_memmove(*header, cryptmethods[method_nr].magic, CRYPT_MAGIC_LEN);
!     if (salt_len > 0 || seed_len > 0)
      {
!       if (salt_len > 0)
!           salt = *header + CRYPT_MAGIC_LEN;
!       if (seed_len > 0)
!           seed = *header + CRYPT_MAGIC_LEN + salt_len;
  
        // TODO: Should this be crypt method specific? (Probably not worth
        // it).  sha2_seed is pretty bad for large amounts of entropy, so make
--- 582,610 ----
      int           *header_len)
  {
      int           len = crypt_get_header_len(method_nr);
!     crypt_arg_T arg;
      cryptstate_T *state;
  
+     CLEAR_FIELD(arg);
+     arg.cat_salt_len = cryptmethods[method_nr].salt_len;
+     arg.cat_seed_len = cryptmethods[method_nr].seed_len;
+     arg.cat_add_len  = cryptmethods[method_nr].add_len;
+     arg.cat_init_from_file = FALSE;
+ 
      *header_len = len;
      *header = alloc(len);
      if (*header == NULL)
        return NULL;
  
      mch_memmove(*header, cryptmethods[method_nr].magic, CRYPT_MAGIC_LEN);
!     if (arg.cat_salt_len > 0 || arg.cat_seed_len > 0 || arg.cat_add_len > 0)
      {
!       if (arg.cat_salt_len > 0)
!           arg.cat_salt = *header + CRYPT_MAGIC_LEN;
!       if (arg.cat_seed_len > 0)
!           arg.cat_seed = *header + CRYPT_MAGIC_LEN + arg.cat_salt_len;
!       if (arg.cat_add_len > 0)
!           arg.cat_add = *header + CRYPT_MAGIC_LEN + arg.cat_salt_len + 
arg.cat_seed_len;
  
        // TODO: Should this be crypt method specific? (Probably not worth
        // it).  sha2_seed is pretty bad for large amounts of entropy, so make
***************
*** 565,580 ****
  #ifdef FEAT_SODIUM
        if (sodium_init() >= 0)
        {
!           if (salt_len > 0)
!               randombytes_buf(salt, salt_len);
!           if (seed_len > 0)
!               randombytes_buf(seed, seed_len);
        }
        else
  #endif
!           sha2_seed(salt, salt_len, seed, seed_len);
      }
!     state = crypt_create(method_nr, key, salt, salt_len, seed, seed_len);
      if (state == NULL)
        VIM_CLEAR(*header);
      return state;
--- 612,627 ----
  #ifdef FEAT_SODIUM
        if (sodium_init() >= 0)
        {
!           if (arg.cat_salt_len > 0)
!               randombytes_buf(arg.cat_salt, arg.cat_salt_len);
!           if (arg.cat_seed_len > 0)
!               randombytes_buf(arg.cat_seed, arg.cat_seed_len);
        }
        else
  #endif
!           sha2_seed(arg.cat_salt, arg.cat_salt_len, arg.cat_seed, 
arg.cat_seed_len);
      }
!     state = crypt_create(method_nr, key, &arg);
      if (state == NULL)
        VIM_CLEAR(*header);
      return state;
***************
*** 587,593 ****
  crypt_free_state(cryptstate_T *state)
  {
  #ifdef FEAT_SODIUM
!     if (state->method_nr == CRYPT_M_SOD)
      {
        sodium_munlock(((sodium_state_T *)state->method_state)->key,
                                                         crypto_box_SEEDBYTES);
--- 634,640 ----
  crypt_free_state(cryptstate_T *state)
  {
  #ifdef FEAT_SODIUM
!     if (crypt_method_is_sodium(state->method_nr))
      {
        sodium_munlock(((sodium_state_T *)state->method_state)->key,
                                                         crypto_box_SEEDBYTES);
***************
*** 742,748 ****
      void
  crypt_check_method(int method)
  {
!     if (method < CRYPT_M_BF2)
      {
        msg_scroll = TRUE;
        msg(_("Warning: Using a weak encryption method; see :help 'cm'"));
--- 789,795 ----
      void
  crypt_check_method(int method)
  {
!     if (method < CRYPT_M_BF2 || method == CRYPT_M_SOD)
      {
        msg_scroll = TRUE;
        msg(_("Warning: Using a weak encryption method; see :help 'cm'"));
***************
*** 754,760 ****
  crypt_check_swapfile_curbuf(void)
  {
      int method = crypt_get_method_nr(curbuf);
!     if (method == CRYPT_M_SOD)
      {
        // encryption uses padding and MAC, that does not work very well with
        // swap and undo files, so disable them
--- 801,807 ----
  crypt_check_swapfile_curbuf(void)
  {
      int method = crypt_get_method_nr(curbuf);
!     if (crypt_method_is_sodium(method))
      {
        // encryption uses padding and MAC, that does not work very well with
        // swap and undo files, so disable them
***************
*** 827,833 ****
      }
  
      // since the user typed this, no need to wait for return
!     if (crypt_get_method_nr(curbuf) != CRYPT_M_SOD)
      {
        if (msg_didout)
            msg_putchar('\n');
--- 874,880 ----
      }
  
      // since the user typed this, no need to wait for return
!     if (!crypt_method_is_sodium(crypt_get_method_nr(curbuf)))
      {
        if (msg_didout)
            msg_putchar('\n');
***************
*** 861,876 ****
  crypt_sodium_init_(
      cryptstate_T      *state UNUSED,
      char_u            *key UNUSED,
!     char_u            *salt UNUSED,
!     int                       salt_len UNUSED,
!     char_u            *seed UNUSED,
!     int                       seed_len UNUSED)
  {
  # ifdef FEAT_SODIUM
      // crypto_box_SEEDBYTES ==  crypto_secretstream_xchacha20poly1305_KEYBYTES
      unsigned char     dkey[crypto_box_SEEDBYTES]; // 32
      sodium_state_T    *sd_state;
      int                       retval = 0;
  
      if (sodium_init() < 0)
        return FAIL;
--- 908,923 ----
  crypt_sodium_init_(
      cryptstate_T      *state UNUSED,
      char_u            *key UNUSED,
!     crypt_arg_T               *arg UNUSED)
  {
  # ifdef FEAT_SODIUM
      // crypto_box_SEEDBYTES ==  crypto_secretstream_xchacha20poly1305_KEYBYTES
      unsigned char     dkey[crypto_box_SEEDBYTES]; // 32
      sodium_state_T    *sd_state;
      int                       retval = 0;
+     unsigned long long        opslimit;
+     size_t            memlimit;
+     int                       alg;
  
      if (sodium_init() < 0)
        return FAIL;
***************
*** 878,902 ****
      sd_state = (sodium_state_T *)sodium_malloc(sizeof(sodium_state_T));
      sodium_memzero(sd_state, sizeof(sodium_state_T));
  
!     // derive a key from the password
!     if (crypto_pwhash(dkey, sizeof(dkey), (const char *)key, STRLEN(key), 
salt,
!       crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE,
!       crypto_pwhash_ALG_DEFAULT) != 0)
      {
!       // out of memory
!       sodium_free(sd_state);
!       return FAIL;
!     }
!     memcpy(sd_state->key, dkey, crypto_box_SEEDBYTES);
  
!     retval += sodium_mlock(sd_state->key, crypto_box_SEEDBYTES);
!     retval += sodium_mlock(key, STRLEN(key));
  
!     if (retval < 0)
      {
!       emsg(_(e_encryption_sodium_mlock_failed));
!       sodium_free(sd_state);
!       return FAIL;
      }
      sd_state->count = 0;
      state->method_state = sd_state;
--- 925,1022 ----
      sd_state = (sodium_state_T *)sodium_malloc(sizeof(sodium_state_T));
      sodium_memzero(sd_state, sizeof(sodium_state_T));
  
!     if ((state->method_nr == CRYPT_M_SOD2 && !arg->cat_init_from_file)
!           || state->method_nr == CRYPT_M_SOD)
      {
!       opslimit = crypto_pwhash_OPSLIMIT_INTERACTIVE;
!       memlimit = crypto_pwhash_MEMLIMIT_INTERACTIVE;
!       alg = crypto_pwhash_ALG_DEFAULT;
! 
! #if 0
!       // For testing
!       if (state->method_nr == CRYPT_M_SOD2)
!       {
!           opslimit = crypto_pwhash_OPSLIMIT_MODERATE;
!           memlimit = crypto_pwhash_MEMLIMIT_MODERATE;
!       }
! #endif
  
!       // derive a key from the password
!       if (crypto_pwhash(dkey, sizeof(dkey), (const char *)key, STRLEN(key),
!                                 arg->cat_salt, opslimit, memlimit, alg) != 0)
!       {
!           // out of memory
!           sodium_free(sd_state);
!           return FAIL;
!       }
!       memcpy(sd_state->key, dkey, crypto_box_SEEDBYTES);
! 
!       retval += sodium_mlock(sd_state->key, crypto_box_SEEDBYTES);
!       retval += sodium_mlock(key, STRLEN(key));
! 
!       if (retval < 0)
!       {
!           emsg(_(e_encryption_sodium_mlock_failed));
!           sodium_free(sd_state);
!           return FAIL;
!       }
!       if (state->method_nr == CRYPT_M_SOD2)
!       {
!           memcpy(arg->cat_add, &opslimit, sizeof(opslimit));
!           arg->cat_add += sizeof(opslimit);
! 
!           memcpy(arg->cat_add, &memlimit, sizeof(memlimit));
!           arg->cat_add += sizeof(memlimit);
  
!           memcpy(arg->cat_add, &alg, sizeof(alg));
!           arg->cat_add += sizeof(alg);
!       }
!     }
!     else
      {
!       // Reading parameters from file
!       if (arg->cat_add_len
!                   < (int)(sizeof(opslimit) + sizeof(memlimit) + sizeof(alg)))
!       {
!           sodium_free(sd_state);
!           return FAIL;
!       }
! 
!       // derive the key from the file header
!       memcpy(&opslimit, arg->cat_add, sizeof(opslimit));
!       arg->cat_add += sizeof(opslimit);
! 
!       memcpy(&memlimit, arg->cat_add, sizeof(memlimit));
!       arg->cat_add += sizeof(memlimit);
! 
!       memcpy(&alg, arg->cat_add, sizeof(alg));
!       arg->cat_add += sizeof(alg);
! 
! #ifdef FEAT_EVAL
!       crypt_sodium_report_hash_params(opslimit,
!                                           crypto_pwhash_OPSLIMIT_INTERACTIVE,
!               memlimit, crypto_pwhash_MEMLIMIT_INTERACTIVE,
!               alg, crypto_pwhash_ALG_DEFAULT);
! #endif
! 
!       if (crypto_pwhash(dkey, sizeof(dkey), (const char *)key, STRLEN(key),
!                                 arg->cat_salt, opslimit, memlimit, alg) != 0)
!       {
!           // out of memory
!           sodium_free(sd_state);
!           return FAIL;
!       }
!       memcpy(sd_state->key, dkey, crypto_box_SEEDBYTES);
! 
!       retval += sodium_mlock(sd_state->key, crypto_box_SEEDBYTES);
!       retval += sodium_mlock(key, STRLEN(key));
! 
!       if (retval < 0)
!       {
!           emsg(_(e_encryption_sodium_mlock_failed));
!           sodium_free(sd_state);
!           return FAIL;
!       }
      }
      sd_state->count = 0;
      state->method_state = sd_state;
***************
*** 1100,1105 ****
--- 1220,1233 ----
      sodium_state_T *sod_st = state->method_state;
      unsigned char  tag;
      unsigned long long out_len;
+ 
+     if (sod_st->count == 0
+           && state->method_nr == CRYPT_M_SOD
+           && len > WRITEBUFSIZE
+               + crypto_secretstream_xchacha20poly1305_HEADERBYTES
+               + crypto_secretstream_xchacha20poly1305_ABYTES)
+       len -= cryptmethods[CRYPT_M_SOD2].add_len;
+ 
      *buf_out = alloc_clear(len);
      if (*buf_out == NULL)
      {
***************
*** 1158,1163 ****
--- 1286,1321 ----
  {
      return randombytes_random();
  }
+ 
+ #if defined(FEAT_EVAL) || defined(PROTO)
+     static void
+ crypt_sodium_report_hash_params(
+       unsigned long long opslimit,
+       unsigned long long ops_def,
+       size_t memlimit,
+       size_t mem_def,
+       int alg,
+       int alg_def)
+ {
+     if (p_verbose > 0)
+     {
+       verbose_enter();
+       if (opslimit != ops_def)
+           smsg(_("xchacha20v2: using custom opslimit \"%llu\" for Key 
derivation."), opslimit);
+       else
+           smsg(_("xchacha20v2: using default opslimit \"%llu\" for Key 
derivation."), opslimit);
+       if (memlimit != mem_def)
+           smsg(_("xchacha20v2: using custom memlimit \"%lu\" for Key 
derivation."), (unsigned long)memlimit);
+       else
+           smsg(_("xchacha20v2: using default memlimit \"%lu\" for Key 
derivation."), (unsigned long)memlimit);
+       if (alg != alg_def)
+           smsg(_("xchacha20v2: using custom algorithm \"%d\" for Key 
derivation."), alg);
+       else
+           smsg(_("xchacha20v2: using default algorithm \"%d\" for Key 
derivation."), alg);
+       verbose_leave();
+     }
+ }
+ #endif
  # endif
  
  #endif // FEAT_CRYPT
*** ../vim-9.0.1480/src/proto/crypt.pro 2022-12-06 16:16:57.971656206 +0000
--- src/proto/crypt.pro 2023-04-23 17:45:03.077516623 +0100
***************
*** 4,15 ****
  int crypt_method_nr_from_magic(char *ptr, int len);
  int crypt_works_inplace(cryptstate_T *state);
  int crypt_get_method_nr(buf_T *buf);
  int crypt_whole_undofile(int method_nr);
  int crypt_get_header_len(int method_nr);
  int crypt_get_max_header_len(void);
  void crypt_set_cm_option(buf_T *buf, int method_nr);
  int crypt_self_test(void);
! cryptstate_T *crypt_create(int method_nr, char_u *key, char_u *salt, int 
salt_len, char_u *seed, int seed_len);
  cryptstate_T *crypt_create_from_header(int method_nr, char_u *key, char_u 
*header);
  cryptstate_T *crypt_create_from_file(FILE *fp, char_u *key);
  cryptstate_T *crypt_create_for_writing(int method_nr, char_u *key, char_u 
**header, int *header_len);
--- 4,16 ----
  int crypt_method_nr_from_magic(char *ptr, int len);
  int crypt_works_inplace(cryptstate_T *state);
  int crypt_get_method_nr(buf_T *buf);
+ int crypt_method_is_sodium(int method);
  int crypt_whole_undofile(int method_nr);
  int crypt_get_header_len(int method_nr);
  int crypt_get_max_header_len(void);
  void crypt_set_cm_option(buf_T *buf, int method_nr);
  int crypt_self_test(void);
! cryptstate_T *crypt_create(int method_nr, char_u *key, crypt_arg_T 
*crypt_arg);
  cryptstate_T *crypt_create_from_header(int method_nr, char_u *key, char_u 
*header);
  cryptstate_T *crypt_create_from_file(FILE *fp, char_u *key);
  cryptstate_T *crypt_create_for_writing(int method_nr, char_u *key, char_u 
**header, int *header_len);
*** ../vim-9.0.1480/src/crypt_zip.c     2023-01-22 21:14:32.613863616 +0000
--- src/crypt_zip.c     2023-04-23 17:20:11.225031020 +0100
***************
*** 83,92 ****
  crypt_zip_init(
      cryptstate_T    *state,
      char_u        *key,
!     char_u        *salt UNUSED,
!     int                   salt_len UNUSED,
!     char_u        *seed UNUSED,
!     int                   seed_len UNUSED)
  {
      char_u    *p;
      zip_state_T       *zs;
--- 83,89 ----
  crypt_zip_init(
      cryptstate_T    *state,
      char_u        *key,
!     crypt_arg_T     *arg UNUSED)
  {
      char_u    *p;
      zip_state_T       *zs;
*** ../vim-9.0.1480/src/proto/crypt_zip.pro     2022-06-27 23:15:00.000000000 
+0100
--- src/proto/crypt_zip.pro     2023-04-23 17:45:07.805524826 +0100
***************
*** 1,5 ****
  /* crypt_zip.c */
! int crypt_zip_init(cryptstate_T *state, char_u *key, char_u *salt, int 
salt_len, char_u *seed, int seed_len);
  void crypt_zip_encode(cryptstate_T *state, char_u *from, size_t len, char_u 
*to, int last);
  void crypt_zip_decode(cryptstate_T *state, char_u *from, size_t len, char_u 
*to, int last);
  /* vim: set ft=c : */
--- 1,5 ----
  /* crypt_zip.c */
! int crypt_zip_init(cryptstate_T *state, char_u *key, crypt_arg_T *arg);
  void crypt_zip_encode(cryptstate_T *state, char_u *from, size_t len, char_u 
*to, int last);
  void crypt_zip_decode(cryptstate_T *state, char_u *from, size_t len, char_u 
*to, int last);
  /* vim: set ft=c : */
*** ../vim-9.0.1480/src/fileio.c        2023-03-07 17:45:07.184247900 +0000
--- src/fileio.c        2023-04-23 17:41:08.081006884 +0100
***************
*** 218,223 ****
--- 218,226 ----
      int               using_b_fname;
      static char *msg_is_a_directory = N_("is a directory");
      int               eof;
+ #ifdef FEAT_SODIUM
+     int               may_need_lseek = FALSE;
+ #endif
  
      au_did_filetype = FALSE; // reset before triggering any autocommands
  
***************
*** 1282,1296 ****
                     */
  # ifdef FEAT_SODIUM
                    // Let the crypt layer work with a buffer size of 8192
                    if (filesize == 0)
                        // set size to 8K + Sodium Crypt Metadata
                        size = WRITEBUFSIZE + crypt_get_max_header_len()
                     + crypto_secretstream_xchacha20poly1305_HEADERBYTES
                     + crypto_secretstream_xchacha20poly1305_ABYTES;
  
!                   else if (filesize > 0 && (curbuf->b_cryptstate != NULL &&
!                        curbuf->b_cryptstate->method_nr == CRYPT_M_SOD))
                        size = WRITEBUFSIZE + 
crypto_secretstream_xchacha20poly1305_ABYTES;
  # endif
                    eof = size;
                    size = read_eintr(fd, ptr, size);
--- 1285,1327 ----
                     */
  # ifdef FEAT_SODIUM
                    // Let the crypt layer work with a buffer size of 8192
+                   //
+                   // Sodium encryption requires a fixed block size to
+                   // successfully decrypt. However, unfortunately the file
+                   // header size changes between xchacha20 and xchacha20v2 by
+                   // 'add_len' bytes.
+                   // So we will now read the maximum header size + encryption
+                   // metadata, but after determining to read an xchacha20
+                   // encrypted file, we have to rewind the file descriptor by
+                   // 'add_len' bytes in the second round.
+                   //
+                   // Be careful with changing it, it needs to stay the same
+                   // for reading back previously encrypted files!
                    if (filesize == 0)
+                   {
                        // set size to 8K + Sodium Crypt Metadata
                        size = WRITEBUFSIZE + crypt_get_max_header_len()
                     + crypto_secretstream_xchacha20poly1305_HEADERBYTES
                     + crypto_secretstream_xchacha20poly1305_ABYTES;
+                       may_need_lseek = TRUE;
+                   }
  
!                   else if (filesize > 0 && (curbuf->b_cryptstate != NULL
!                               && crypt_method_is_sodium(
!                                            curbuf->b_cryptstate->method_nr)))
!                   {
                        size = WRITEBUFSIZE + 
crypto_secretstream_xchacha20poly1305_ABYTES;
+                       // need to rewind by - add_len from CRYPT_M_SOD2 (see
+                       // description above)
+                       if (curbuf->b_cryptstate->method_nr == CRYPT_M_SOD
+                                                    && !eof && may_need_lseek)
+                       {
+                           lseek(fd, crypt_get_header_len(
+                                              curbuf->b_cryptstate->method_nr)
+                                      - crypt_get_max_header_len(), SEEK_CUR);
+                           may_need_lseek = FALSE;
+                       }
+                   }
  # endif
                    eof = size;
                    size = read_eintr(fd, ptr, size);
*** ../vim-9.0.1480/src/memline.c       2023-04-22 21:14:21.585140551 +0100
--- src/memline.c       2023-04-23 17:43:59.217398741 +0100
***************
*** 436,442 ****
        sha2_seed(buf->b_ml.ml_mfp->mf_seed, MF_SEED_LEN, NULL, 0);
      }
  #ifdef FEAT_SODIUM
!     else if (method_nr == CRYPT_M_SOD)
        crypt_sodium_randombytes_buf(buf->b_ml.ml_mfp->mf_seed,
                MF_SEED_LEN);
  #endif
--- 436,442 ----
        sha2_seed(buf->b_ml.ml_mfp->mf_seed, MF_SEED_LEN, NULL, 0);
      }
  #ifdef FEAT_SODIUM
!     else if (crypt_method_is_sodium(method_nr))
        crypt_sodium_randombytes_buf(buf->b_ml.ml_mfp->mf_seed,
                MF_SEED_LEN);
  #endif
***************
*** 495,501 ****
      old_method = crypt_method_nr_from_name(old_cm);
  
      // Swapfile encryption not supported by XChaCha20
!     if (crypt_get_method_nr(buf) == CRYPT_M_SOD && *buf->b_p_key != NUL)
      {
        // close the swapfile
        mf_close_file(buf, TRUE);
--- 495,501 ----
      old_method = crypt_method_nr_from_name(old_cm);
  
      // Swapfile encryption not supported by XChaCha20
!     if (crypt_method_is_sodium(crypt_get_method_nr(buf)) && *buf->b_p_key != 
NUL)
      {
        // close the swapfile
        mf_close_file(buf, TRUE);
***************
*** 5512,5517 ****
--- 5512,5518 ----
  /*
   * Prepare for encryption/decryption, using the key, seed and offset.
   * Return an allocated cryptstate_T *.
+  * Note: Encryption not supported for SODIUM
   */
      static cryptstate_T *
  ml_crypt_prepare(memfile_T *mfp, off_T offset, int reading)
***************
*** 5520,5540 ****
      char_u    salt[50];
      int               method_nr;
      char_u    *key;
!     char_u    *seed;
  
      if (reading && mfp->mf_old_key != NULL)
      {
        // Reading back blocks with the previous key/method/seed.
        method_nr = mfp->mf_old_cm;
        key = mfp->mf_old_key;
!       seed = mfp->mf_old_seed;
      }
      else
      {
        method_nr = crypt_get_method_nr(buf);
        key = buf->b_p_key;
!       seed = mfp->mf_seed;
      }
      if (*key == NUL)
        return NULL;
  
--- 5521,5543 ----
      char_u    salt[50];
      int               method_nr;
      char_u    *key;
!     crypt_arg_T arg;
  
+     CLEAR_FIELD(arg);
      if (reading && mfp->mf_old_key != NULL)
      {
        // Reading back blocks with the previous key/method/seed.
        method_nr = mfp->mf_old_cm;
        key = mfp->mf_old_key;
!       arg.cat_seed = mfp->mf_old_seed;
      }
      else
      {
        method_nr = crypt_get_method_nr(buf);
        key = buf->b_p_key;
!       arg.cat_seed = mfp->mf_seed;
      }
+ 
      if (*key == NUL)
        return NULL;
  
***************
*** 5543,5556 ****
        // For PKzip: Append the offset to the key, so that we use a different
        // key for every block.
        vim_snprintf((char *)salt, sizeof(salt), "%s%ld", key, (long)offset);
!       return crypt_create(method_nr, salt, NULL, 0, NULL, 0);
      }
  
      // Using blowfish or better: add salt and seed. We use the byte offset
      // of the block for the salt.
      vim_snprintf((char *)salt, sizeof(salt), "%ld", (long)offset);
!     return crypt_create(method_nr, key, salt, (int)STRLEN(salt),
!                                                       seed, MF_SEED_LEN);
  }
  
  #endif
--- 5546,5569 ----
        // For PKzip: Append the offset to the key, so that we use a different
        // key for every block.
        vim_snprintf((char *)salt, sizeof(salt), "%s%ld", key, (long)offset);
!       arg.cat_seed = NULL;
!       arg.cat_init_from_file = FALSE;
! 
!       return crypt_create(method_nr, salt, &arg);
      }
  
      // Using blowfish or better: add salt and seed. We use the byte offset
      // of the block for the salt.
      vim_snprintf((char *)salt, sizeof(salt), "%ld", (long)offset);
! 
!     arg.cat_salt = salt;
!     arg.cat_salt_len = (int)STRLEN(salt);
!     arg.cat_seed_len = MF_SEED_LEN;
!     arg.cat_add_len = 0;
!     arg.cat_add = NULL;
!     arg.cat_init_from_file = FALSE;
! 
!     return crypt_create(method_nr, key, &arg);
  }
  
  #endif
*** ../vim-9.0.1480/src/option.c        2023-03-15 22:05:40.603998755 +0000
--- src/option.c        2023-04-23 17:20:11.229031016 +0100
***************
*** 4274,4280 ****
                && !curbufIsChanged() && curbuf->b_ml.ml_mfp != NULL)
        {
  #ifdef FEAT_CRYPT
!           if (crypt_get_method_nr(curbuf) == CRYPT_M_SOD)
                continue;
  #endif
            u_compute_hash(hash);
--- 4274,4280 ----
                && !curbufIsChanged() && curbuf->b_ml.ml_mfp != NULL)
        {
  #ifdef FEAT_CRYPT
!           if (crypt_method_is_sodium(crypt_get_method_nr(curbuf)))
                continue;
  #endif
            u_compute_hash(hash);
*** ../vim-9.0.1480/src/optionstr.c     2023-03-12 21:20:51.441254187 +0000
--- src/optionstr.c     2023-04-23 17:20:11.229031016 +0100
***************
*** 29,35 ****
  #ifdef FEAT_CRYPT
  static char *(p_cm_values[]) = {"zip", "blowfish", "blowfish2",
   # ifdef FEAT_SODIUM
!     "xchacha20",
   # endif
      NULL};
  #endif
--- 29,35 ----
  #ifdef FEAT_CRYPT
  static char *(p_cm_values[]) = {"zip", "blowfish", "blowfish2",
   # ifdef FEAT_SODIUM
!     "xchacha20", "xchacha20v2",
   # endif
      NULL};
  #endif
*** ../vim-9.0.1480/src/structs.h       2023-04-16 20:53:50.189171575 +0100
--- src/structs.h       2023-04-23 17:20:11.229031016 +0100
***************
*** 2771,2781 ****
  # define CRYPT_M_BF   1
  # define CRYPT_M_BF2  2
  # define CRYPT_M_SOD    3
! # define CRYPT_M_COUNT        4 // number of crypt methods
  
  // Currently all crypt methods work inplace.  If one is added that isn't then
  // define this.
  # define CRYPT_NOT_INPLACE 1
  #endif
  
  #ifdef FEAT_PROP_POPUP
--- 2771,2794 ----
  # define CRYPT_M_BF   1
  # define CRYPT_M_BF2  2
  # define CRYPT_M_SOD    3
! # define CRYPT_M_SOD2   4
! # define CRYPT_M_COUNT        5 // number of crypt methods
  
  // Currently all crypt methods work inplace.  If one is added that isn't then
  // define this.
  # define CRYPT_NOT_INPLACE 1
+ 
+ // Struct for passing arguments down to the crypt_init functions
+ typedef struct {
+     char_u    *cat_salt;
+     int               cat_salt_len;
+     char_u    *cat_seed;
+     int               cat_seed_len;
+     char_u    *cat_add;
+     int               cat_add_len;
+     int               cat_init_from_file;
+ } crypt_arg_T;
+ 
  #endif
  
  #ifdef FEAT_PROP_POPUP
*** ../vim-9.0.1480/src/testdir/test_crypt.vim  2022-11-10 00:25:00.577023938 
+0000
--- src/testdir/test_crypt.vim  2023-04-23 17:20:11.229031016 +0100
***************
*** 81,86 ****
--- 81,91 ----
    call Crypt_uncrypt('xchacha20')
  endfunc
  
+ func Test_crypt_sodium_v2()
+   CheckFeature sodium
+   call Crypt_uncrypt('xchacha20v2')
+ endfunc
+ 
  func Uncrypt_stable(method, crypted_text, key, uncrypted_text)
    split Xtest.txt
    set bin noeol key= fenc=latin1
***************
*** 96,108 ****
    set key=
  endfunc
  
! func Uncrypt_stable_xxd(method, hex, key, uncrypted_text)
    if empty(s:xxd_cmd)
      throw 'Skipped: xxd program missing'
    endif
    " use xxd to write the binary content
    call system(s:xxd_cmd .. ' -r >Xtest.txt', a:hex)
!   call feedkeys(":split Xtest.txt\<CR>" . a:key . "\<CR>", 'xt')
    call assert_equal(a:uncrypted_text, getline(1, len(a:uncrypted_text)))
    bwipe!
    call delete('Xtest.txt')
--- 101,115 ----
    set key=
  endfunc
  
! func Uncrypt_stable_xxd(method, hex, key, uncrypted_text, verbose)
    if empty(s:xxd_cmd)
      throw 'Skipped: xxd program missing'
    endif
    " use xxd to write the binary content
    call system(s:xxd_cmd .. ' -r >Xtest.txt', a:hex)
!   let cmd = (a:verbose ? ':verbose' : '') ..
!         \ ":split Xtest.txt\<CR>" . a:key . "\<CR>"
!   call feedkeys(cmd, 'xt')
    call assert_equal(a:uncrypted_text, getline(1, len(a:uncrypted_text)))
    bwipe!
    call delete('Xtest.txt')
***************
*** 138,144 ****
          \  '00000080: 72be 0136 84a1 d3                        r..6...']
    " the file should be in latin1 encoding, this makes sure that readfile()
    " retries several times converting the multi-byte characters
!   call Uncrypt_stable_xxd('xchacha20', hex, "sodium_crypt", 
["abcdefghijklmnopqrstuvwxyzäöü", "ZZZ_äüöÄÜÖ_!@#$%^&*()_+=-`~"])
  endfunc
  
  func Test_uncrypt_xchacha20_invalid()
--- 145,184 ----
          \  '00000080: 72be 0136 84a1 d3                        r..6...']
    " the file should be in latin1 encoding, this makes sure that readfile()
    " retries several times converting the multi-byte characters
!   call Uncrypt_stable_xxd('xchacha20', hex, "sodium_crypt", 
["abcdefghijklmnopqrstuvwxyzäöü", "ZZZ_äüöÄÜÖ_!@#$%^&*()_+=-`~"], 0)
! endfunc
! 
! func Test_uncrypt_xchacha20v2_custom()
!   CheckFeature sodium
!   " Test, reading xchacha20v2 with custom encryption parameters
!   let hex = ['00000000: 5669 6d43 7279 7074 7e30 3521 934b f288  
VimCrypt~05!.K..',
!         \ '00000010: 10ba 8bc9 25a0 8876 f85c f135 6fb8 518b  
....%..v.\.5o.Q.',
!         \ '00000020: b133 9af1 0300 0000 0000 0000 0000 0010  
.3..............',
!         \ '00000030: 0000 0000 0200 0000 b973 5f33 80e9 54fc  
.........s_3..T.',
!         \ '00000040: 138f ba3e 046b 3135 90b7 7783 5eac 7fe3  
...>.k15..w.^...',
!         \ '00000050: 0cd2 14df ed75 4b65 8763 8205 035c ec81  
.....uKe.c...\..',
!         \ "00000060: a4cf 33d2 7507 ec38 ba62 a327 9068 d8ad  
..3.u..8.b.'.h..",
!         \ '00000070: 2607 3fa6 f95d 7ea8 9799 f997 4820 0c    &.?..]~.....H 
.']
!   call Uncrypt_stable_xxd('xchacha20v2', hex, "foobar", ["", "foo", "bar", 
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], 1)
!   call assert_match('xchacha20v2: using custom \w\+ "\d\+" for Key 
derivation.', execute(':messages'))
! endfunc
! 
! func Test_uncrypt_xchacha20v2()
!   CheckFeature sodium
!   " Test, reading xchacha20v2
!   let hex = [
!         \ '00000000: 5669 6d43 7279 7074 7e30 3521 9f20 4e14  VimCrypt~05!. 
N.',
!         \ '00000010: c7da c1bd 7dea 8fbc db6c 38e6 7a77 6fef  
....}....l8.zwo.',
!         \ '00000020: 82dd 964b 0300 0000 0000 0000 0000 0010  
...K............',
!         \ '00000030: 0000 0000 0200 0000 a97c 2f00 0b9d 19eb  
.........|/.....',
!         \ '00000040: 1d92 1ea5 3f22 c179 4b3e 870a eb19 6380  
....?".yK>....c.',
!         \ '00000050: 63f8 222d b5d1 3c73 7be5 d580 47ea 44cc  
c."-..<s{...G.D.',
!         \ '00000060: 6c25 8078 3fd5 d836 c700 0122 bb30 7a59  
l%.x?..6...".0zY',
!         \ '00000070: b184 2ae8 e7db 113a f732 938f 7a34 1333  
..*....:.2..z4.3',
!         \ '00000080: dc89 1491 51a0 67b9 0f3a b56c 1f9d 53b0  
....Q.g..:.l..S.',
!         \ '00000090: 2416 205a 8c4c 5fde 4dac 2611 8a48 24f0  $. 
Z.L_.M.&..H$.',
!         \ '000000a0: ba00 92c1 60                             ....`']
!   call Uncrypt_stable_xxd('xchacha20v2', hex, "foo1234", 
["abcdefghijklmnopqrstuvwxyzäöü", 'ZZZ_äüöÄÜÖ_!@#$%^&*()_+=-`~"'], 0)
  endfunc
  
  func Test_uncrypt_xchacha20_invalid()
***************
*** 165,171 ****
  
    sp Xcrypt_sodium.txt
    " Create a larger file, so that Vim will write in several blocks
!   call setline(1, range(1,4000))
    call assert_equal(1, &swapfile)
    set cryptmethod=xchacha20
    call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
--- 205,211 ----
  
    sp Xcrypt_sodium.txt
    " Create a larger file, so that Vim will write in several blocks
!   call setline(1, range(1, 4000))
    call assert_equal(1, &swapfile)
    set cryptmethod=xchacha20
    call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
***************
*** 186,223 ****
    bw!
    call delete('Xcrypt_sodium.txt')
    set cryptmethod&vim
  endfunc
  
! func Test_uncrypt_xchacha20_3_persistent_undo()
    CheckFeature sodium
-   CheckFeature persistent_undo
  
!   sp Xcrypt_sodium_undo.txt
!   set cryptmethod=xchacha20 undofile
    call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
!   call assert_equal(1, &undofile)
!   let ufile=undofile(@%)
!   call append(0, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
!   call cursor(1, 1)
! 
!   set undolevels=100
!   normal dd
!   set undolevels=100
!   normal dd
!   set undolevels=100
!   normal dd
!   set undolevels=100
    w!
!   call assert_equal(0, &undofile)
    bw!
!   call feedkeys(":sp Xcrypt_sodium_undo.txt\<CR>sodium\<CR>", 'xt')
!   " should fail
!   norm! u
!   call assert_match('Already at oldest change', execute(':1mess'))
!   call assert_fails('verbose rundo ' .. fnameescape(ufile), 'E822')
    bw!
!   set undolevels& cryptmethod& undofile&
!   call delete('Xcrypt_sodium_undo.txt')
  endfunc
  
  func Test_encrypt_xchacha20_missing()
--- 226,298 ----
    bw!
    call delete('Xcrypt_sodium.txt')
    set cryptmethod&vim
+ 
  endfunc
  
! func Test_uncrypt_xchacha20v2_2()
    CheckFeature sodium
  
!   sp Xcrypt_sodium_v2.txt
!   " Create a larger file, so that Vim will write in several blocks
!   call setline(1, range(1, 4000))
!   call assert_equal(1, &swapfile)
!   set cryptmethod=xchacha20v2
    call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
!   " swapfile disabled
!   call assert_equal(0, &swapfile)
!   call assert_match("Note: Encryption of swapfile not supported, disabling 
swap file", execute(':messages'))
    w!
!   " encrypted using xchacha20
!   call assert_match("\[xchachav2\]", execute(':messages'))
    bw!
!   call feedkeys(":verbose :sp Xcrypt_sodium_v2.txt\<CR>sodium\<CR>", 'xt')
!   " successfully decrypted
!   call assert_equal(range(1, 4000)->map( {_, v -> string(v)}), getline(1,'$'))
!   call assert_match('xchacha20v2: using default \w\+ "\d\+" for Key 
derivation.', execute(':messages'))
!   set key=
!   w! ++ff=unix
!   " encryption removed (on MS-Windows the .* matches [unix])
!   call assert_match('"Xcrypt_sodium_v2.txt".*4000L, 18893B written', 
execute(':message'))
    bw!
!   call delete('Xcrypt_sodium_v2.txt')
!   set cryptmethod&vim
! 
! endfunc
! 
! func Test_uncrypt_xchacha20_3_persistent_undo()
!   CheckFeature sodium
!   CheckFeature persistent_undo
! 
!   for meth in ['xchacha20', 'xchacha20v2']
! 
!     sp Xcrypt_sodium_undo.txt
!     exe "set cryptmethod=" .. meth .. " undofile"
!     call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
!     call assert_equal(1, &undofile)
!     let ufile=undofile(@%)
!     call append(0, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
!     call cursor(1, 1)
! 
!     set undolevels=100
!     normal dd
!     set undolevels=100
!     normal dd
!     set undolevels=100
!     normal dd
!     set undolevels=100
!     w!
!     call assert_equal(0, &undofile)
!     bw!
!     call feedkeys(":sp Xcrypt_sodium_undo.txt\<CR>sodium\<CR>", 'xt')
!     " should fail
!     norm! u
!     call assert_match('Already at oldest change', execute(':1mess'))
!     call assert_fails('verbose rundo ' .. fnameescape(ufile), 'E822')
!     bw!
!     set undolevels& cryptmethod& undofile&
!     call delete('Xcrypt_sodium_undo.txt')
! 
!   endfor
  endfunc
  
  func Test_encrypt_xchacha20_missing()
***************
*** 226,231 ****
--- 301,307 ----
    endif
    sp Xcrypt_sodium_undo.txt
    call assert_fails(':set cryptmethod=xchacha20', 'E474')
+   call assert_fails(':set cryptmethod=xchacha20v2', 'E474')
    bw!
    set cm&
  endfunc
*** ../vim-9.0.1480/src/version.c       2023-04-23 16:23:48.505183683 +0100
--- src/version.c       2023-04-23 17:22:22.136952375 +0100
***************
*** 697,698 ****
--- 697,700 ----
  {   /* Add new patch number below this line */
+ /**/
+     1481,
  /**/

-- 
(letter from Mark to Mike, about the film's probable certificate)
      For an 'A' we would have to: Lose as many shits as possible; Take Jesus
      Christ out, if possible; Loose "I fart in your general direction"; Lose
      "the oral sex"; Lose "oh, fuck off"; Lose "We make castanets out of your
      testicles"
                 "Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD

 /// Bram Moolenaar -- b...@moolenaar.net -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20230423165053.9387D1C078F%40moolenaar.net.

Raspunde prin e-mail lui