-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: improve nonce performance (#162)
* feat: improve nonce performance Introduce Nonce class to maintain both Uint8Array and DataView and uint64 so that we can remove nonceToBytes function * chore: keep the encrypt() and decrypt() interfaces * chore: move nonce contants to nonce.ts * chore: fix MAX_NONCE
- Loading branch information
Showing
5 changed files
with
110 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* eslint-disable */ | ||
import benchmark from 'benchmark' | ||
import { Nonce } from '../dist/src/nonce.js' | ||
|
||
/** | ||
* Using Nonce class is 150x faster than nonceToBytes | ||
* nonceToBytes x 2.25 ops/sec ±1.41% (10 runs sampled) | ||
* Nonce class x 341 ops/sec ±0.71% (87 runs sampled) | ||
*/ | ||
function nonceToBytes (n) { | ||
// Even though we're treating the nonce as 8 bytes, RFC7539 specifies 12 bytes for a nonce. | ||
const nonce = new Uint8Array(12) | ||
new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength).setUint32(4, n, true) | ||
return nonce | ||
} | ||
const main = function () { | ||
const bench1 = new benchmark('nonceToBytes', { | ||
fn: function () { | ||
for (let i = 1e6; i < 2 * 1e6; i++) { | ||
nonceToBytes(i) | ||
} | ||
} | ||
}) | ||
.on('complete', function (stats) { | ||
console.log(String(stats.currentTarget)) | ||
}) | ||
|
||
bench1.run() | ||
|
||
const bench2 = new benchmark('Nonce class', { | ||
fn: function () { | ||
const nonce = new Nonce(1e6) | ||
for (let i = 1e6; i < 2 * 1e6; i++) { | ||
nonce.increment() | ||
} | ||
} | ||
}) | ||
.on('complete', function (stats) { | ||
console.log(String(stats.currentTarget)) | ||
}) | ||
|
||
bench2.run() | ||
} | ||
|
||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import type { bytes, uint64 } from './@types/basic' | ||
|
||
export const MIN_NONCE = 0 | ||
// For performance reasons, the nonce is represented as a JS `number` | ||
// Although JS `number` can safely represent integers up to 2 ** 53 - 1, we choose to only use | ||
// 4 bytes to store the data for performance reason. | ||
// This is a slight deviation from the noise spec, which describes the max nonce as 2 ** 64 - 2 | ||
// The effect is that this implementation will need a new handshake to be performed after fewer messages are exchanged than other implementations with full uint64 nonces. | ||
// this MAX_NONCE is still a large number of messages, so the practical effect of this is negligible. | ||
export const MAX_NONCE = 0xffffffff | ||
|
||
const ERR_MAX_NONCE = 'Cipherstate has reached maximum n, a new handshake must be performed' | ||
|
||
/** | ||
* The nonce is an uint that's increased over time. | ||
* Maintaining different representations help improve performance. | ||
*/ | ||
export class Nonce { | ||
private n: uint64 | ||
private readonly bytes: bytes | ||
private readonly view: DataView | ||
|
||
constructor (n = MIN_NONCE) { | ||
this.n = n | ||
this.bytes = new Uint8Array(12) | ||
this.view = new DataView(this.bytes.buffer, this.bytes.byteOffset, this.bytes.byteLength) | ||
this.view.setUint32(4, n, true) | ||
} | ||
|
||
increment (): void { | ||
this.n++ | ||
// Even though we're treating the nonce as 8 bytes, RFC7539 specifies 12 bytes for a nonce. | ||
this.view.setUint32(4, this.n, true) | ||
} | ||
|
||
getBytes (): bytes { | ||
return this.bytes | ||
} | ||
|
||
getUint64 (): uint64 { | ||
return this.n | ||
} | ||
|
||
assertValue (): void { | ||
if (this.n > MAX_NONCE) { | ||
throw new Error(ERR_MAX_NONCE) | ||
} | ||
} | ||
} |