Skip to content

XSalsa20: make hsalsa20 a public API function#728

Open
karel-m wants to merge 2 commits intodevelopfrom
pr/xsalsa20-hsalsa20
Open

XSalsa20: make hsalsa20 a public API function#728
karel-m wants to merge 2 commits intodevelopfrom
pr/xsalsa20-hsalsa20

Conversation

@karel-m
Copy link
Copy Markdown
Member

@karel-m karel-m commented Apr 16, 2026

While checking the interoperability of libtomcrypt with NaCl/libsodium seal box, crypto box and secret box, I ended up with this patch.

HSalsa20 was a missing piece needed to implement NaCl's crypto_box (= authenticated public-key encryption using X25519 + HSalsa20 + XSalsa20-Poly1305).

Technically HSalsa20 was already implemented inside xsalsa20_setup(..) - this PR simply exposes it as xsalsa20_hsalsa20(..) as part of the public API.

NaCl sealed box (crypto_box_seal) needed unkeyed BLAKE2b. However, blake2bmac_init() rejected (NULL, 0) even though blake2b_init() supports it. This PR allows NULL key when keylen is 0 (in line with the BLAKE2 specification).

Checklist

  • documentation is added or updated
  • tests are added or updated

@karel-m
Copy link
Copy Markdown
Member Author

karel-m commented Apr 16, 2026

If anybody is interested in how interoperability between libtomcrypt and libsodium works, here it is:

/* Interoperability test between libtomcrypt and libsodium for:
   1. secretbox  (XSalsa20-Poly1305)
   2. cryptobox  (X25519 + HSalsa20 + XSalsa20-Poly1305)
   3. sealbox    (ephemeral X25519 + BLAKE2b nonce + cryptobox)
*/

#include <stdio.h>
#include <string.h>
#include <tomcrypt.h>
#include <sodium.h>

#define CHECK(e) do { if ((e) != 0) { fprintf(stderr, "FAIL line %d\n", __LINE__); return 1; } } while(0)

static int ltc_secretbox_create(unsigned char *out, const unsigned char *msg, unsigned long msglen, const unsigned char nonce[24], const unsigned char key[32])
{
   salsa20_state st;
   poly1305_state poly;
   unsigned char polykey[32];
   unsigned long taglen = 16;
   int err;

   if ((err = xsalsa20_setup(&st, key, 32, nonce, 24, 20)) != CRYPT_OK) return err;
   if ((err = salsa20_keystream(&st, polykey, 32))         != CRYPT_OK) { salsa20_done(&st); return err; }
   if ((err = salsa20_crypt(&st, msg, msglen, out + 16))   != CRYPT_OK) { salsa20_done(&st); return err; }
   salsa20_done(&st);
   if ((err = poly1305_init(&poly, polykey, 32))           != CRYPT_OK) return err;
   if ((err = poly1305_process(&poly, out + 16, msglen))   != CRYPT_OK) return err;
   if ((err = poly1305_done(&poly, out, &taglen))          != CRYPT_OK) return err;
   return CRYPT_OK;
}

static int ltc_secretbox_open(unsigned char *msg, const unsigned char *box, unsigned long boxlen, const unsigned char nonce[24], const unsigned char key[32])
{
   salsa20_state st;
   poly1305_state poly;
   unsigned char polykey[32], tag[16];
   unsigned long taglen = 16, msglen;
   int err;

   if (boxlen < 16) return CRYPT_ERROR;
   msglen = boxlen - 16;
   if ((err = xsalsa20_setup(&st, key, 32, nonce, 24, 20)) != CRYPT_OK) return err;
   if ((err = salsa20_keystream(&st, polykey, 32))         != CRYPT_OK) { salsa20_done(&st); return err; }
   if ((err = poly1305_init(&poly, polykey, 32))           != CRYPT_OK) { salsa20_done(&st); return err; }
   if ((err = poly1305_process(&poly, box + 16, msglen))   != CRYPT_OK) { salsa20_done(&st); return err; }
   if ((err = poly1305_done(&poly, tag, &taglen))          != CRYPT_OK) { salsa20_done(&st); return err; }
   if (memcmp(tag, box, 16) != 0) { salsa20_done(&st); return CRYPT_ERROR; }
   if ((err = salsa20_crypt(&st, box + 16, msglen, msg)) != CRYPT_OK) { salsa20_done(&st); return err; }
   salsa20_done(&st);
   return CRYPT_OK;
}

static int ltc_cryptobox_create(unsigned char *out, const unsigned char *msg, unsigned long msglen, const unsigned char nonce[24], const unsigned char recipient_pk[32], const unsigned char sender_sk[32])
{
   curve25519_key sk, pk;
   unsigned char shared[32], symkey[32];
   static const unsigned char zero16[16] = {0};
   unsigned long shared_len = 32;
   int err;

   if ((err = x25519_import_raw(sender_sk, 32, PK_PRIVATE, &sk))     != CRYPT_OK) return err;
   if ((err = x25519_import_raw(recipient_pk, 32, PK_PUBLIC, &pk))    != CRYPT_OK) return err;
   if ((err = x25519_shared_secret(&sk, &pk, shared, &shared_len))   != CRYPT_OK) return err;
   if ((err = xsalsa20_hsalsa20(symkey, 32, shared, 32, zero16, 16, 20)) != CRYPT_OK) return err;
   return ltc_secretbox_create(out, msg, msglen, nonce, symkey);
}

static int ltc_cryptobox_open(unsigned char *msg, const unsigned char *box, unsigned long boxlen, const unsigned char nonce[24], const unsigned char sender_pk[32], const unsigned char recipient_sk[32])
{
   curve25519_key sk, pk;
   unsigned char shared[32], symkey[32];
   static const unsigned char zero16[16] = {0};
   unsigned long shared_len = 32;
   int err;

   if ((err = x25519_import_raw(recipient_sk, 32, PK_PRIVATE, &sk))  != CRYPT_OK) return err;
   if ((err = x25519_import_raw(sender_pk, 32, PK_PUBLIC, &pk))      != CRYPT_OK) return err;
   if ((err = x25519_shared_secret(&sk, &pk, shared, &shared_len))   != CRYPT_OK) return err;
   if ((err = xsalsa20_hsalsa20(symkey, 32, shared, 32, zero16, 16, 20)) != CRYPT_OK) return err;
   return ltc_secretbox_open(msg, box, boxlen, nonce, symkey);
}

static void ltc_seal_nonce(unsigned char nonce[24], const unsigned char eph_pk[32], const unsigned char recipient_pk[32])
{
   blake2bmac_state bst;
   unsigned long noncelen = 24;

   blake2bmac_init(&bst, 24, NULL, 0);
   blake2bmac_process(&bst, eph_pk, 32);
   blake2bmac_process(&bst, recipient_pk, 32);
   blake2bmac_done(&bst, nonce, &noncelen);
}

static int ltc_sealbox_create(unsigned char *out, const unsigned char *msg, unsigned long msglen, const unsigned char recipient_pk[32])
{
   curve25519_key eph;
   prng_state prng;
   unsigned char nonce[24];
   int prng_idx, err;

   prng_idx = find_prng("sprng");
   if (prng_idx == -1) return CRYPT_ERROR;
   if ((err = x25519_make_key(&prng, prng_idx, &eph)) != CRYPT_OK) return err;
   memcpy(out, eph.pub, 32);
   ltc_seal_nonce(nonce, eph.pub, recipient_pk);
   return ltc_cryptobox_create(out + 32, msg, msglen, nonce, recipient_pk, eph.priv);
}

static int ltc_sealbox_open(unsigned char *msg, const unsigned char *box, unsigned long boxlen, const unsigned char recipient_pk[32], const unsigned char recipient_sk[32])
{
   unsigned char nonce[24];

   if (boxlen < 48) return CRYPT_ERROR;
   ltc_seal_nonce(nonce, box, recipient_pk);
   return ltc_cryptobox_open(msg, box + 32, boxlen - 32, nonce, box, recipient_sk);
}

static int test_secretbox(void)
{
   const unsigned char key[32] = {
      0x1b,0x27,0x55,0x64,0x73,0xe9,0x85,0xd4,0x62,0xcd,0x51,0x19,0x7a,0x9a,0x46,0xc7,
      0x60,0x09,0x54,0x9e,0xac,0x64,0x74,0xf2,0x06,0xc4,0xee,0x08,0x44,0xf6,0x83,0x89
   };
   const unsigned char nonce[24] = {
      0x69,0x69,0x6e,0xe9,0x55,0xb6,0x2b,0x73,0xcd,0x62,0xbd,0xa8,0x75,0xfc,0x73,0xd6,
      0x82,0x19,0xe0,0x03,0x6b,0x7a,0x0b,0x37
   };
   const char *msg = "Hello from the interop test!";
   unsigned long msglen = strlen(msg);
   unsigned char sbox[256], lbox[256], dec[256];

   printf("  sodium create -> ltc open ... ");
   CHECK(crypto_secretbox_easy(sbox, (const unsigned char *)msg, msglen, nonce, key));
   CHECK(ltc_secretbox_open(dec, sbox, msglen + 16, nonce, key));
   CHECK(memcmp(dec, msg, msglen));
   printf("OK\n");
   printf("  ltc create -> sodium open ... ");
   CHECK(ltc_secretbox_create(lbox, (const unsigned char *)msg, msglen, nonce, key));
   CHECK(crypto_secretbox_open_easy(dec, lbox, msglen + 16, nonce, key));
   CHECK(memcmp(dec, msg, msglen));
   printf("OK\n");
   printf("  identical ciphertext ... ");
   CHECK(memcmp(sbox, lbox, msglen + 16));
   printf("OK\n");
   return 0;
}

static int test_cryptobox(void)
{
   unsigned char alice_pk[32], alice_sk[32];
   unsigned char bob_pk[32], bob_sk[32];
   const unsigned char nonce[24] = {
      0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,
      0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18
   };
   const char *msg = "Testing crypto_box interop between libtomcrypt and libsodium.";
   unsigned long msglen = strlen(msg);
   unsigned char sbox[256], lbox[256], dec[256];

   crypto_box_keypair(alice_pk, alice_sk);
   crypto_box_keypair(bob_pk, bob_sk);
   printf("  sodium create -> ltc open ... ");
   CHECK(crypto_box_easy(sbox, (const unsigned char *)msg, msglen, nonce, bob_pk, alice_sk));
   CHECK(ltc_cryptobox_open(dec, sbox, msglen + 16, nonce, alice_pk, bob_sk));
   CHECK(memcmp(dec, msg, msglen));
   printf("OK\n");
   printf("  ltc create -> sodium open ... ");
   CHECK(ltc_cryptobox_create(lbox, (const unsigned char *)msg, msglen, nonce, bob_pk, alice_sk));
   CHECK(crypto_box_open_easy(dec, lbox, msglen + 16, nonce, alice_pk, bob_sk));
   CHECK(memcmp(dec, msg, msglen));
   printf("OK\n");
   printf("  identical ciphertext ... ");
   CHECK(memcmp(sbox, lbox, msglen + 16));
   printf("OK\n");
   return 0;
}

static int test_sealbox(void)
{
   unsigned char pk[32], sk[32];
   const char *msg = "Sealed box: anonymous public-key authenticated encryption!";
   unsigned long msglen = strlen(msg);
   unsigned long sealedlen = msglen + 48;   /* 32 eph_pk + 16 tag */
   unsigned char sealed[256], dec[256];

   crypto_box_keypair(pk, sk);
   printf("  sodium create -> ltc open ... ");
   CHECK(crypto_box_seal(sealed, (const unsigned char *)msg, msglen, pk));
   CHECK(ltc_sealbox_open(dec, sealed, sealedlen, pk, sk));
   CHECK(memcmp(dec, msg, msglen));
   printf("OK\n");
   printf("  ltc create -> sodium open ... ");
   CHECK(ltc_sealbox_create(sealed, (const unsigned char *)msg, msglen, pk));
   CHECK(crypto_box_seal_open(dec, sealed, sealedlen, pk, sk));
   CHECK(memcmp(dec, msg, msglen));
   printf("OK\n");
   return 0;
}

int main(void)
{
   if (sodium_init() < 0) {
      fprintf(stderr, "sodium_init() failed\n");
      return 1;
   }
   if (register_prng(&sprng_desc) == -1) {
      fprintf(stderr, "register_prng(sprng) failed\n");
      return 1;
   }
   printf("Test 1: secretbox (XSalsa20-Poly1305)\n");
   if (test_secretbox() != 0) return 1;
   printf("Test 2: cryptobox (X25519 + HSalsa20 + secretbox)\n");
   if (test_cryptobox() != 0) return 1;
   printf("Test 3: sealbox (ephemeral X25519 + BLAKE2b + cryptobox)\n");
   if (test_sealbox() != 0) return 1;
   printf("All tests passed.\n");
   return 0;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant