or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

aead.mdauth.mdbox.mded25519.mdhash.mdindex.mdkdf.mdkx.mdmemory.mdpwhash.mdrandom.mdsecretbox.mdsecretstream.mdshorthash.mdsign.mdstream.md

secretstream.mddocs/

0

# Secret Streams

1

2

Streaming authenticated encryption using XChaCha20-Poly1305 for encrypting sequences of messages with automatic key rotation and message ordering.

3

4

## Capabilities

5

6

### Key Generation

7

8

Generate keys for secret stream operations.

9

10

```javascript { .api }

11

/**

12

* Generate random key for secret stream encryption

13

* @param k - Output buffer for key (must be KEYBYTES long)

14

*/

15

function crypto_secretstream_xchacha20poly1305_keygen(k: Buffer): void;

16

```

17

18

**Usage Example:**

19

20

```javascript

21

const sodium = require('sodium-native');

22

23

// Generate key for secret stream

24

const key = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES);

25

sodium.crypto_secretstream_xchacha20poly1305_keygen(key);

26

```

27

28

### Push Stream Initialization

29

30

Initialize a secret stream for encryption (push mode).

31

32

```javascript { .api }

33

/**

34

* Initialize secret stream for encryption

35

* @param state - Output buffer for stream state (must be STATEBYTES long)

36

* @param header - Output buffer for stream header (must be HEADERBYTES long)

37

* @param k - Key buffer (must be KEYBYTES long)

38

* @throws Error if initialization fails or buffer sizes incorrect

39

*/

40

function crypto_secretstream_xchacha20poly1305_init_push(

41

state: Buffer,

42

header: Buffer,

43

k: Buffer

44

): void;

45

```

46

47

### Pull Stream Initialization

48

49

Initialize a secret stream for decryption (pull mode).

50

51

```javascript { .api }

52

/**

53

* Initialize secret stream for decryption

54

* @param state - Output buffer for stream state (must be STATEBYTES long)

55

* @param header - Stream header from push initialization (must be HEADERBYTES long)

56

* @param k - Key buffer (must be KEYBYTES long)

57

* @throws Error if initialization fails or buffer sizes incorrect

58

*/

59

function crypto_secretstream_xchacha20poly1305_init_pull(

60

state: Buffer,

61

header: Buffer,

62

k: Buffer

63

): void;

64

```

65

66

**Usage Example:**

67

68

```javascript

69

const sodium = require('sodium-native');

70

71

// Initialize encryption stream

72

const pushState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);

73

const header = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);

74

75

sodium.crypto_secretstream_xchacha20poly1305_init_push(pushState, header, key);

76

77

// Initialize decryption stream

78

const pullState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);

79

sodium.crypto_secretstream_xchacha20poly1305_init_pull(pullState, header, key);

80

```

81

82

### Push (Encryption)

83

84

Encrypt and authenticate a message chunk in the stream.

85

86

```javascript { .api }

87

/**

88

* Encrypt message chunk in secret stream

89

* @param state - Stream state buffer from init_push

90

* @param c - Output buffer for ciphertext (must be m.length + ABYTES)

91

* @param m - Message buffer to encrypt

92

* @param ad - Optional additional data buffer (can be null)

93

* @param tag - Message tag indicating message type/position

94

* @returns Number of bytes written to ciphertext buffer

95

* @throws Error if encryption fails

96

*/

97

function crypto_secretstream_xchacha20poly1305_push(

98

state: Buffer,

99

c: Buffer,

100

m: Buffer,

101

ad: Buffer | null,

102

tag: number

103

): number;

104

```

105

106

### Pull (Decryption)

107

108

Decrypt and verify a message chunk from the stream.

109

110

```javascript { .api }

111

/**

112

* Decrypt and verify message chunk from secret stream

113

* @param state - Stream state buffer from init_pull

114

* @param m - Output buffer for plaintext (must be c.length - ABYTES)

115

* @param tag - Output buffer for message tag (must be TAGBYTES long)

116

* @param c - Ciphertext buffer to decrypt

117

* @param ad - Optional additional data buffer (can be null)

118

* @returns Number of bytes written to plaintext buffer

119

* @throws Error if decryption or verification fails

120

*/

121

function crypto_secretstream_xchacha20poly1305_pull(

122

state: Buffer,

123

m: Buffer,

124

tag: Buffer,

125

c: Buffer,

126

ad: Buffer | null

127

): number;

128

```

129

130

**Usage Example:**

131

132

```javascript

133

const sodium = require('sodium-native');

134

135

// Encrypt message chunks

136

const message1 = Buffer.from('First message');

137

const message2 = Buffer.from('Second message');

138

const message3 = Buffer.from('Final message');

139

140

// Encrypt first message

141

const ciphertext1 = Buffer.alloc(message1.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);

142

const len1 = sodium.crypto_secretstream_xchacha20poly1305_push(

143

pushState,

144

ciphertext1,

145

message1,

146

null,

147

sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE

148

);

149

150

// Encrypt second message

151

const ciphertext2 = Buffer.alloc(message2.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);

152

const len2 = sodium.crypto_secretstream_xchacha20poly1305_push(

153

pushState,

154

ciphertext2,

155

message2,

156

null,

157

sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE

158

);

159

160

// Encrypt final message

161

const ciphertext3 = Buffer.alloc(message3.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);

162

const len3 = sodium.crypto_secretstream_xchacha20poly1305_push(

163

pushState,

164

ciphertext3,

165

message3,

166

null,

167

sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL

168

);

169

170

// Decrypt message chunks

171

const plaintext1 = Buffer.alloc(ciphertext1.length - sodium.crypto_secretstream_xchacha20poly1305_ABYTES);

172

const tag1 = Buffer.alloc(1);

173

sodium.crypto_secretstream_xchacha20poly1305_pull(pullState, plaintext1, tag1, ciphertext1, null);

174

175

console.log('Decrypted:', plaintext1.toString());

176

console.log('Tag:', tag1[0]); // Should be TAG_MESSAGE

177

```

178

179

### Key Rotation

180

181

Rotate the stream key to provide forward secrecy.

182

183

```javascript { .api }

184

/**

185

* Rotate stream key for forward secrecy

186

* @param state - Stream state buffer to rekey

187

*/

188

function crypto_secretstream_xchacha20poly1305_rekey(state: Buffer): void;

189

```

190

191

**Usage Example:**

192

193

```javascript

194

const sodium = require('sodium-native');

195

196

// Rotate key after processing sensitive data

197

sodium.crypto_secretstream_xchacha20poly1305_rekey(pushState);

198

199

// Messages encrypted after rekey cannot be decrypted with old state

200

const sensitiveMessage = Buffer.from('Top secret data');

201

const encryptedSensitive = Buffer.alloc(sensitiveMessage.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);

202

203

sodium.crypto_secretstream_xchacha20poly1305_push(

204

pushState,

205

encryptedSensitive,

206

sensitiveMessage,

207

null,

208

sodium.crypto_secretstream_xchacha20poly1305_TAG_REKEY

209

);

210

```

211

212

## Constants

213

214

```javascript { .api }

215

// State buffer size in bytes

216

const crypto_secretstream_xchacha20poly1305_STATEBYTES: number;

217

218

// Authentication tag size in bytes

219

const crypto_secretstream_xchacha20poly1305_ABYTES: number;

220

221

// Stream header size in bytes

222

const crypto_secretstream_xchacha20poly1305_HEADERBYTES: number;

223

224

// Key size in bytes

225

const crypto_secretstream_xchacha20poly1305_KEYBYTES: number;

226

227

// Message tag size in bytes

228

const crypto_secretstream_xchacha20poly1305_TAGBYTES: number;

229

230

// Maximum message size in bytes

231

const crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX: number;

232

233

// Message tags for different message types

234

const crypto_secretstream_xchacha20poly1305_TAG_MESSAGE: number;

235

const crypto_secretstream_xchacha20poly1305_TAG_PUSH: number;

236

const crypto_secretstream_xchacha20poly1305_TAG_REKEY: number;

237

const crypto_secretstream_xchacha20poly1305_TAG_FINAL: number;

238

```

239

240

## Message Tags

241

242

Secret streams use tags to indicate message types and stream state:

243

244

- **TAG_MESSAGE**: Regular message in the stream

245

- **TAG_PUSH**: End of a push sequence (commit point)

246

- **TAG_REKEY**: Key rotation occurred (forward secrecy)

247

- **TAG_FINAL**: Final message in the stream

248

249

## Security Considerations

250

251

- **Forward Secrecy**: Use TAG_REKEY and `rekey()` function to prevent decryption of future messages if state is compromised.

252

- **Message Ordering**: Secret streams enforce message ordering and prevent replay attacks.

253

- **Stream Integrity**: Each message is authenticated within the context of the entire stream.

254

- **Key Management**: Store and transmit the stream header securely alongside the first ciphertext.

255

256

## Common Patterns

257

258

### Secure File Streaming

259

260

```javascript

261

const sodium = require('sodium-native');

262

const fs = require('fs');

263

264

class SecureFileStream {

265

constructor(key) {

266

this.key = key || Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES);

267

if (!key) {

268

sodium.crypto_secretstream_xchacha20poly1305_keygen(this.key);

269

}

270

}

271

272

encryptFile(inputFile, outputFile) {

273

const state = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);

274

const header = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);

275

276

sodium.crypto_secretstream_xchacha20poly1305_init_push(state, header, this.key);

277

278

const input = fs.createReadStream(inputFile, { highWaterMark: 4096 });

279

const output = fs.createWriteStream(outputFile);

280

281

// Write header first

282

output.write(header);

283

284

let isFirstChunk = true;

285

let isLastChunk = false;

286

287

input.on('data', (chunk) => {

288

const ciphertext = Buffer.alloc(chunk.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);

289

290

// Determine tag based on position

291

let tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;

292

if (isFirstChunk) {

293

tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_PUSH;

294

isFirstChunk = false;

295

}

296

297

const len = sodium.crypto_secretstream_xchacha20poly1305_push(

298

state, ciphertext, chunk, null, tag

299

);

300

301

output.write(ciphertext.subarray(0, len));

302

});

303

304

input.on('end', () => {

305

// Write final empty message with TAG_FINAL

306

const finalCiphertext = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_ABYTES);

307

const len = sodium.crypto_secretstream_xchacha20poly1305_push(

308

state, finalCiphertext, Buffer.alloc(0), null,

309

sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL

310

);

311

312

output.write(finalCiphertext.subarray(0, len));

313

output.end();

314

});

315

}

316

317

decryptFile(inputFile, outputFile) {

318

const encryptedData = fs.readFileSync(inputFile);

319

const header = encryptedData.subarray(0, sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);

320

321

const state = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);

322

sodium.crypto_secretstream_xchacha20poly1305_init_pull(state, header, this.key);

323

324

const output = fs.createWriteStream(outputFile);

325

let offset = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES;

326

327

while (offset < encryptedData.length) {

328

// Read chunk length (simplified - real implementation needs proper framing)

329

const chunkSize = Math.min(4096 + sodium.crypto_secretstream_xchacha20poly1305_ABYTES,

330

encryptedData.length - offset);

331

332

const ciphertext = encryptedData.subarray(offset, offset + chunkSize);

333

const plaintext = Buffer.alloc(ciphertext.length - sodium.crypto_secretstream_xchacha20poly1305_ABYTES);

334

const tag = Buffer.alloc(1);

335

336

const len = sodium.crypto_secretstream_xchacha20poly1305_pull(

337

state, plaintext, tag, ciphertext, null

338

);

339

340

if (len > 0) {

341

output.write(plaintext.subarray(0, len));

342

}

343

344

// Check for final tag

345

if (tag[0] === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {

346

break;

347

}

348

349

offset += chunkSize;

350

}

351

352

output.end();

353

}

354

}

355

```

356

357

### Network Protocol with Forward Secrecy

358

359

```javascript

360

const sodium = require('sodium-native');

361

362

class SecureProtocol {

363

constructor() {

364

this.sendState = null;

365

this.receiveState = null;

366

this.messageCount = 0;

367

}

368

369

initializeSession(key, isInitiator = true) {

370

const header = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);

371

372

if (isInitiator) {

373

this.sendState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);

374

sodium.crypto_secretstream_xchacha20poly1305_init_push(this.sendState, header, key);

375

return header; // Send to peer

376

} else {

377

this.receiveState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);

378

sodium.crypto_secretstream_xchacha20poly1305_init_pull(this.receiveState, header, key);

379

}

380

}

381

382

sendMessage(message, isImportant = false) {

383

if (!this.sendState) throw new Error('Send state not initialized');

384

385

const ciphertext = Buffer.alloc(message.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);

386

387

// Use different tags for message importance

388

const tag = isImportant ?

389

sodium.crypto_secretstream_xchacha20poly1305_TAG_PUSH :

390

sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;

391

392

const len = sodium.crypto_secretstream_xchacha20poly1305_push(

393

this.sendState, ciphertext, message, null, tag

394

);

395

396

this.messageCount++;

397

398

// Rotate key every 1000 messages for forward secrecy

399

if (this.messageCount % 1000 === 0) {

400

sodium.crypto_secretstream_xchacha20poly1305_rekey(this.sendState);

401

}

402

403

return ciphertext.subarray(0, len);

404

}

405

406

receiveMessage(ciphertext) {

407

if (!this.receiveState) throw new Error('Receive state not initialized');

408

409

const plaintext = Buffer.alloc(ciphertext.length - sodium.crypto_secretstream_xchacha20poly1305_ABYTES);

410

const tag = Buffer.alloc(1);

411

412

const len = sodium.crypto_secretstream_xchacha20poly1305_pull(

413

this.receiveState, plaintext, tag, ciphertext, null

414

);

415

416

// Handle different message tags

417

switch (tag[0]) {

418

case sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE:

419

return { message: plaintext.subarray(0, len), type: 'regular' };

420

case sodium.crypto_secretstream_xchacha20poly1305_TAG_PUSH:

421

return { message: plaintext.subarray(0, len), type: 'important' };

422

case sodium.crypto_secretstream_xchacha20poly1305_TAG_REKEY:

423

sodium.crypto_secretstream_xchacha20poly1305_rekey(this.receiveState);

424

return { message: plaintext.subarray(0, len), type: 'rekey' };

425

case sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL:

426

return { message: plaintext.subarray(0, len), type: 'final' };

427

default:

428

throw new Error('Unknown message tag');

429

}

430

}

431

}

432

```