#!/usr/bin/env node const argv = require('minimist')(process.argv.slice(2)) const sodium = require('sodium-javascript') const assert = require('assert') // Decrypt shares: give one or more secret keys (--sk), // a public key --pk // and shares (as positional arguments) if (argv.sk && argv.pk) { console.log('Attempting to decrypt shares', argv._) if (!Array.isArray(argv.sk)) argv.sk = [argv.sk] let shareSet = '' for (const sk of argv.sk) { const plaintext = attemptDecryptShareSet(argv._, argv.pk, sk) console.log(plaintext ? 'Decrypted share!' : 'Decryption failed' ) shareSet += `${plaintext.toString('hex')} ` } console.log(shareSet) } else { // Otherwise generate some keypairs const numKeypairs = argv.keypairs || 3 console.log(`Generating ${numKeypairs} keypairs`) let pkString = '' let skString = '' for (let i = 0; i < numKeypairs; i++) { const { publicKey, secretKey } = generateKeypair() console.log(` Public key: ${publicKey.toString('hex')}`) console.log(` Secret key: ${secretKey.toString('hex')}\n`) pkString += `--pk ${publicKey.toString('hex')} ` skString += `--sk ${secretKey.toString('hex')} ` } console.log(pkString, '\n') console.log(skString) } /** * Generate a Curve25519 encryption keypair */ function generateKeypair (seed) { seed = seed || generateSeed() const publicKey = Buffer.alloc(sodium.crypto_box_PUBLICKEYBYTES) const secretKey = Buffer.alloc(sodium.crypto_box_SECRETKEYBYTES) sodium.crypto_box_seed_keypair(publicKey, secretKey, seed) return { publicKey, secretKey } function generateSeed () { const seed = Buffer.alloc(sodium.crypto_box_SEEDBYTES) sodium.randombytes_buf(seed) return seed } } /** * Attempt to decrypt a given ciphertext */ function decrypt (ciphertextWithNonce, publicKey, secretKey) { assert(ciphertextWithNonce.length > sodium.crypto_box_MACBYTES + sodium.crypto_box_NONCEBYTES, 'cipherText too short') const nonce = ciphertextWithNonce.slice(0, sodium.crypto_box_NONCEBYTES) const ciphertext = ciphertextWithNonce.slice(sodium.crypto_box_NONCEBYTES) const plain = Buffer.alloc(ciphertext.length - sodium.crypto_box_MACBYTES) const decrypted = sodium.crypto_box_open_easy(plain, ciphertext, nonce, publicKey, secretKey) if (!decrypted) throw new Error('Decryption failed') return plain } /** * Given an array of encrypted shares, return either a successful * decryption or undefined if none could be decrypted */ function attemptDecryptShareSet (shareSet, publicKey, secretKey) { for (const share of shareSet) { try { return decrypt( Buffer.from(share, 'hex'), Buffer.from(publicKey, 'hex'), Buffer.from(secretKey, 'hex') ) } catch (e) { continue } } }