├── LICENSE ├── README.md ├── design.md ├── index.js ├── package.json └── test ├── benchmark.js └── index.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dominic Tarr 4 | 5 | Permission is hereby granted, free of charge, 6 | to any person obtaining a copy of this software and 7 | associated documentation files (the "Software"), to 8 | deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, 10 | merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom 12 | the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 22 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # private-box 2 | 3 | A format for encrypting a private message to many parties. 4 | `private-box` is designed according to the [auditdrivencrypto design process](https://github.com/crypto-browserify/crypto-browserify/issues/128) 5 | 6 | ## API 7 | 8 | ### encrypt (plaintext Buffer, recipients Array) 9 | 10 | Takes a `plaintext` Buffer of the message you want to encrypt, 11 | and an array of recipient public keys. 12 | Returns a message that is encrypted to all recipients 13 | and openable by them with `PrivateBox.decrypt`. 14 | The `recipients` must be between 1 and 7 items long. 15 | 16 | The encrypted length will be `56 + (recipients.length * 33) + plaintext.length` bytes long, 17 | between 89 and 287 bytes longer than the plaintext. 18 | 19 | ### decrypt (cyphertext Buffer, secretKey curve25519_sk) 20 | 21 | Attempt to decrypt a private-box message, using your secret key. 22 | If you where an intended recipient then the plaintext will be returned. 23 | If it was not for you, then `undefined` will be returned. 24 | 25 | ## Protocol 26 | 27 | ### Encryption 28 | 29 | `private-box` generates an ephemeral curve25519 keypair that will only be used with this message (`ephemeral`), 30 | and a random key that will be used to encrypt the plaintext body (`body_key`). 31 | First, private-box outputs the ephemeral public key, then multiplies each recipient public key 32 | with its secret to produce ephemeral shared keys (`shared_keys[1..n]`). 33 | Then, private-box concatenates `body_key` with the number of recipients, 34 | encrypts that to each shared key, and concatenates the encrypted body. 35 | 36 | ``` 37 | function encrypt (plaintext, recipients) { 38 | var ephemeral = keypair() 39 | var nonce = random(24) 40 | var body_key = random(32) 41 | var body_key_with_length = concat([body_key, recipients.length]) 42 | return concat([ 43 | nonce, 44 | ephemeral.publicKey, 45 | concat(recipients.map(function (publicKey) { 46 | return secretbox( 47 | body_key_with_length, 48 | nonce, 49 | scalarmult(publicKey, ephemeral.secretKey) 50 | ) 51 | }), 52 | secretbox(plaintext, nonce, body_key) 53 | ]) 54 | } 55 | ``` 56 | 57 | ## Decryption 58 | 59 | `private-box` takes the nonce and ephemeral public key, 60 | multiplies that with your secret key, then tests each possible 61 | recipient slot until it either decrypts a key or runs out of slots. 62 | If it runs out of slots, the message was not addressed to you, 63 | so `undefined` is returned. Else, the message is found and the body 64 | is decrypted. 65 | 66 | ``` js 67 | function decrypt (cyphertext, secretKey) { 68 | var next = reader(cyphertext) // next() will read the passed N bytes 69 | var nonce = next(24) 70 | var publicKey = next(32) 71 | var sharedKey = salarmult(publicKey, secretKey) 72 | 73 | for(var i = 0; i < 7; i++) { 74 | var maybe_key = next(33) 75 | var key_with_length = secretbox_open(maybe_key, nonce, sharedKey) 76 | if (key_with_length) { // decrypted! 77 | var key = key_with_length.slice(0, 32) 78 | var length = key_with_length[32] 79 | return secretbox_open( 80 | key, 81 | cyphertext.slice(56 + 33*(length+1), cyphertext.length), 82 | ) 83 | } 84 | } 85 | // this message was not addressed to the owner of secretKey 86 | return undefined 87 | } 88 | ``` 89 | 90 | ## Assumptions 91 | 92 | Messages will be posted in public, so that the sender is likely to be known, 93 | and everyone can read the messages. (This makes it possible to hide the recipient, 94 | but probably not the sender.) 95 | 96 | Resisting traffic analysis of the timing or size of messages is out of scope of this spec. 97 | 98 | ## Prior Art 99 | 100 | ### PGP 101 | 102 | In PGP the recipient, the sender, and the subject are sent as plaintext. 103 | If the recipient is known, then the metadata graph of who is communicating with who can be read, 104 | which, since it is easier to analyze than the content, is important to protect. 105 | 106 | ### Sodium seal 107 | 108 | The Sodium library provides a _seal_ function that generates an ephemeral keypair, 109 | derives a shared key to encrypt a message, and then sends the ephemeral public key and the message. 110 | The recipient is hidden, and it is forward secure if the sender throws out the ephemeral key. 111 | However, it's only possible to have one recipient. 112 | 113 | ### Minilock 114 | 115 | Minilock uses a similar approach to `private-box` but does not hide the 116 | number of recipients. In the case of a group discussion where multiple rounds 117 | of messages are sent to everyone, this may enable an eavesdropper to deanonymize 118 | the participiants of a discussion if the sender of each message is known. 119 | 120 | ## Properties 121 | 122 | This protocol was designed for use with secure-scuttlebutt. 123 | In this place, messages are placed in public, and the sender is known via a signature, 124 | but we can hide the recipient and the content. 125 | 126 | ### Recipients are hidden. 127 | 128 | An eaves-dropper cannot know the recipients or their number. 129 | Since the message is encrypted to each recipient, and then placed in public, 130 | to receive a message you will have to decrypt every message posted. 131 | This would not be scalable if you had to decrypt every message on the internet, 132 | but if you can restrict the number of messages you might have to decrypt, 133 | then it's reasonable. For example, if you frequented a forum which contained these messages, 134 | then it would only be a reasonable number of messages, and posting a message would only 135 | reveal that you where talking to some other member of that forum. 136 | 137 | Hiding access to such a forum is another problem that's out of the current scope. 138 | 139 | ### The number of recipients are hidden. 140 | 141 | If the number of recipients was not hidden, then sometimes it would be possible 142 | to deanonymise the recipients, if there was a large group discussion with 143 | an unusual number of recipients. Encrypting the number of recipients means that 144 | when you fail to decrypt a message you must attempt to decrypt same number of times 145 | as the maximum recipients. 146 | 147 | ### A valid recipient does not know the other recipients. 148 | 149 | A valid recipient knows the number of recipients but now who they are. 150 | This is more a sideeffect of the design than an intentional design element. 151 | 152 | ### By providing the `key` for a message a outside party could decrypt the message. 153 | 154 | When you tell someone a secret you must trust them not to reveal it. 155 | Anyone who knows the `key` could reveal that to some other party who could then read the message content, 156 | but not the recipients (unless the sender revealed the ephemeral secret key). 157 | 158 | ## License 159 | 160 | MIT 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /design.md: -------------------------------------------------------------------------------- 1 | # private-box 2 | 3 | an unaddressed box, with a private note-to-self so the sender can remember who it was for. 4 | 5 | ``` js 6 | 7 | private_box(msg, nonce, recipient_pk, sender_sk, sender_key) => ciphertext 8 | 9 | //then, the receiver can open it if they know the sender. 10 | 11 | private_unbox(msg, nonce, sender_pk, recipient_sk) => plaintext 12 | 13 | //OR, the sender can decrypt. 14 | 15 | private_unbox2(msg, nonce, sender_sk, sender_key) => plaintext 16 | 17 | ``` 18 | 19 | In secure scuttlebutt, a potential receiver knows who posted 20 | a message, because it has a pk/signature. The envelope is marked 21 | with a _from_ field, but there is no _to_ field. 22 | 23 | However, sometimes the sender needs to look at a message 24 | they sent. If there is no _to_ field, the sender must encrypt 25 | a short message _to themselves_. 26 | 27 | ## generate one time key. 28 | 29 | generate a onetime keypair, box a message to the receipient 30 | with it, and also box a message back to your self, including 31 | the onetime secret, so that you can reopen the message if necessary. 32 | 33 | ```js 34 | //two scalarmult + key_pair 35 | //152 byte overhead 36 | function private_box (msg, nonce, recipient_pk, sender_sk) { 37 | var onetime = box_keypair() 38 | return concat([ 39 | nonce, //24 bytes 40 | onetime.publicKey, //32 bytes 41 | box_easy( //32+32+16 = 80 bytes 42 | concat([recipient_pk, onetime.secretKey]), 43 | onetime.publicKey, 44 | sender_sk 45 | ), 46 | //msg.length + 16 bytes 47 | box_easy(msg, nonce, recipient_pk, onetime.secretKey) 48 | ] 49 | } 50 | ``` 51 | this design generates a new key pair on ever write, 52 | and then uses two scalarmult operations. 53 | there are 152 bytes of overhead. 54 | 55 | One interesting benefit is that you could have a oneway 56 | write, where the author forgets the onetime secret key, 57 | so the box can only be opened by it's recipient. 58 | 59 | ## keep a symmetric key for the note-to-self 60 | 61 | We have to keep track of another secret key 62 | (it could be derived from the private key, though) 63 | 64 | ``` js 65 | 66 | function private_box (msg, nonce, recipient_pk, sender_sk, sender_key) { 67 | return concat([ 68 | nonce, //24 bytes 69 | secretbox_easy(recipient_pk, nonce, sender_key), //32+16=40 bytes 70 | box_easy(msg, nonce, recipient_pk, sender_sk) //msg.length + 16 71 | ]) 72 | } 73 | ``` 74 | 75 | Only 80 bytes overhead (just over half as much) and only one 76 | scalarmult. This will be a more performant encrypt operation, 77 | but decrypt will be only slightly better. 78 | 79 | This construction could be used to store encrypted messages 80 | for yourself, by "sending them" to a onetime key. 81 | 82 | Also, it would mean that `sender_key` is 83 | 84 | ## one way box 85 | 86 | you could have a box that only the recipient can open. 87 | 88 | ``` js 89 | function oneway_box (msg, nonce, recipient_pk) { 90 | var onetime = keypair() 91 | return concat([ 92 | nonce, 93 | onetime.publicKey, 94 | box_easy(msg, nonce, recipient_pk, onetime.secretKey) 95 | ]) 96 | } 97 | ``` 98 | This would have the interesting property that the message 99 | could not be opened by the sender (once they have deleted 100 | `onetime.secretKey`) 101 | 102 | This doesn't seem very useful for a database. 103 | 104 | ## multiple recipients 105 | 106 | maybe, a way to generalize this would be to have multiple 107 | recipients? 108 | 109 | ``` js 110 | 111 | function multibox (msg, nonce, recipients, sender_sk) { 112 | 113 | var key = random(32) 114 | 115 | return concat([ 116 | nonce, //24 bytes 117 | //1 byte 118 | new Buffer([recipients.length & 255]), //MAX 1 byte! 119 | //recipients.length * 16+32 120 | recipients.map(function (r_pk) { 121 | return box(key, nonce, r_pk, sender_sk) 122 | }), 123 | //msg.length + 16 124 | secretbox_easy(msg, nonce, key) 125 | ]) 126 | } 127 | ``` 128 | 129 | So, to use this model, you would normally make the first recipient 130 | your self. This would support messages to N recipients, 131 | and also support one way messages, or messages to yourself. 132 | 133 | To decrypt, you would take `scalarmult(your_sk, sender_pk)` 134 | and then use that to unbox recipients until you get a valid 135 | mac. This could be pretty fast, because there would be only one 136 | curve op, and then the rest is symmetric crypto. 137 | 138 | ### time complexity 139 | 140 | failed decrypt: recipients.length 141 | successful decrypt: recipients.length/2 142 | 143 | ## a more private multibox 144 | 145 | The properties might get a bit cleaner 146 | 147 | ``` js 148 | 149 | function multibox2 (msg, nonce, recipients) { 150 | 151 | var key = random(32) 152 | //MAX 16 recipients 153 | var _key = concat([new Buffer([(recipients.length-1) && 15]), key) 154 | var onetime = box_keypair() 155 | 156 | return concat([ 157 | nonce, //24 bytes 158 | onetime.publicKey, //32 bytes 159 | //recipients.length * 16+33 160 | recipients.map(function (r_pk) { 161 | return box(_key, nonce, r_pk, onetime.secretKey) 162 | }), 163 | //msg.length + 16 164 | secretbox_easy(msg, nonce, key) 165 | ]) 166 | } 167 | ``` 168 | 169 | An interesting property of this is that the recipient 170 | identities are forward secure (though, since I am assuming 171 | that the sender encrypts this message back to themself, 172 | whoever has their private key can read the message, and 173 | those id's are likely written in the message) 174 | 175 | Note, here that the recipient length field is encrypted to each 176 | recipient! If the number of recipients is not hidden, 177 | and I send a group message to a weird number, then someone 178 | hits "reply-all" it would suggest it was a reply. 179 | By hiding the number of "to" addresses, the messages will be _very private_. 180 | 181 | They will be more expensive to calculate, but since an `secretbox_open` 182 | attempt is actually very cheap (about 50 make 1 `scalarmult` op) 183 | so if you have 1 asym operation, then doing less than say, 50 184 | unboxes won't matter much. 185 | [see sodiumperf tests](https://github.com/dominictarr/sodiumperf) 186 | 187 | So this wouldn't be very much slower than any of the above 188 | algorithms, but it would be more private, even though it supports 189 | multiple recipients. Also, since the encrypted message has a one-off 190 | key, you could reveal the key to one message... if you needed 191 | to prove someone was harassing you, for example. Or, if you wanted 192 | to implement moderated groups, you could post a message to the moderator 193 | who would then reveal the key for that message to the group. 194 | 195 | Decrypt might look like this: 196 | 197 | ``` js 198 | function multibox2_open (ctxt, sk) { 199 | var nonce = ctxt.slice(0, 24) 200 | var onetime_pk = ctxt.slice(24, 24+32) 201 | var my_key = scalarmult(sk, onetime_pk) 202 | //try a bunch of keys 203 | var _key, start = 24+32, keysize = 16+1+32 204 | for(var i = 0; i < 8 || !key; i++) { 205 | var s = start+(keysize*i), e = s + keysize 206 | _key = secretbox_easy_open(ctxt.slice(s, e), nonce, my_key) 207 | } 208 | if(!key) return //message not addressed to us 209 | 210 | var length = key[0] 211 | var rest = ctxt.slice(start + keysize*length, ctxt.length) 212 | 213 | return secretbox_easy_open(rest, nonce, key.slice(1, 33)) 214 | } 215 | 216 | ``` 217 | 218 | ## Groups 219 | 220 | Often we want to communicate not just with individuals, but with groups. 221 | Although if more actors know the secret, then it's less secure. 222 | 223 | I can see two ways this could work, 224 | 225 | ### One Way Groups 226 | 227 | an author delegates a read cap (key) to selected peers, 228 | and then posts messages that holders of that key can read. 229 | The dynamic here is similar to facebook - if I add you 230 | as my friend then you can read my posts. 231 | 232 | When a peer is decrypting messages, they will try the keys 233 | on each message received from that author. In most cases, 234 | a given actor will create a handful of groups (friends, family, work, 235 | hobby group, etc) and any other peer is probably only a member 236 | of one or two of those. 237 | 238 | The cost of this would be `groups_added*max_groups`, 239 | the max number of groups a message should be broadcast to 240 | should probably be very small, like 2 or 3, then if 241 | A adds B to 3 groups, B will only need to attempt 9 unboxings 242 | to read a message. 243 | 244 | ### Shared Groups 245 | 246 | In othercases, there are groups of people who do not personally 247 | know each other form around a common interest. Facebook groups 248 | work like this. 249 | 250 | In this situation, it could be quite complicated to know what 251 | groups a given actor is in. For example, A creates group G, 252 | then adds B, who adds C. C then posts a message to group G. 253 | suppose that A sees C's message before she hears from A that 254 | C is now a member of G. Either A dosen't know to try G_key on 255 | C's message, or A just tries every group key on every message A sees. 256 | 257 | As long as A is not a member of more than a few groups, this is not 258 | too much of a problem. But, if there are two types of groups, 259 | then G that could be many groups to check. Probably the simplest 260 | way to mitigate this is _prevent cross posting_, allow only one 261 | shared group per message, then only check for group keys 262 | on the first slot! 263 | 264 | ## License 265 | 266 | MIT 267 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var sodium = require('chloride') 2 | var scalarmult = sodium.crypto_scalarmult 3 | var box = sodium.crypto_box_easy 4 | var secretbox = sodium.crypto_secretbox_easy 5 | var secretbox_open = sodium.crypto_secretbox_open_easy 6 | var keypair = sodium.crypto_box_keypair 7 | var concat = Buffer.concat 8 | 9 | function randombytes(n) { 10 | var b = new Buffer(n) 11 | sodium.randombytes(b) 12 | return b 13 | } 14 | 15 | function setMax (m) { 16 | m = m || DEFAULT_MAX 17 | if (m < 1 || m > 255) 18 | throw new Error('max recipients must be between 0 and 255.') 19 | return m 20 | } 21 | 22 | 23 | const DEFAULT_MAX = 7 24 | 25 | exports.encrypt = 26 | exports.multibox = function (msg, recipients, max) { 27 | 28 | max = setMax(max) 29 | 30 | if(recipients.length > max) 31 | throw new Error('max recipients is:'+max+' found:'+recipients.length) 32 | 33 | var nonce = randombytes(24) 34 | var key = randombytes(32) 35 | var onetime = keypair() 36 | 37 | var length_and_key = concat([new Buffer([recipients.length]), key]) 38 | return concat([ 39 | nonce, 40 | onetime.publicKey, 41 | concat(recipients.map(function (r_pk, i) { 42 | return secretbox(length_and_key, nonce, scalarmult(onetime.secretKey, r_pk)) 43 | })), 44 | secretbox(msg, nonce, key) 45 | ]) 46 | } 47 | 48 | exports.multibox_open_key = function (ctxt, sk, max) { //, groups... 49 | 50 | max = setMax(max) 51 | 52 | var nonce = ctxt.slice(0, 24) 53 | var onetime_pk = ctxt.slice(24, 24+32) 54 | var my_key = scalarmult(sk, onetime_pk) 55 | var length_and_key, key, length, start = 24+32, size = 32+1+16 56 | for(var i = 0; i <= max; i++) { 57 | var s = start+size*i 58 | if(s + size > (ctxt.length - 16)) return null 59 | length_and_key = secretbox_open(ctxt.slice(s, s + size), nonce, my_key) 60 | if(length_and_key) return length_and_key 61 | } 62 | } 63 | 64 | exports.multibox_open_body = function (ctxt, length_and_key) { //, groups... 65 | if(!length_and_key) return 66 | var key = length_and_key.slice(1) 67 | var length = length_and_key[0] 68 | var start = 24+32, size = 32+1+16 69 | var nonce = ctxt.slice(0, 24) 70 | return secretbox_open(ctxt.slice(start+length*size), nonce, key) 71 | } 72 | 73 | exports.decrypt = 74 | exports.multibox_open = function (ctxt, sk, max) { //, groups... 75 | var _key = exports.multibox_open_key(ctxt, sk, max) 76 | if(_key) return exports.multibox_open_body(ctxt, _key) 77 | } 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "private-box", 3 | "description": "encrypt a message to a secret number of recipients", 4 | "version": "0.3.1", 5 | "homepage": "https://github.com/auditdrivencrypto/private-box", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/auditdrivencrypto/private-box.git" 9 | }, 10 | "dependencies": { 11 | "chloride": "^2.2.9" 12 | }, 13 | "devDependencies": { 14 | "tape": "~4.0.0" 15 | }, 16 | "scripts": { 17 | "prepublish": "npm ls && npm test", 18 | "test": "set -e; for t in test/*.js; do node $t; done" 19 | }, 20 | "author": "Dominic Tarr (http://dominictarr.com)", 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /test/benchmark.js: -------------------------------------------------------------------------------- 1 | 2 | var chloride = require('chloride') 3 | var crypto = require('crypto') 4 | var box = require('../') 5 | 6 | var content = crypto.randomBytes(1024) 7 | var alice = chloride.crypto_box_keypair() 8 | var bob = chloride.crypto_box_keypair() 9 | function create (n, content, pk, max) { 10 | var a = [pk] 11 | for(var i = 1; i < n; i++) { 12 | a.push(chloride.crypto_box_keypair().publicKey) 13 | } 14 | return box.encrypt(content, a, max) 15 | } 16 | 17 | console.log(alice) 18 | console.log() 19 | 20 | function bench (max, N) { 21 | var ctxt = create(max, content, alice.publicKey, max) 22 | console.log('max:', max) //number of recipients 23 | //length of cyphertext, ratio of cyphertext to plaintext length 24 | console.log('length', ctxt.length, ctxt.length/content.length) 25 | var start = Date.now() 26 | for(var i = 0; i < N; i++) 27 | box.decrypt(ctxt, alice.secretKey, max) 28 | var hit = Date.now() - start 29 | console.log('hit', hit/N) //ms to decrypt a message that was for us 30 | 31 | var start = Date.now() 32 | for(var i = 0; i < N; i++) 33 | box.decrypt(ctxt, bob.secretKey, max) 34 | var miss = Date.now() - start 35 | console.log('miss', miss/N) //ms to fail to decrypt a message not for us 36 | 37 | console.log('ratio', miss/hit) //how much miss is bigger than hit. 38 | } 39 | 40 | var N = 10000 41 | bench(8, N) 42 | bench(16, N) 43 | bench(32, N) 44 | bench(64, N) 45 | bench(128, N) 46 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | var tape = require('tape') 3 | var crypto = require('crypto') 4 | 5 | var c = require('../') 6 | var sodium = require('chloride') 7 | 8 | var keypair = sodium.crypto_box_keypair 9 | 10 | var alice = keypair() 11 | var bob = keypair() 12 | 13 | function arrayOfSize (n) { 14 | return new Array(n+1).join('0').split('') 15 | } 16 | 17 | tape('simple', function (t) { 18 | 19 | var msg = new Buffer('hello there!') 20 | var ctxt = c.multibox(msg, [alice.publicKey, bob.publicKey]) 21 | console.log(ctxt) 22 | 23 | ;[alice.secretKey, bob.secretKey].forEach(function (sk) { 24 | 25 | console.log(ctxt, sk) 26 | var _msg = c.multibox_open(ctxt, sk) 27 | 28 | t.deepEqual(_msg, msg) 29 | 30 | }) 31 | 32 | t.end() 33 | }) 34 | 35 | tape('errors when too many recipients', function (t) { 36 | var msg = new Buffer('hello there!') 37 | var pk = alice.publicKey 38 | t.throws(function () { 39 | c.multibox(msg, [ 40 | pk,pk,pk,pk, 41 | pk,pk,pk,pk, 42 | pk,pk,pk,pk, 43 | pk,pk,pk,pk 44 | ]) 45 | }) 46 | t.end() 47 | }) 48 | 49 | function encryptDecryptTo (n, t) { 50 | var msg = crypto.randomBytes(1024) 51 | var keys = arrayOfSize(n).map(function () { return keypair() }) 52 | 53 | var ctxt = c.multibox(msg, keys.map(function (e) { return e.publicKey }), n) 54 | 55 | // a recipient key may open the message. 56 | keys.forEach(function (keys) { 57 | t.deepEqual(c.multibox_open(ctxt, keys.secretKey, n), msg) 58 | }) 59 | 60 | t.equal(c.multibox_open(ctxt, keypair().secretKey), undefined) 61 | } 62 | 63 | tape('with no custom max set, encrypt/decrypt to 7 keys', function (t) { 64 | encryptDecryptTo(7, t) 65 | t.end() 66 | }) 67 | 68 | tape('can encrypt/decrypt up to 255 recipients after setting a custom max', function (t) { 69 | encryptDecryptTo(255, t) 70 | t.end() 71 | }) 72 | 73 | 74 | tape('errors when max is more than 255 or less than 1', function (t) { 75 | var msg = new Buffer('hello there!') 76 | var ctxt = c.multibox(msg, [alice.publicKey, bob.publicKey]) 77 | var pk = alice.publicKey 78 | var sk = alice.secretKey 79 | t.throws(function () { 80 | c.multibox(msg, [ 81 | pk,pk,pk,pk, 82 | ], -1) 83 | }) 84 | t.throws(function () { 85 | c.multibox.open(ctxt, sk, 256) 86 | }) 87 | t.end() 88 | }) 89 | --------------------------------------------------------------------------------