javascriptcryptographypublic-keylibsodiumnacl-cryptography

Encryption using libsodium and need to generate public and private keys using crypto_box_keypair


I have been working with the libsodium library to implement Shamir secret sharing and trying to test the implementation done by dark crystal

https://gitlab.com/dark-crystal-javascript/key-backup-crypto/-/blob/master/example.js

Implementation is something like this

function encryptionKeypair () {
    const keypair = {
      publicKey: sodium.sodium_malloc(sodium.crypto_box_PUBLICKEYBYTES),
      secretKey: sodium.sodium_malloc(sodium.crypto_box_SECRETKEYBYTES)
    }
    sodium.crypto_box_keypair(keypair.publicKey, keypair.secretKey)
    return keypair
  },

 function oneWayBox (message, publicKey) {
    console.log('in one way box');
    const curvePublicKey = sodium.sodium_malloc(sodium.crypto_box_PUBLICKEYBYTES)
    // console.log('curvePublicKey', curvePublicKey.toString('hex'));
    console.log('curvePublicKey', curvePublicKey.length);
    console.log('publicKey', publicKey.length);
    
    sodium.crypto_sign_ed25519_pk_to_curve25519(curvePublicKey, publicKey)

    // console.log('curvePublicKey', curvePublicKey.toString('hex'));


    console.log('in one way box');
    console.log('\n');

    const ephemeral = this.encryptionKeypair()
    const nonce = this.randomBytes(sodium.crypto_box_NONCEBYTES)
    const cipherText = sodium.sodium_malloc(message.length + sodium.crypto_box_MACBYTES)
  
    sodium.crypto_box_easy(cipherText, message, nonce, curvePublicKey, ephemeral.secretKey)
    zero(ephemeral.secretKey)
    zero(message)
    return Buffer.concat([nonce, ephemeral.publicKey, cipherText])
  },

below is Secret-sharing-generation.js

const secrets = require('secret-sharing')
const s = require('.')

const secret = 'My secret key'
const label = ''

console.log('Secret to share:', secret.toString('hex'))

console.log(`Packing with label: '${label}'`)
const packedSecret = s.packLabel(secret, label)
console.log(`Packed secret: ${packedSecret.toString('hex')}`)
console.log(`Length of packed secret is ${packedSecret.length} bytes.`)
const signingKeypair = s.keypair()
const encryptionKeypair = s.signingKeypairToEncryptionKeypair(signingKeypair)

const custodians = []
for (let i = 0; i < 5; i++) {
  custodians.push(s.encryptionKeypair())
}


console.log('custodians', custodians);

console.log('Creating 5 shares, 3 needed to recover')
secrets.share(packedSecret, 5, 3).then((shards) => {
  console.log('Shards:')
  console.log(shards.map(s => s.toString('hex')))
  console.log('Signed shards:')
  const signedShards = s.signShards(shards, signingKeypair)
  console.log(signedShards.map(s => s.toString('hex')))

  const boxedShards = signedShards.map((shard, i) => {
    return s.oneWayBox(shard, custodians[i].publicKey)
  })

  console.log('Boxed shards:')
  console.log(boxedShards.map(s => s.toString('hex')))
  console.log(`Length of boxed shards are ${boxedShards[0].length} bytes.`)
  secrets.combine(shards.slice(2)).then((result) => {
    console.log('Result of recombining 3 shares:', result.toString())
  })
})

Now the problem is when I am using encryptionKeypair function to generate key pair for and then after that when I am trying to generate to do crypto_sign_ed25519_sk_to_curve25519 using the key pair generated in this encryptionKeypair function I am getting

UnhandledPromiseRejectionWarning: Error: ENOMEM, Cannot allocate memory

I have checked my swap space it is completely free

           total        used        free      shared  buff/cache   available
Mem:           3138          83        2896           0         158        2908
Swap:          5119           0        5119

I am not able to understand what is the issue.


Solution

  • It's not clear to me why you want to convert a key pair created with encryptionKeypair() with crypto_sign_ed25519_sk_to_curve25519() or crypto_sign_ed25519_pk_to_curve25519().

    The latter two methods convert a secret or public Ed25519 key (used in the context of signing) to a secret or public X25519 key (used in the context of key exchange).

    encryptionKeypair() applies crypto_box_keypair() and thus already creates an X25519 key pair, so conversion is not necessary (and not possible).

    A working use of the conversion methods would be, e.g. using crypto_sign_keypair(), which generates an Ed25519 key pair:

    var sodium = require('sodium-native');
    ...
    var ed25519KeyPair = signingKeypair() // Create an Ed25519 keypair
    var x25519KeyPair = {
        publicKey: sodium.sodium_malloc(sodium.crypto_box_PUBLICKEYBYTES),
        secretKey: sodium.sodium_malloc(sodium.crypto_box_SECRETKEYBYTES)
    }
    sodium.crypto_sign_ed25519_pk_to_curve25519(x25519KeyPair.publicKey, ed25519KeyPair.publicKey) // Convert the public Ed25519 into a public X25519 key
    sodium.crypto_sign_ed25519_sk_to_curve25519(x25519KeyPair.secretKey, ed25519KeyPair.secretKey) // Convert the secret Ed25519 into a secret X25519 key
    console.log(x25519KeyPair.publicKey)
    console.log(x25519KeyPair.secretKey)
    
    function signingKeypair () {
        const keypair = {
            publicKey: sodium.sodium_malloc(sodium.crypto_sign_PUBLICKEYBYTES),
            secretKey: sodium.sodium_malloc(sodium.crypto_sign_SECRETKEYBYTES)
        }
        sodium.crypto_sign_keypair(keypair.publicKey, keypair.secretKey)
        return keypair
    }
    

    Also, I can't reproduce the posted error message. When using encryptionKeypair() instead of signingKeypair(), I get the following error message Error: public key conversion failed.

    Edit:

    In the 2nd code snippet, the custodians' key pairs are created with s.encryptionKeypair(), which produces X25519 key pairs. In the later called s.oneWayBox() it is then tried to convert the public keys with crypto_sign_ed25519_pk_to_curve25519(), which must fail as described above.

    Presumably this is a bug! A possible fix is to generate the custodians' key pairs with s.keypair() (or signingKeypair()), which creates Ed25519 key pairs. The public keys can then be successfully converted to X25519 keys in s.oneWayBox(). With this change, oneWayBox() runs without any errors on my machine.

    This change to Ed25519 key pairs is also consistent with the description of encryptionKeypair(), which states that this method is only used when generating ephemeral keys, e.g. in oneWayBox(). In all other cases it is internally derived from Ed25519 keys.