working rss feed to nostr publish

This commit is contained in:
2023-11-24 00:43:28 -05:00
parent 06edcb57ae
commit 88d2f9cfec
8396 changed files with 783105 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2022 Paul Miller (https://paulmillr.com)
Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+424
View File
@@ -0,0 +1,424 @@
# noble-ciphers
Auditable & minimal JS implementation of Salsa20, ChaCha, Poly1305 & AES-SIV
- 🔒 Auditable
- 🔻 Tree-shaking-friendly: use only what's necessary, other code won't be included
- 🏎 [Ultra-fast](#speed), hand-optimized for caveats of JS engines
- 🔍 Unique tests ensure correctness: property-based, cross-library and Wycheproof vectors
- 💼 AES: GCM (Galois Counter Mode), SIV (Nonce Misuse-Resistant encryption)
- 💃 Salsa20, ChaCha, XSalsa20, XChaCha, Poly1305, ChaCha8, ChaCha12
- ✍️ FF1 format-preserving encryption
- 🧂 Compatible with NaCl / libsodium secretbox
- 🪶 Just 500 lines / 4KB gzipped for Salsa + ChaCha + Poly build
### This library belongs to _noble_ crypto
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
- No dependencies, protection against supply chain attacks
- Auditable TypeScript / JS code
- Supported on all major platforms
- Releases are signed with PGP keys and built transparently with NPM provenance
- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
[ciphers](https://github.com/paulmillr/noble-ciphers),
[curves](https://github.com/paulmillr/noble-curves),
[hashes](https://github.com/paulmillr/noble-hashes),
4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) /
[ed25519](https://github.com/paulmillr/noble-ed25519)
## Usage
> npm install @noble/ciphers
We support all major platforms and runtimes.
For [Deno](https://deno.land), ensure to use
[npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers).
For React Native, you may need a
[polyfill for crypto.getRandomValues](https://github.com/LinusU/react-native-get-random-values).
If you don't like NPM, a standalone
[noble-ciphers.js](https://github.com/paulmillr/noble-ciphers/releases) is also available.
```js
// import * from '@noble/ciphers'; // Error: use sub-imports, to ensure small app size
// Simple API: uses xchacha20poly1305 with random nonce. Abstracts complexity away.
import { encrypt, decrypt, utf8ToBytes, randomKey } from '@noble/ciphers/simple';
const key = randomKey();
const plaintext = utf8ToBytes('hello'); // Library works over Uint8Array-s
const ciphertext = encrypt(key, plaintext);
const plaintext_ = decrypt(key, ciphertext);
// Simple AES API: uses aes_256_gcm with random nonce.
import { aes_encrypt, aes_decrypt } from '@noble/ciphers/simple';
const a_key = randomKey();
const a_ciphertext = await aes_encrypt(a_key, plaintext);
const a_plaintext = await aes_decrypt(a_key, a_ciphertext);
```
For specific APIs, see [salsa](#salsa), [chacha](#chacha) and [aes](#aes)
sections below. All available imports:
```js
// AEADs
import { xsalsa20poly1305 } from '@noble/ciphers/salsa'; // aka sodium secretbox
import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha';
// Pure ciphers
import { salsa20, xsalsa20 } from '@noble/ciphers/salsa';
import { chacha20, xchacha20, chacha8, chacha12 } from '@noble/ciphers/chacha';
// AES webcrypto shortcuts
import {
aes_128_gcm, aes_128_ctr, aes_128_cbc, aes_256_gcm, aes_256_ctr, aes_256_cbc
} from '@noble/ciphers/webcrypto/aes';
import { aes_256_gcm_siv } from '@noble/ciphers/webcrypto/siv'; // AES-GCM-SIV
import { FF1, BinaryFF1 } from '@noble/ciphers/webcrypto/ff1'; // FF1
import { randomBytes } from '@noble/ciphers/webcrypto/utils';
import { bytesToHex, hexToBytes, bytesToUtf8, utf8ToBytes, concatBytes } from '@noble/ciphers/utils';
import * as c from '@noble/ciphers/_micro'; // Everything, written in minimal, auditable way
```
### How to encrypt properly
1. Use unpredictable key with enough entropy
- Random key must be using cryptographically secure random number generator (CSPRNG), not `Math.random` etc.
- Non-random key generated from KDF is fine
- Re-using key is fine, but be aware of rules for cryptographic key wear-out and [encryption limits](#encryption-limits)
2. Use new nonce every time and [don't repeat it](#nonces)
- `simple` module manages nonces for you
- chacha and salsa20 are fine for sequential counters that *never* repeat: `01, 02...`
- xchacha and xsalsa20 should be used for random nonces instead
3. Prefer authenticated encryption (AEAD)
- chacha20poly1305 is good, chacha20 without poly1305 is bad
- aes-gcm is good, aes-ctr / aes-cbc is bad
- Flipping bits or even ciphertext substitution won't be detected in
unauthenticated ciphers
4. Don't re-use keys between different protocols
- For example, using secp256k1 key in AES is bad
- Use hkdf or, at least, a hash function to create sub-key instead
### Salsa
```js
import { xsalsa20poly1305 } from '@noble/ciphers/salsa';
import { utf8ToBytes } from '@noble/ciphers/utils';
import { randomBytes } from '@noble/ciphers/webcrypto/utils';
const key = randomBytes(32);
const data = utf8ToBytes('hello, noble'); // strings must be converted to Uint8Array
const nonce = randomBytes(24);
const stream_x = xsalsa20poly1305(key, nonce); // === secretbox(key, nonce)
const ciphertext = stream_x.encrypt(data); // === secretbox.seal(data)
const plaintext = stream_x.decrypt(ciphertext); // === secretbox.open(ciphertext)
// Avoid memory allocations: re-use same uint8array
stream_x.decrypt(ciphertext, ciphertext.subarray(-16));
// ciphertext is now plaintext
// We provide sodium secretbox alias, which is just xsalsa20poly1305
import { secretbox } from '@noble/ciphers/simple';
const box = secretbox(key, nonce);
const ciphertext = box.seal(plaintext);
const plaintext = box.open(ciphertext);
// Standalone salsa is also available
import { salsa20, xsalsa20 } from '@noble/ciphers/salsa';
const nonce12 = randomBytes(12); // salsa uses 96-bit nonce, xsalsa uses 192-bit
const encrypted_s = salsa20(key, nonce12, data);
const encrypted_xs = xsalsa20(key, nonce, data);
```
Salsa20 stream cipher ([website](https://cr.yp.to/snuffle.html),
[PDF](https://cr.yp.to/snuffle/salsafamily-20071225.pdf),
[wiki](https://en.wikipedia.org/wiki/Salsa20)) was released in 2005.
Salsa's goal was to implement AES replacement that does not rely on S-Boxes,
which are hard to implement in a constant-time manner.
Salsa20 is usually faster than AES, a big deal on slow, budget mobile phones.
[XSalsa20](https://cr.yp.to/snuffle/xsalsa-20110204.pdf), extended-nonce
variant was released in 2008. It switched nonces from 96-bit to 192-bit,
and became safe to be picked at random.
Nacl / Libsodium popularized term "secretbox", a simple black-box
authenticated encryption. Secretbox is just xsalsa20-poly1305. We provide the
alias and corresponding seal / open methods.
### ChaCha
```js
import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha';
import { utf8ToBytes } from '@noble/ciphers/utils';
import { randomBytes } from '@noble/ciphers/webcrypto/utils';
const key = randomBytes(32);
const data = utf8ToBytes('hello, noble'); // strings must be converted to Uint8Array
const nonce12 = randomBytes(12); // chacha uses 96-bit nonce
const stream_c = chacha20poly1305(key, nonce12);
const ciphertext_c = stream_c.encrypt(data);
const plaintext_c = stream_c.decrypt(ciphertext_c); // === data
// Avoid memory allocations: re-use same uint8array
stream_c.decrypt(ciphertext_c, ciphertext_c.subarray(-16));
// ciphertext_c is now plaintext_c
const nonce24 = randomBytes(24); // xchacha uses 192-bit nonce
const stream_xc = xchacha20poly1305(key, nonce24);
const ciphertext_xc = stream_xc.encrypt(data);
const plaintext_xc = stream_xc.decrypt(ciphertext_xc); // === data
// Standalone chacha is also available
import { chacha20, xchacha20, chacha8, chacha12 } from '@noble/ciphers/chacha';
const ciphertext_pc = chacha20(key, nonce12, data);
const ciphertext_pxc = xchacha20(key, nonce24, data);
const ciphertext_8 = chacha8(key, nonce12, data);
const ciphertext_12 = chacha12(key, nonce12, data);
```
ChaCha20 stream cipher ([website](https://cr.yp.to/chacha.html),
[PDF](http://cr.yp.to/chacha/chacha-20080128.pdf),
[wiki](https://en.wikipedia.org/wiki/Salsa20),
[blog post](https://loup-vaillant.fr/tutorials/chacha20-design)) was released
in 2008. ChaCha aims to increase the diffusion per round, but had slightly less
cryptanalysis. It was standardized in
[RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439) and is now used in TLS 1.3.
XChaCha20 ([draft RFC](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha))
extended-nonce variant is also provided. Similar to XSalsa, it's safe to use with
randomly-generated nonces.
### Poly1305
Poly1305 ([website](https://cr.yp.to/mac.html),
[PDF](https://cr.yp.to/mac/poly1305-20050329.pdf),
[wiki](https://en.wikipedia.org/wiki/Poly1305),
[blog post](https://loup-vaillant.fr/tutorials/poly1305-design))
is a fast and parallel secret-key message-authentication code suitable for
a wide variety of applications. It was standardized in
[RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439) and is now used in TLS 1.3.
Poly1305 is polynomial-evaluation MAC, which is not perfect for every situation:
just like GCM, it lacks Random Key Robustness: the tags can be forged, and can't
be used in PAKE schemes. See
[invisible salamanders attack](https://keymaterial.net/2020/09/07/invisible-salamanders-in-aes-gcm-siv/).
To combat invisible salamanders, `hash(key)` can be included in ciphertext,
however, this would violate ciphertext indistinguishability:
an attacker would know which key was used - so `HKDF(key, i)`
could be used instead.
Even though poly1305 can be imported separately from the library, we suggest
using chacha-poly or xsalsa-poly.
### AES
```js
import {
aes_128_gcm, aes_128_ctr, aes_128_cbc,
aes_256_gcm, aes_256_ctr, aes_256_cbc
} from '@noble/ciphers/webcrypto/aes';
for (let cipher of [aes_256_gcm, aes_256_ctr, aes_256_cbc]) {
const stream_new = cipher(key, nonce);
const ciphertext_new = await stream_new.encrypt(plaintext);
const plaintext_new = await stream_new.decrypt(ciphertext);
}
import { aes_256_gcm_siv } from '@noble/ciphers/webcrypto/siv';
const stream_siv = aes_256_gcm_siv(key, nonce)
await stream_siv.encrypt(plaintext, AAD);
```
AES ([wiki](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard))
is a variant of Rijndael block cipher, standardized by NIST.
We don't implement AES in pure JS for now: instead, we wrap WebCrypto built-in
and provide an improved, simple API. There is a simple reason for this:
webcrypto API is terrible: different block modes require different params.
Optional [AES-GCM-SIV](https://en.wikipedia.org/wiki/AES-GCM-SIV)
(synthetic initialization vector) nonce-misuse-resistant mode is also provided.
##### How AES works
`cipher = encrypt(block, key)`. Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256bit). Every round does:
1. **S-box**, table substitution
2. **Shift rows**, cyclic shift left of all rows of data array
3. **Mix columns**, multiplying every column by fixed polynomial
4. **Add round key**, round_key xor i-th column of array
For non-deterministic (not ECB) schemes, initialization vector (IV) is mixed to block/key;
and each new round either depends on previous block's key, or on some counter.
##### Block modes
We only expose GCM & SIV for now.
- ECB — simple deterministic replacement. Dangerous: always map x to y. See [AES Penguin](https://words.filippo.io/the-ecb-penguin/)
- CBC — key is previous rounds block. Hard to use: need proper padding, also needs MAC
- CTR — counter, allows to create streaming cipher. Requires good IV. Parallelizable. OK, but no MAC
- GCM — modern CTR, parallel, with MAC. Not ideal:
- Conservative key wear-out is `2**32` (4B) msgs
- MAC can be forged: see Poly1305 section above
- SIV — synthetic initialization vector, nonce-misuse-resistant
- Can be 1.5-2x slower than GCM by itself
- nonce misuse-resistant schemes guarantee that if a
nonce repeats, then the only security loss is that identical
plaintexts will produce identical ciphertexts
- MAC can be forged: see Poly1305 section above
- XTS — used in hard drives. Similar to ECB (deterministic), but has `[i][j]`
tweak arguments corresponding to sector i and 16-byte block (part of sector) j. Not authenticated!
### FF1
Format-preserving encryption algorithm (FPE-FF1) specified in NIST Special Publication 800-38G.
More info: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38G.pdf
## Security
The library is experimental. Use at your own risk.
### Nonces
Most ciphers need a key and a nonce (aka initialization vector / IV) to encrypt a data:
ciphertext = encrypt(plaintext, key, nonce)
Repeating (key, nonce) pair with different plaintexts would allow an attacker to decrypt it:
ciphertext_a = encrypt(plaintext_a, key, nonce)
ciphertext_b = encrypt(plaintext_b, key, nonce)
stream_diff = xor(ciphertext_a, ciphertext_b) # Break encryption
So, you can't repeat nonces. One way of doing so is using counters:
for i in 0..:
ciphertext[i] = encrypt(plaintexts[i], key, i)
Another is generating random nonce every time:
for i in 0..:
rand_nonces[i] = random()
ciphertext[i] = encrypt(plaintexts[i], key, rand_nonces[i])
Counters are OK, but it's not always possible to store current counter value:
e.g. in decentralized, unsyncable systems.
Randomness is OK, but there's a catch:
ChaCha20 and AES-GCM use 96-bit / 12-byte nonces, which implies
higher chance of collision. In the example above,
`random()` can collide and produce repeating nonce.
To safely use random nonces, utilize XSalsa20 or XChaCha:
they increased nonce length to 192-bit, minimizing a chance of collision.
AES-SIV is also fine. In situations where you can't use eXtended-nonce
algorithms, key rotation is advised. hkdf would work great for this case.
### Encryption limits
A "protected message" would mean a probability of `2**-50` that a passive attacker
successfully distinguishes the ciphertext outputs of the AEAD scheme from the outputs
of a random function. See [RFC draft](https://datatracker.ietf.org/doc/draft-irtf-cfrg-aead-limits/) for details.
- Max message size:
- AES-GCM: ~68GB, `2**36-256`
- Salsa, ChaCha, XSalsa, XChaCha: ~256GB, `2**38-64`
- Max amount of protected messages, under same key:
- AES-GCM: `2**32.5`
- Salsa, ChaCha: `2**46`, but only integrity is affected, not confidentiality
- XSalsa, XChaCha: `2**72`
- Max amount of protected messages, across all keys:
- AES-GCM: `2**69/B` where B is max blocks encrypted by a key. Meaning
`2**59` for 1KB, `2**49` for 1MB, `2**39` for 1GB
- Salsa, ChaCha, XSalsa, XChaCha: `2**100`
## Speed
To summarize, noble is the fastest JS implementation.
You can gain additional speed-up and
avoid memory allocations by passing `output`
uint8array into encrypt / decrypt methods.
Benchmark results on Apple M2 with node v20:
```
encrypt (64B)
├─xsalsa20poly1305 x 484,966 ops/sec @ 2μs/op
├─chacha20poly1305 x 442,282 ops/sec @ 2μs/op
└─xchacha20poly1305 x 300,842 ops/sec @ 3μs/op
encrypt (1KB)
├─xsalsa20poly1305 x 143,905 ops/sec @ 6μs/op
├─chacha20poly1305 x 141,663 ops/sec @ 7μs/op
└─xchacha20poly1305 x 122,639 ops/sec @ 8μs/op
encrypt (8KB)
├─xsalsa20poly1305 x 23,373 ops/sec @ 42μs/op
├─chacha20poly1305 x 23,683 ops/sec @ 42μs/op
└─xchacha20poly1305 x 23,066 ops/sec @ 43μs/op
encrypt (1MB)
├─xsalsa20poly1305 x 193 ops/sec @ 5ms/op
├─chacha20poly1305 x 196 ops/sec @ 5ms/op
└─xchacha20poly1305 x 195 ops/sec @ 5ms/op
```
Unauthenticated encryption:
```
encrypt (64B)
├─salsa x 1,272,264 ops/sec @ 786ns/op
├─chacha x 1,526,717 ops/sec @ 655ns/op
├─xsalsa x 847,457 ops/sec @ 1μs/op
└─xchacha x 848,896 ops/sec @ 1μs/op
encrypt (1KB)
├─salsa x 355,492 ops/sec @ 2μs/op
├─chacha x 377,358 ops/sec @ 2μs/op
├─xsalsa x 311,915 ops/sec @ 3μs/op
└─xchacha x 315,457 ops/sec @ 3μs/op
encrypt (8KB)
├─salsa x 56,063 ops/sec @ 17μs/op
├─chacha x 57,359 ops/sec @ 17μs/op
├─xsalsa x 54,848 ops/sec @ 18μs/op
└─xchacha x 55,475 ops/sec @ 18μs/op
encrypt (1MB)
├─salsa x 465 ops/sec @ 2ms/op
├─chacha x 474 ops/sec @ 2ms/op
├─xsalsa x 466 ops/sec @ 2ms/op
└─xchacha x 476 ops/sec @ 2ms/op
```
Compare to other implementations:
```
xsalsa20poly1305 (encrypt, 1MB)
├─tweetnacl x 108 ops/sec @ 9ms/op
├─noble x 190 ops/sec @ 5ms/op
└─micro x 21 ops/sec @ 47ms/op
chacha20poly1305 (encrypt, 1MB)
├─node x 1,360 ops/sec @ 735μs/op
├─stablelib x 117 ops/sec @ 8ms/op
├─noble x 193 ops/sec @ 5ms/op
└─micro x 19 ops/sec @ 50ms/op
chacha (encrypt, 1MB)
├─node x 2,035 ops/sec @ 491μs/op
├─stablelib x 206 ops/sec @ 4ms/op
├─noble x 474 ops/sec @ 2ms/op
└─micro x 61 ops/sec @ 16ms/op
```
## Contributing & testing
1. Clone the repository
2. `npm install` to install build dependencies like TypeScript
3. `npm run build` to compile TypeScript code
4. `npm run test` will execute all main tests
## License
The MIT License (MIT)
Copyright (c) 2023 Paul Miller [(https://paulmillr.com)](https://paulmillr.com)
Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
See LICENSE file.
+23
View File
@@ -0,0 +1,23 @@
declare function number(n: number): void;
declare function bool(b: boolean): void;
declare function bytes(b: Uint8Array | undefined, ...lengths: number[]): void;
export type Hash = {
(data: Uint8Array): Uint8Array;
blockLen: number;
outputLen: number;
create: any;
};
declare function hash(hash: Hash): void;
declare function exists(instance: any, checkFinished?: boolean): void;
declare function output(out: any, instance: any): void;
export { number, bool, bytes, hash, exists, output };
declare const assert: {
number: typeof number;
bool: typeof bool;
bytes: typeof bytes;
hash: typeof hash;
exists: typeof exists;
output: typeof output;
};
export default assert;
//# sourceMappingURL=_assert.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"_assert.d.ts","sourceRoot":"","sources":["src/_assert.ts"],"names":[],"mappings":"AAAA,iBAAS,MAAM,CAAC,CAAC,EAAE,MAAM,QAExB;AAED,iBAAS,IAAI,CAAC,CAAC,EAAE,OAAO,QAEvB;AAED,iBAAS,KAAK,CAAC,CAAC,EAAE,UAAU,GAAG,SAAS,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,QAI7D;AAED,MAAM,MAAM,IAAI,GAAG;IACjB,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;CACb,CAAC;AACF,iBAAS,IAAI,CAAC,IAAI,EAAE,IAAI,QAKvB;AAED,iBAAS,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE,aAAa,UAAO,QAGlD;AACD,iBAAS,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,QAMtC;AAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACrD,QAAA,MAAM,MAAM;;;;;;;CAAgD,CAAC;AAC7D,eAAe,MAAM,CAAC"}
+45
View File
@@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.output = exports.exists = exports.hash = exports.bytes = exports.bool = exports.number = void 0;
function number(n) {
if (!Number.isSafeInteger(n) || n < 0)
throw new Error(`Wrong positive integer: ${n}`);
}
exports.number = number;
function bool(b) {
if (typeof b !== 'boolean')
throw new Error(`Expected boolean, not ${b}`);
}
exports.bool = bool;
function bytes(b, ...lengths) {
if (!(b instanceof Uint8Array))
throw new Error('Expected Uint8Array');
if (lengths.length > 0 && !lengths.includes(b.length))
throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
}
exports.bytes = bytes;
function hash(hash) {
if (typeof hash !== 'function' || typeof hash.create !== 'function')
throw new Error('hash must be wrapped by utils.wrapConstructor');
number(hash.outputLen);
number(hash.blockLen);
}
exports.hash = hash;
function exists(instance, checkFinished = true) {
if (instance.destroyed)
throw new Error('Hash instance has been destroyed');
if (checkFinished && instance.finished)
throw new Error('Hash#digest() has already been called');
}
exports.exists = exists;
function output(out, instance) {
bytes(out);
const min = instance.outputLen;
if (out.length < min) {
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
}
}
exports.output = output;
const assert = { number, bool, bytes, hash, exists, output };
exports.default = assert;
//# sourceMappingURL=_assert.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"_assert.js","sourceRoot":"","sources":["src/_assert.ts"],"names":[],"mappings":";;;AAAA,SAAS,MAAM,CAAC,CAAS;IACvB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,EAAE,CAAC,CAAC;AACzF,CAAC;AAqCQ,wBAAM;AAnCf,SAAS,IAAI,CAAC,CAAU;IACtB,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC;AAiCgB,oBAAI;AA/BrB,SAAS,KAAK,CAAC,CAAyB,EAAE,GAAG,OAAiB;IAC5D,IAAI,CAAC,CAAC,CAAC,YAAY,UAAU,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACvE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,iCAAiC,OAAO,mBAAmB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AAC3F,CAAC;AA2BsB,sBAAK;AAnB5B,SAAS,IAAI,CAAC,IAAU;IACtB,IAAI,OAAO,IAAI,KAAK,UAAU,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU;QACjE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACxB,CAAC;AAc6B,oBAAI;AAZlC,SAAS,MAAM,CAAC,QAAa,EAAE,aAAa,GAAG,IAAI;IACjD,IAAI,QAAQ,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAC5E,IAAI,aAAa,IAAI,QAAQ,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;AACnG,CAAC;AASmC,wBAAM;AAR1C,SAAS,MAAM,CAAC,GAAQ,EAAE,QAAa;IACrC,KAAK,CAAC,GAAG,CAAC,CAAC;IACX,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC;IAC/B,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE;QACpB,MAAM,IAAI,KAAK,CAAC,yDAAyD,GAAG,EAAE,CAAC,CAAC;KACjF;AACH,CAAC;AAE2C,wBAAM;AAClD,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC7D,kBAAe,MAAM,CAAC"}
+58
View File
@@ -0,0 +1,58 @@
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
import * as u from './utils.js';
export declare function hsalsa(c: Uint32Array, key: Uint8Array, nonce: Uint8Array): Uint8Array;
export declare function hchacha(c: Uint32Array, key: Uint8Array, nonce: Uint8Array): Uint8Array;
/**
* salsa20, 12-byte nonce.
*/
export declare const salsa20: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* xsalsa20, 24-byte nonce.
*/
export declare const xsalsa20: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* chacha20 non-RFC, original version by djb. 8-byte nonce, 8-byte counter.
*/
export declare const chacha20orig: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* chacha20 RFC 8439 (IETF / TLS). 12-byte nonce, 4-byte counter.
*/
export declare const chacha20: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* xchacha20 eXtended-nonce. https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
*/
export declare const xchacha20: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* 8-round chacha from the original paper.
*/
export declare const chacha8: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* 12-round chacha from the original paper.
*/
export declare const chacha12: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
export declare function poly1305(msg: Uint8Array, key: Uint8Array): Uint8Array;
/**
* xsalsa20-poly1305 eXtended-nonce (24 bytes) salsa.
*/
export declare function xsalsa20poly1305(key: Uint8Array, nonce: Uint8Array): {
encrypt: (plaintext: Uint8Array) => Uint8Array;
decrypt: (ciphertext: Uint8Array) => Uint8Array;
};
/**
* Alias to xsalsa20-poly1305
*/
export declare function secretbox(key: Uint8Array, nonce: Uint8Array): {
seal: (plaintext: Uint8Array) => Uint8Array;
open: (ciphertext: Uint8Array) => Uint8Array;
};
export declare const _poly1305_aead: (fn: typeof chacha20) => (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => u.Cipher;
/**
* chacha20-poly1305 12-byte-nonce chacha.
*/
export declare const chacha20poly1305: (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => u.Cipher;
/**
* xchacha20-poly1305 eXtended-nonce (24 bytes) chacha.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
export declare const xchacha20poly1305: (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => u.Cipher;
//# sourceMappingURL=_micro.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"_micro.d.ts","sourceRoot":"","sources":["src/_micro.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAMvE,OAAO,KAAK,CAAC,MAAM,YAAY,CAAC;AA8EhC,wBAAgB,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAYrF;AAsBD,wBAAgB,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAYtF;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,yHAAsD,CAAC;AAE3E;;GAEG;AACH,eAAO,MAAM,QAAQ,yHAKnB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY,yHAAuE,CAAC;AACjG;;GAEG;AACH,eAAO,MAAM,QAAQ,yHAKnB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,yHAMpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,OAAO,yHAKlB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,QAAQ,yHAKnB,CAAC;AAKH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,GAAG,UAAU,CAcrE;AA4BD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;yBAI1C,UAAU;0BAST,UAAU;EAUnC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;;;EAK3D;AAED,eAAO,MAAM,cAAc,OACpB,eAAe,WACd,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,EAAE,MAwBzD,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,gBAAgB,QA7BrB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,EAAE,MA6BJ,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAnCtB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,EAAE,MAmCF,CAAC"}
+290
View File
@@ -0,0 +1,290 @@
"use strict";
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
Object.defineProperty(exports, "__esModule", { value: true });
exports.xchacha20poly1305 = exports.chacha20poly1305 = exports._poly1305_aead = exports.secretbox = exports.xsalsa20poly1305 = exports.poly1305 = exports.chacha12 = exports.chacha8 = exports.xchacha20 = exports.chacha20 = exports.chacha20orig = exports.xsalsa20 = exports.salsa20 = exports.hchacha = exports.hsalsa = void 0;
// micro-noble-ciphers: more auditable, but slower version of salsa20, chacha & poly1305.
// Implements the same algorithms that are present in other files,
// but without unrolled loops (https://en.wikipedia.org/wiki/Loop_unrolling).
const u = require("./utils.js");
const _salsa_js_1 = require("./_salsa.js");
// Utils
function hexToNumber(hex) {
if (typeof hex !== 'string')
throw new Error('hex string expected, got ' + typeof hex);
// Big Endian
return BigInt(hex === '' ? '0' : `0x${hex}`);
}
function bytesToNumberLE(bytes) {
return hexToNumber(u.bytesToHex(Uint8Array.from(bytes).reverse()));
}
function numberToBytesLE(n, len) {
return u.hexToBytes(n.toString(16).padStart(len * 2, '0')).reverse();
}
const rotl = (a, b) => (a << b) | (a >>> (32 - b));
// /Utils
function salsaQR(x, a, b, c, d) {
x[b] ^= rotl((x[a] + x[d]) | 0, 7);
x[c] ^= rotl((x[b] + x[a]) | 0, 9);
x[d] ^= rotl((x[c] + x[b]) | 0, 13);
x[a] ^= rotl((x[d] + x[c]) | 0, 18);
}
// prettier-ignore
function chachaQR(x, a, b, c, d) {
x[a] = (x[a] + x[b]) | 0;
x[d] = rotl(x[d] ^ x[a], 16);
x[c] = (x[c] + x[d]) | 0;
x[b] = rotl(x[b] ^ x[c], 12);
x[a] = (x[a] + x[b]) | 0;
x[d] = rotl(x[d] ^ x[a], 8);
x[c] = (x[c] + x[d]) | 0;
x[b] = rotl(x[b] ^ x[c], 7);
}
function salsaRound(x, rounds = 20) {
for (let i = 0; i < rounds; i += 2) {
salsaQR(x, 0, 4, 8, 12);
salsaQR(x, 5, 9, 13, 1);
salsaQR(x, 10, 14, 2, 6);
salsaQR(x, 15, 3, 7, 11);
salsaQR(x, 0, 1, 2, 3);
salsaQR(x, 5, 6, 7, 4);
salsaQR(x, 10, 11, 8, 9);
salsaQR(x, 15, 12, 13, 14);
}
}
function chachaRound(x, rounds = 20) {
for (let i = 0; i < rounds; i += 2) {
chachaQR(x, 0, 4, 8, 12);
chachaQR(x, 1, 5, 9, 13);
chachaQR(x, 2, 6, 10, 14);
chachaQR(x, 3, 7, 11, 15);
chachaQR(x, 0, 5, 10, 15);
chachaQR(x, 1, 6, 11, 12);
chachaQR(x, 2, 7, 8, 13);
chachaQR(x, 3, 4, 9, 14);
}
}
function salsaCore(c, k, n, out, cnt, rounds = 20) {
// prettier-ignore
const y = new Uint32Array([
c[0], k[0], k[1], k[2],
k[3], c[1], n[0], n[1],
cnt, 0, c[2], k[4],
k[5], k[6], k[7], c[3], // Key Key Key "te k"
]);
const x = y.slice();
salsaRound(x, rounds);
for (let i = 0; i < 16; i++)
out[i] = (y[i] + x[i]) | 0;
}
function hsalsa(c, key, nonce) {
const k = u.u32(key);
const i = u.u32(nonce);
// prettier-ignore
const x = new Uint32Array([
c[0], k[0], k[1], k[2],
k[3], c[1], i[0], i[1],
i[2], i[3], c[2], k[4],
k[5], k[6], k[7], c[3]
]);
salsaRound(x);
return u.u8(new Uint32Array([x[0], x[5], x[10], x[15], x[6], x[7], x[8], x[9]]));
}
exports.hsalsa = hsalsa;
function chachaCore(c, k, n, out, cnt, rounds = 20) {
// prettier-ignore
const y = new Uint32Array([
c[0], c[1], c[2], c[3],
k[0], k[1], k[2], k[3],
k[4], k[5], k[6], k[7],
cnt, n[0], n[1], n[2], // Counter Counter Nonce Nonce
]);
const x = y.slice();
chachaRound(x, rounds);
for (let i = 0; i < 16; i++)
out[i] = (y[i] + x[i]) | 0;
}
function hchacha(c, key, nonce) {
const k = u.u32(key);
const i = u.u32(nonce);
// prettier-ignore
const x = new Uint32Array([
c[0], c[1], c[2], c[3],
k[0], k[1], k[2], k[3],
k[4], k[5], k[6], k[7],
i[0], i[1], i[2], i[3],
]);
chachaRound(x);
return u.u8(new Uint32Array([x[0], x[1], x[2], x[3], x[12], x[13], x[14], x[15]]));
}
exports.hchacha = hchacha;
/**
* salsa20, 12-byte nonce.
*/
exports.salsa20 = (0, _salsa_js_1.salsaBasic)({ core: salsaCore, counterRight: true });
/**
* xsalsa20, 24-byte nonce.
*/
exports.xsalsa20 = (0, _salsa_js_1.salsaBasic)({
core: salsaCore,
counterRight: true,
extendNonceFn: hsalsa,
allow128bitKeys: false,
});
/**
* chacha20 non-RFC, original version by djb. 8-byte nonce, 8-byte counter.
*/
exports.chacha20orig = (0, _salsa_js_1.salsaBasic)({ core: chachaCore, counterRight: false, counterLen: 8 });
/**
* chacha20 RFC 8439 (IETF / TLS). 12-byte nonce, 4-byte counter.
*/
exports.chacha20 = (0, _salsa_js_1.salsaBasic)({
core: chachaCore,
counterRight: false,
counterLen: 4,
allow128bitKeys: false,
});
/**
* xchacha20 eXtended-nonce. https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
*/
exports.xchacha20 = (0, _salsa_js_1.salsaBasic)({
core: chachaCore,
counterRight: false,
counterLen: 8,
extendNonceFn: hchacha,
allow128bitKeys: false,
});
/**
* 8-round chacha from the original paper.
*/
exports.chacha8 = (0, _salsa_js_1.salsaBasic)({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 8,
});
/**
* 12-round chacha from the original paper.
*/
exports.chacha12 = (0, _salsa_js_1.salsaBasic)({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 12,
});
const POW_2_130_5 = 2n ** 130n - 5n;
const POW_2_128_1 = 2n ** (16n * 8n) - 1n;
// Can be speed-up using BigUint64Array, but would be more complicated
function poly1305(msg, key) {
u.ensureBytes(msg);
u.ensureBytes(key);
let acc = 0n;
const r = bytesToNumberLE(key.subarray(0, 16)) & 0x0ffffffc0ffffffc0ffffffc0fffffffn;
const s = bytesToNumberLE(key.subarray(16));
// Process by 16 byte chunks
for (let i = 0; i < msg.length; i += 16) {
const m = msg.subarray(i, i + 16);
const n = bytesToNumberLE(m) | (1n << BigInt(8 * m.length));
acc = ((acc + n) * r) % POW_2_130_5;
}
const res = (acc + s) & POW_2_128_1;
return numberToBytesLE(res, 16);
}
exports.poly1305 = poly1305;
function computeTag(fn, key, nonce, ciphertext, AAD) {
const res = [];
if (AAD) {
res.push(AAD);
const leftover = AAD.length % 16;
if (leftover > 0)
res.push(new Uint8Array(16 - leftover));
}
res.push(ciphertext);
const leftover = ciphertext.length % 16;
if (leftover > 0)
res.push(new Uint8Array(16 - leftover));
// Lengths
const num = new Uint8Array(16);
const view = u.createView(num);
u.setBigUint64(view, 0, BigInt(AAD ? AAD.length : 0), true);
u.setBigUint64(view, 8, BigInt(ciphertext.length), true);
res.push(num);
const authKey = fn(key, nonce, new Uint8Array(32));
return poly1305(u.concatBytes(...res), authKey);
}
/**
* xsalsa20-poly1305 eXtended-nonce (24 bytes) salsa.
*/
function xsalsa20poly1305(key, nonce) {
u.ensureBytes(key);
u.ensureBytes(nonce);
return {
encrypt: (plaintext) => {
u.ensureBytes(plaintext);
const m = u.concatBytes(new Uint8Array(32), plaintext);
const c = (0, exports.xsalsa20)(key, nonce, m);
const authKey = c.subarray(0, 32);
const data = c.subarray(32);
const tag = poly1305(data, authKey);
return u.concatBytes(tag, data);
},
decrypt: (ciphertext) => {
u.ensureBytes(ciphertext);
if (ciphertext.length < 16)
throw new Error('encrypted data must be at least 16 bytes');
const c = u.concatBytes(new Uint8Array(16), ciphertext);
const authKey = (0, exports.xsalsa20)(key, nonce, new Uint8Array(32));
const tag = poly1305(c.subarray(32), authKey);
if (!u.equalBytes(c.subarray(16, 32), tag))
throw new Error('invalid poly1305 tag');
return (0, exports.xsalsa20)(key, nonce, c).subarray(32);
},
};
}
exports.xsalsa20poly1305 = xsalsa20poly1305;
/**
* Alias to xsalsa20-poly1305
*/
function secretbox(key, nonce) {
u.ensureBytes(key);
u.ensureBytes(nonce);
const xs = xsalsa20poly1305(key, nonce);
return { seal: xs.encrypt, open: xs.decrypt };
}
exports.secretbox = secretbox;
const _poly1305_aead = (fn) => (key, nonce, AAD) => {
const tagLength = 16;
const keyLength = 32;
u.ensureBytes(key, keyLength);
u.ensureBytes(nonce);
return {
tagLength,
encrypt: (plaintext) => {
u.ensureBytes(plaintext);
const res = fn(key, nonce, plaintext, undefined, 1);
const tag = computeTag(fn, key, nonce, res, AAD);
return u.concatBytes(res, tag);
},
decrypt: (ciphertext) => {
u.ensureBytes(ciphertext);
if (ciphertext.length < tagLength)
throw new Error(`encrypted data must be at least ${tagLength} bytes`);
const passedTag = ciphertext.subarray(-tagLength);
const data = ciphertext.subarray(0, -tagLength);
const tag = computeTag(fn, key, nonce, data, AAD);
if (!u.equalBytes(passedTag, tag))
throw new Error('invalid poly1305 tag');
return fn(key, nonce, data, undefined, 1);
},
};
};
exports._poly1305_aead = _poly1305_aead;
/**
* chacha20-poly1305 12-byte-nonce chacha.
*/
exports.chacha20poly1305 = (0, exports._poly1305_aead)(exports.chacha20);
/**
* xchacha20-poly1305 eXtended-nonce (24 bytes) chacha.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
exports.xchacha20poly1305 = (0, exports._poly1305_aead)(exports.xchacha20);
//# sourceMappingURL=_micro.js.map
+1
View File
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
import { Input, Hash } from './utils.js';
export type CHash = ReturnType<typeof wrapConstructorWithKey>;
export declare function wrapConstructorWithKey<H extends Hash<H>>(hashCons: (key: Input) => Hash<H>): {
(msg: Input, key: Input): Uint8Array;
outputLen: number;
blockLen: number;
create(key: Input): Hash<H>;
};
export declare const poly1305: {
(msg: Input, key: Input): Uint8Array;
outputLen: number;
blockLen: number;
create(key: Input): Hash<Hash<unknown>>;
};
//# sourceMappingURL=_poly1305.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"_poly1305.d.ts","sourceRoot":"","sources":["src/_poly1305.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,EAAe,IAAI,EAAE,MAAM,YAAY,CAAC;AAmR/D,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC9D,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC;UACrE,KAAK,OAAO,KAAK,GAAG,UAAU;;;gBAI7B,KAAK;EAE3B;AAED,eAAO,MAAM,QAAQ;UARC,KAAK,OAAO,KAAK,GAAG,UAAU;;;gBAI7B,KAAK;CAI8C,CAAC"}
+268
View File
@@ -0,0 +1,268 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.poly1305 = exports.wrapConstructorWithKey = void 0;
const utils_js_1 = require("./utils.js");
const _assert_js_1 = require("./_assert.js");
// Poly1305 is a fast and parallel secret-key message-authentication code.
// https://cr.yp.to/mac.html, https://cr.yp.to/mac/poly1305-20050329.pdf
// https://datatracker.ietf.org/doc/html/rfc8439
// Based on Public Domain poly1305-donna https://github.com/floodyberry/poly1305-donna
const u8to16 = (a, i) => (a[i++] & 0xff) | ((a[i++] & 0xff) << 8);
class Poly1305 {
constructor(key) {
this.blockLen = 16;
this.outputLen = 16;
this.buffer = new Uint8Array(16);
this.r = new Uint16Array(10);
this.h = new Uint16Array(10);
this.pad = new Uint16Array(8);
this.pos = 0;
this.finished = false;
key = (0, utils_js_1.toBytes)(key);
(0, utils_js_1.ensureBytes)(key, 32);
const t0 = u8to16(key, 0);
const t1 = u8to16(key, 2);
const t2 = u8to16(key, 4);
const t3 = u8to16(key, 6);
const t4 = u8to16(key, 8);
const t5 = u8to16(key, 10);
const t6 = u8to16(key, 12);
const t7 = u8to16(key, 14);
// https://github.com/floodyberry/poly1305-donna/blob/e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781/poly1305-donna-16.h#L47
this.r[0] = t0 & 0x1fff;
this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03;
this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff;
this.r[5] = (t4 >>> 1) & 0x1ffe;
this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81;
this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
this.r[9] = (t7 >>> 5) & 0x007f;
for (let i = 0; i < 8; i++)
this.pad[i] = u8to16(key, 16 + 2 * i);
}
process(data, offset, isLast = false) {
const hibit = isLast ? 0 : 1 << 11;
const { h, r } = this;
const r0 = r[0];
const r1 = r[1];
const r2 = r[2];
const r3 = r[3];
const r4 = r[4];
const r5 = r[5];
const r6 = r[6];
const r7 = r[7];
const r8 = r[8];
const r9 = r[9];
const t0 = u8to16(data, offset + 0);
const t1 = u8to16(data, offset + 2);
const t2 = u8to16(data, offset + 4);
const t3 = u8to16(data, offset + 6);
const t4 = u8to16(data, offset + 8);
const t5 = u8to16(data, offset + 10);
const t6 = u8to16(data, offset + 12);
const t7 = u8to16(data, offset + 14);
let h0 = h[0] + (t0 & 0x1fff);
let h1 = h[1] + (((t0 >>> 13) | (t1 << 3)) & 0x1fff);
let h2 = h[2] + (((t1 >>> 10) | (t2 << 6)) & 0x1fff);
let h3 = h[3] + (((t2 >>> 7) | (t3 << 9)) & 0x1fff);
let h4 = h[4] + (((t3 >>> 4) | (t4 << 12)) & 0x1fff);
let h5 = h[5] + ((t4 >>> 1) & 0x1fff);
let h6 = h[6] + (((t4 >>> 14) | (t5 << 2)) & 0x1fff);
let h7 = h[7] + (((t5 >>> 11) | (t6 << 5)) & 0x1fff);
let h8 = h[8] + (((t6 >>> 8) | (t7 << 8)) & 0x1fff);
let h9 = h[9] + ((t7 >>> 5) | hibit);
let c = 0;
let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
c = d0 >>> 13;
d0 &= 0x1fff;
d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
c += d0 >>> 13;
d0 &= 0x1fff;
let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
c = d1 >>> 13;
d1 &= 0x1fff;
d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
c += d1 >>> 13;
d1 &= 0x1fff;
let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
c = d2 >>> 13;
d2 &= 0x1fff;
d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
c += d2 >>> 13;
d2 &= 0x1fff;
let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
c = d3 >>> 13;
d3 &= 0x1fff;
d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
c += d3 >>> 13;
d3 &= 0x1fff;
let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
c = d4 >>> 13;
d4 &= 0x1fff;
d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
c += d4 >>> 13;
d4 &= 0x1fff;
let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
c = d5 >>> 13;
d5 &= 0x1fff;
d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
c += d5 >>> 13;
d5 &= 0x1fff;
let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
c = d6 >>> 13;
d6 &= 0x1fff;
d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
c += d6 >>> 13;
d6 &= 0x1fff;
let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
c = d7 >>> 13;
d7 &= 0x1fff;
d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
c += d7 >>> 13;
d7 &= 0x1fff;
let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
c = d8 >>> 13;
d8 &= 0x1fff;
d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
c += d8 >>> 13;
d8 &= 0x1fff;
let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
c = d9 >>> 13;
d9 &= 0x1fff;
d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
c += d9 >>> 13;
d9 &= 0x1fff;
c = ((c << 2) + c) | 0;
c = (c + d0) | 0;
d0 = c & 0x1fff;
c = c >>> 13;
d1 += c;
h[0] = d0;
h[1] = d1;
h[2] = d2;
h[3] = d3;
h[4] = d4;
h[5] = d5;
h[6] = d6;
h[7] = d7;
h[8] = d8;
h[9] = d9;
}
finalize() {
const { h, pad } = this;
const g = new Uint16Array(10);
let c = h[1] >>> 13;
h[1] &= 0x1fff;
for (let i = 2; i < 10; i++) {
h[i] += c;
c = h[i] >>> 13;
h[i] &= 0x1fff;
}
h[0] += c * 5;
c = h[0] >>> 13;
h[0] &= 0x1fff;
h[1] += c;
c = h[1] >>> 13;
h[1] &= 0x1fff;
h[2] += c;
g[0] = h[0] + 5;
c = g[0] >>> 13;
g[0] &= 0x1fff;
for (let i = 1; i < 10; i++) {
g[i] = h[i] + c;
c = g[i] >>> 13;
g[i] &= 0x1fff;
}
g[9] -= 1 << 13;
let mask = (c ^ 1) - 1;
for (let i = 0; i < 10; i++)
g[i] &= mask;
mask = ~mask;
for (let i = 0; i < 10; i++)
h[i] = (h[i] & mask) | g[i];
h[0] = (h[0] | (h[1] << 13)) & 0xffff;
h[1] = ((h[1] >>> 3) | (h[2] << 10)) & 0xffff;
h[2] = ((h[2] >>> 6) | (h[3] << 7)) & 0xffff;
h[3] = ((h[3] >>> 9) | (h[4] << 4)) & 0xffff;
h[4] = ((h[4] >>> 12) | (h[5] << 1) | (h[6] << 14)) & 0xffff;
h[5] = ((h[6] >>> 2) | (h[7] << 11)) & 0xffff;
h[6] = ((h[7] >>> 5) | (h[8] << 8)) & 0xffff;
h[7] = ((h[8] >>> 8) | (h[9] << 5)) & 0xffff;
let f = h[0] + pad[0];
h[0] = f & 0xffff;
for (let i = 1; i < 8; i++) {
f = (((h[i] + pad[i]) | 0) + (f >>> 16)) | 0;
h[i] = f & 0xffff;
}
}
update(data) {
_assert_js_1.default.exists(this);
const { buffer, blockLen } = this;
data = (0, utils_js_1.toBytes)(data);
const len = data.length;
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);
// Fast path: we have at least one block in input
if (take === blockLen) {
for (; blockLen <= len - pos; pos += blockLen)
this.process(data, pos);
continue;
}
buffer.set(data.subarray(pos, pos + take), this.pos);
this.pos += take;
pos += take;
if (this.pos === blockLen) {
this.process(buffer, 0, false);
this.pos = 0;
}
}
return this;
}
destroy() {
this.h.fill(0);
this.r.fill(0);
this.buffer.fill(0);
this.pad.fill(0);
}
digestInto(out) {
_assert_js_1.default.exists(this);
_assert_js_1.default.output(out, this);
this.finished = true;
const { buffer, h } = this;
let { pos } = this;
if (pos) {
buffer[pos++] = 1;
// buffer.subarray(pos).fill(0);
for (; pos < 16; pos++)
buffer[pos] = 0;
this.process(buffer, 0, true);
}
this.finalize();
let opos = 0;
for (let i = 0; i < 8; i++) {
out[opos++] = h[i] >>> 0;
out[opos++] = h[i] >>> 8;
}
return out;
}
digest() {
const { buffer, outputLen } = this;
this.digestInto(buffer);
const res = buffer.slice(0, outputLen);
this.destroy();
return res;
}
}
function wrapConstructorWithKey(hashCons) {
const hashC = (msg, key) => hashCons(key).update((0, utils_js_1.toBytes)(msg)).digest();
const tmp = hashCons(new Uint8Array(32));
hashC.outputLen = tmp.outputLen;
hashC.blockLen = tmp.blockLen;
hashC.create = (key) => hashCons(key);
return hashC;
}
exports.wrapConstructorWithKey = wrapConstructorWithKey;
exports.poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
//# sourceMappingURL=_poly1305.js.map
+1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
export declare function polyval(h: Uint8Array, data: Uint8Array): Uint8Array;
//# sourceMappingURL=_polyval.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"_polyval.d.ts","sourceRoot":"","sources":["src/_polyval.ts"],"names":[],"mappings":"AAyDA,wBAAgB,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,cAiDtD"}
+104
View File
@@ -0,0 +1,104 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.polyval = void 0;
const utils_js_1 = require("./utils.js");
// AES-SIV polyval, little-endian "mirror image" of AES-GCM GHash
// polynomial hash function. Defined in RFC 8452.
// Reverse bits in u32, constant-time, precompute will be faster, but non-constant time
function rev32(x) {
x = ((x & 1431655765) << 1) | ((x >>> 1) & 1431655765);
x = ((x & 858993459) << 2) | ((x >>> 2) & 858993459);
x = ((x & 252645135) << 4) | ((x >>> 4) & 252645135);
x = ((x & 16711935) << 8) | ((x >>> 8) & 16711935);
return (x << 16) | (x >>> 16);
}
// wrapped 32 bit multiplication
const wrapMul = (a, b) => Math.imul(a, b) >>> 0;
// https://timtaubert.de/blog/2017/06/verified-binary-multiplication-for-ghash/
function bmul32(x, y) {
const x0 = x & 286331153;
const x1 = x & 572662306;
const x2 = x & 1145324612;
const x3 = x & 2290649224;
const y0 = y & 286331153;
const y1 = y & 572662306;
const y2 = y & 1145324612;
const y3 = y & 2290649224;
let res = (wrapMul(x0, y0) ^ wrapMul(x1, y3) ^ wrapMul(x2, y2) ^ wrapMul(x3, y1)) & 286331153;
res |= (wrapMul(x0, y1) ^ wrapMul(x1, y0) ^ wrapMul(x2, y3) ^ wrapMul(x3, y2)) & 572662306;
res |= (wrapMul(x0, y2) ^ wrapMul(x1, y1) ^ wrapMul(x2, y0) ^ wrapMul(x3, y3)) & 1145324612;
res |= (wrapMul(x0, y3) ^ wrapMul(x1, y2) ^ wrapMul(x2, y1) ^ wrapMul(x3, y0)) & 2290649224;
return res >>> 0;
}
function mulPart(arr) {
const a = new Uint32Array(18);
a[0] = arr[0];
a[1] = arr[1];
a[2] = arr[2];
a[3] = arr[3];
a[4] = a[0] ^ a[1];
a[5] = a[2] ^ a[3];
a[6] = a[0] ^ a[2];
a[7] = a[1] ^ a[3];
a[8] = a[6] ^ a[7];
a[9] = rev32(arr[0]);
a[10] = rev32(arr[1]);
a[11] = rev32(arr[2]);
a[12] = rev32(arr[3]);
a[13] = a[9] ^ a[10];
a[14] = a[11] ^ a[12];
a[15] = a[9] ^ a[11];
a[16] = a[10] ^ a[12];
a[17] = a[15] ^ a[16];
return a;
}
function polyval(h, data) {
(0, utils_js_1.ensureBytes)(h);
(0, utils_js_1.ensureBytes)(data);
const s = new Uint32Array(4);
// Precompute for multiplication
const a = mulPart((0, utils_js_1.u32)(h));
if (data.length % 16)
throw new Error('polyval: data must be padded to 16 bytes');
const data32 = (0, utils_js_1.u32)(data);
for (let i = 0; i < data32.length; i += 4) {
// Xor
s[0] ^= data32[i + 0];
s[1] ^= data32[i + 1];
s[2] ^= data32[i + 2];
s[3] ^= data32[i + 3];
// Dot via Karatsuba multiplication, based on MIT-licensed
// https://bearssl.org/gitweb/?p=BearSSL;a=blob;f=src/hash/ghash_ctmul32.c;hb=4b6046412
const b = mulPart(s);
const c = new Uint32Array(18);
for (let i = 0; i < 18; i++)
c[i] = bmul32(a[i], b[i]);
c[4] ^= c[0] ^ c[1];
c[5] ^= c[2] ^ c[3];
c[8] ^= c[6] ^ c[7];
c[13] ^= c[9] ^ c[10];
c[14] ^= c[11] ^ c[12];
c[17] ^= c[15] ^ c[16];
const zw = new Uint32Array(8);
zw[0] = c[0];
zw[1] = c[4] ^ (rev32(c[9]) >>> 1);
zw[2] = c[1] ^ c[0] ^ c[2] ^ c[6] ^ (rev32(c[13]) >>> 1);
zw[3] = c[4] ^ c[5] ^ c[8] ^ (rev32(c[10] ^ c[9] ^ c[11] ^ c[15]) >>> 1);
zw[4] = c[2] ^ c[1] ^ c[3] ^ c[7] ^ (rev32(c[13] ^ c[14] ^ c[17]) >>> 1);
zw[5] = c[5] ^ (rev32(c[11] ^ c[10] ^ c[12] ^ c[16]) >>> 1);
zw[6] = c[3] ^ (rev32(c[14]) >>> 1);
zw[7] = rev32(c[12]) >>> 1;
for (let i = 0; i < 4; i++) {
const lw = zw[i];
zw[i + 4] ^= lw ^ (lw >>> 1) ^ (lw >>> 2) ^ (lw >>> 7);
zw[i + 3] ^= (lw << 31) ^ (lw << 30) ^ (lw << 25);
}
s[0] = zw[4];
s[1] = zw[5];
s[2] = zw[6];
s[3] = zw[7];
}
return (0, utils_js_1.u8)(s);
}
exports.polyval = polyval;
//# sourceMappingURL=_polyval.js.map
+1
View File
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
export type SalsaOpts = {
core: (c: Uint32Array, key: Uint32Array, nonce: Uint32Array, out: Uint32Array, counter: number, rounds?: number) => void;
rounds?: number;
counterRight?: boolean;
counterLen?: number;
blockLen?: number;
allow128bitKeys?: boolean;
extendNonceFn?: (c: Uint32Array, key: Uint8Array, src: Uint8Array, dst: Uint8Array) => Uint8Array;
};
export declare const salsaBasic: (opts: SalsaOpts) => (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array, counter?: number) => Uint8Array;
//# sourceMappingURL=_salsa.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"_salsa.d.ts","sourceRoot":"","sources":["src/_salsa.ts"],"names":[],"mappings":"AA0DA,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,CACJ,CAAC,EAAE,WAAW,EACd,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,KACZ,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,KAAK,UAAU,CAAC;CACnG,CAAC;AAKF,eAAO,MAAM,UAAU,SAAU,SAAS,WAcjC,UAAU,SACR,UAAU,QACX,UAAU,WACP,UAAU,uBAElB,UA6EJ,CAAC"}
+158
View File
@@ -0,0 +1,158 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.salsaBasic = void 0;
// Basic utils for salsa-like ciphers
// Check out _micro.ts for descriptive documentation.
const _assert_js_1 = require("./_assert.js");
const utils_js_1 = require("./utils.js");
/*
RFC8439 requires multi-step cipher stream, where
authKey starts with counter: 0, actual msg with counter: 1.
For this, we need a way to re-use nonce / counter:
const counter = new Uint8Array(4);
chacha(..., counter, ...); // counter is now 1
chacha(..., counter, ...); // counter is now 2
This is complicated:
- Original papers don't allow mutating counters
- Counter overflow is undefined: https://mailarchive.ietf.org/arch/msg/cfrg/gsOnTJzcbgG6OqD8Sc0GO5aR_tU/
- 3rd-party library stablelib implementation uses an approach where you can provide
nonce and counter instead of just nonce - and it will re-use it
- We could have did something similar, but ChaCha has different counter position
(counter | nonce), which is not composable with XChaCha, because full counter
is (nonce16 | counter | nonce16). Stablelib doesn't support in-place counter for XChaCha.
- We could separate nonce & counter and provide separate API for counter re-use, but
there are different counter sizes depending on an algorithm.
- Salsa & ChaCha also differ in structures of key / sigma:
salsa: c0 | k(4) | c1 | nonce(2) | ctr(2) | c2 | k(4) | c4
chacha: c(4) | k(8) | ctr(1) | nonce(3)
chachaDJB: c(4) | k(8) | ctr(2) | nonce(2)
- Creating function such as `setSalsaState(key, nonce, sigma, data)` won't work,
because we can't re-use counter array
- 32-bit nonce is `2 ** 32 * 64` = 256GB with 32-bit counter
- JS does not allow UintArrays bigger than 4GB, so supporting 64-bit counters doesn't matter
Structure is as following:
key=16 -> sigma16, k=key|key
key=32 -> sigma32, k=key
nonces:
salsa20: 8 (8-byte counter)
chacha20djb: 8 (8-byte counter)
chacha20tls: 12 (4-byte counter)
xsalsa: 24 (16 -> hsalsa, 8 -> old nonce)
xchacha: 24 (16 -> hchacha, 8 -> old nonce)
https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha#appendix-A.2
Use the subkey and remaining 8 byte nonce with ChaCha20 as normal
(prefixed by 4 NUL bytes, since [RFC8439] specifies a 12-byte nonce).
*/
const sigma16 = (0, utils_js_1.utf8ToBytes)('expand 16-byte k');
const sigma32 = (0, utils_js_1.utf8ToBytes)('expand 32-byte k');
const sigma16_32 = (0, utils_js_1.u32)(sigma16);
const sigma32_32 = (0, utils_js_1.u32)(sigma32);
// Is byte array aligned to 4 byte offset (u32)?
const isAligned32 = (b) => !(b.byteOffset % 4);
const salsaBasic = (opts) => {
const { core, rounds, counterRight, counterLen, allow128bitKeys, extendNonceFn, blockLen } = (0, utils_js_1.checkOpts)({ rounds: 20, counterRight: false, counterLen: 8, allow128bitKeys: true, blockLen: 64 }, opts);
_assert_js_1.default.number(counterLen);
_assert_js_1.default.number(rounds);
_assert_js_1.default.number(blockLen);
_assert_js_1.default.bool(counterRight);
_assert_js_1.default.bool(allow128bitKeys);
const blockLen32 = blockLen / 4;
if (blockLen % 4 !== 0)
throw new Error('Salsa/ChaCha: blockLen must be aligned to 4 bytes');
return (key, nonce, data, output, counter = 0) => {
_assert_js_1.default.bytes(key);
_assert_js_1.default.bytes(nonce);
_assert_js_1.default.bytes(data);
if (!output)
output = new Uint8Array(data.length);
_assert_js_1.default.bytes(output);
_assert_js_1.default.number(counter);
// > new Uint32Array([2**32])
// Uint32Array(1) [ 0 ]
// > new Uint32Array([2**32-1])
// Uint32Array(1) [ 4294967295 ]
if (counter < 0 || counter >= 2 ** 32 - 1)
throw new Error('Salsa/ChaCha: counter overflow');
if (output.length < data.length) {
throw new Error(`Salsa/ChaCha: output (${output.length}) is shorter than data (${data.length})`);
}
const toClean = [];
let k, sigma;
// Handle 128 byte keys
if (key.length === 32) {
k = key;
sigma = sigma32_32;
}
else if (key.length === 16 && allow128bitKeys) {
k = new Uint8Array(32);
k.set(key);
k.set(key, 16);
sigma = sigma16_32;
toClean.push(k);
}
else
throw new Error(`Salsa/ChaCha: invalid 32-byte key, got length=${key.length}`);
// Handle extended nonce (HChaCha/HSalsa)
if (extendNonceFn) {
if (nonce.length <= 16)
throw new Error(`Salsa/ChaCha: extended nonce must be bigger than 16 bytes`);
k = extendNonceFn(sigma, k, nonce.subarray(0, 16), new Uint8Array(32));
toClean.push(k);
nonce = nonce.subarray(16);
}
// Handle nonce counter
const nonceLen = 16 - counterLen;
if (nonce.length !== nonceLen)
throw new Error(`Salsa/ChaCha: nonce must be ${nonceLen} or 16 bytes`);
// Pad counter when nonce is 64 bit
if (nonceLen !== 12) {
const nc = new Uint8Array(12);
nc.set(nonce, counterRight ? 0 : 12 - nonce.length);
toClean.push((nonce = nc));
}
// Counter positions
const block = new Uint8Array(blockLen);
// Cast to Uint32Array for speed
const b32 = (0, utils_js_1.u32)(block);
const k32 = (0, utils_js_1.u32)(k);
const n32 = (0, utils_js_1.u32)(nonce);
// Make sure that buffers aligned to 4 bytes
const d32 = isAligned32(data) && (0, utils_js_1.u32)(data);
const o32 = isAligned32(output) && (0, utils_js_1.u32)(output);
toClean.push(b32);
const len = data.length;
for (let pos = 0, ctr = counter; pos < len; ctr++) {
core(sigma, k32, n32, b32, ctr, rounds);
if (ctr >= 2 ** 32 - 1)
throw new Error('Salsa/ChaCha: counter overflow');
const take = Math.min(blockLen, len - pos);
// full block && aligned to 4 bytes
if (take === blockLen && o32 && d32) {
const pos32 = pos / 4;
if (pos % 4 !== 0)
throw new Error('Salsa/ChaCha: invalid block position');
for (let j = 0; j < blockLen32; j++)
o32[pos32 + j] = d32[pos32 + j] ^ b32[j];
pos += blockLen;
continue;
}
for (let j = 0; j < take; j++)
output[pos + j] = data[pos + j] ^ block[j];
pos += take;
}
for (let i = 0; i < toClean.length; i++)
toClean[i].fill(0);
return output;
};
};
exports.salsaBasic = salsaBasic;
//# sourceMappingURL=_salsa.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"_salsa.js","sourceRoot":"","sources":["src/_salsa.ts"],"names":[],"mappings":";;;AAAA,qCAAqC;AACrC,qDAAqD;AACrD,6CAAkC;AAClC,yCAAyD;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8CE;AAEF,MAAM,OAAO,GAAG,IAAA,sBAAW,EAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,OAAO,GAAG,IAAA,sBAAW,EAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,UAAU,GAAG,IAAA,cAAG,EAAC,OAAO,CAAC,CAAC;AAChC,MAAM,UAAU,GAAG,IAAA,cAAG,EAAC,OAAO,CAAC,CAAC;AAmBhC,gDAAgD;AAChD,MAAM,WAAW,GAAG,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;AAEpD,MAAM,UAAU,GAAG,CAAC,IAAe,EAAE,EAAE;IAC5C,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,QAAQ,EAAE,GACxF,IAAA,oBAAS,EACP,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EACvF,IAAI,CACL,CAAC;IACJ,oBAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1B,oBAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtB,oBAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACxB,oBAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC1B,oBAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC7B,MAAM,UAAU,GAAG,QAAQ,GAAG,CAAC,CAAC;IAChC,IAAI,QAAQ,GAAG,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAC7F,OAAO,CACL,GAAe,EACf,KAAiB,EACjB,IAAgB,EAChB,MAAmB,EACnB,OAAO,GAAG,CAAC,EACC,EAAE;QACd,oBAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClB,oBAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACpB,oBAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC,MAAM;YAAE,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAClD,oBAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrB,oBAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACvB,6BAA6B;QAC7B,uBAAuB;QACvB,+BAA+B;QAC/B,gCAAgC;QAChC,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QAC7F,IAAI,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE;YAC/B,MAAM,IAAI,KAAK,CACb,yBAAyB,MAAM,CAAC,MAAM,2BAA2B,IAAI,CAAC,MAAM,GAAG,CAChF,CAAC;SACH;QACD,MAAM,OAAO,GAAG,EAAE,CAAC;QACnB,IAAI,CAAC,EAAE,KAAK,CAAC;QACb,uBAAuB;QACvB,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,EAAE;YACrB,CAAC,GAAG,GAAG,CAAC;YACR,KAAK,GAAG,UAAU,CAAC;SACpB;aAAM,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,IAAI,eAAe,EAAE;YAC/C,CAAC,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;YACvB,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACX,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACf,KAAK,GAAG,UAAU,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SACjB;;YAAM,MAAM,IAAI,KAAK,CAAC,iDAAiD,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACtF,yCAAyC;QACzC,IAAI,aAAa,EAAE;YACjB,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE;gBACpB,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;YAC/E,CAAC,GAAG,aAAa,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;YACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChB,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;SAC5B;QACD,uBAAuB;QACvB,MAAM,QAAQ,GAAG,EAAE,GAAG,UAAU,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ;YAC3B,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,cAAc,CAAC,CAAC;QACzE,mCAAmC;QACnC,IAAI,QAAQ,KAAK,EAAE,EAAE;YACnB,MAAM,EAAE,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;YAC9B,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC;SAC5B;QACD,oBAAoB;QACpB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC;QACvC,gCAAgC;QAChC,MAAM,GAAG,GAAG,IAAA,cAAG,EAAC,KAAK,CAAC,CAAC;QACvB,MAAM,GAAG,GAAG,IAAA,cAAG,EAAC,CAAC,CAAC,CAAC;QACnB,MAAM,GAAG,GAAG,IAAA,cAAG,EAAC,KAAK,CAAC,CAAC;QACvB,4CAA4C;QAC5C,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,IAAA,cAAG,EAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,IAAA,cAAG,EAAC,MAAM,CAAC,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;QACxB,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,OAAO,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,EAAE;YACjD,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;YACxC,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;YAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC;YAC3C,mCAAmC;YACnC,IAAI,IAAI,KAAK,QAAQ,IAAI,GAAG,IAAI,GAAG,EAAE;gBACnC,MAAM,KAAK,GAAG,GAAG,GAAG,CAAC,CAAC;gBACtB,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC;oBAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;gBAC3E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE;oBAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC9E,GAAG,IAAI,QAAQ,CAAC;gBAChB,SAAS;aACV;YACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE;gBAAE,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1E,GAAG,IAAI,IAAI,CAAC;SACb;QACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE;YAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5D,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC,CAAC;AAhGW,QAAA,UAAU,cAgGrB"}
+53
View File
@@ -0,0 +1,53 @@
import { Cipher } from './utils.js';
/**
* hchacha helper method, used primarily in xchacha, to hash
* key and nonce into key' and nonce'.
* Same as chachaCore, but there doesn't seem to be a way to move the block
* out without 25% performance hit.
*/
export declare function hchacha(c: Uint32Array, key: Uint8Array, src: Uint8Array, out: Uint8Array): Uint8Array;
/**
* Original, non-RFC chacha20 from DJB. 8-byte nonce, 8-byte counter.
*/
export declare const chacha20orig: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* ChaCha stream cipher. Conforms to RFC 8439 (IETF, TLS). 12-byte nonce, 4-byte counter.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
export declare const chacha20: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* XChaCha eXtended-nonce ChaCha. 24-byte nonce.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
*/
export declare const xchacha20: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* Reduced 8-round chacha, described in original paper.
*/
export declare const chacha8: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* Reduced 12-round chacha, described in original paper.
*/
export declare const chacha12: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* AEAD algorithm from RFC 8439.
* Salsa20 and chacha (RFC 8439) use poly1305 differently.
* We could have composed them similar to:
* https://github.com/paulmillr/scure-base/blob/b266c73dde977b1dd7ef40ef7a23cc15aab526b3/index.ts#L250
* But it's hard because of authKey:
* In salsa20, authKey changes position in salsa stream.
* In chacha, authKey can't be computed inside computeTag, it modifies the counter.
*/
export declare const _poly1305_aead: (xorStream: typeof chacha20) => (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => Cipher;
/**
* ChaCha20-Poly1305 from RFC 8439.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
export declare const chacha20poly1305: (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => Cipher;
/**
* XChaCha20-Poly1305 extended-nonce chacha.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
export declare const xchacha20poly1305: (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => Cipher;
//# sourceMappingURL=chacha.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"chacha.d.ts","sourceRoot":"","sources":["src/chacha.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA0D,MAAM,YAAY,CAAC;AAgF5F;;;;;GAKG;AAEH,wBAAgB,OAAO,CACrB,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,GAChE,UAAU,CA0DZ;AACD;;GAEG;AACH,eAAO,MAAM,YAAY,yHAIvB,CAAC;AACH;;;GAGG;AACH,eAAO,MAAM,QAAQ,yHAKnB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,SAAS,yHAMpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,OAAO,yHAKlB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,QAAQ,yHAKnB,CAAC;AA+BH;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,cACb,eAAe,WACrB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,MAqCvD,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QA3CrB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,MA2Cc,CAAC;AACzE;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,QAjDtB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,MAiDgB,CAAC"}
+334
View File
@@ -0,0 +1,334 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.xchacha20poly1305 = exports.chacha20poly1305 = exports._poly1305_aead = exports.chacha12 = exports.chacha8 = exports.xchacha20 = exports.chacha20 = exports.chacha20orig = exports.hchacha = void 0;
const utils_js_1 = require("./utils.js");
const _poly1305_js_1 = require("./_poly1305.js");
const _salsa_js_1 = require("./_salsa.js");
// ChaCha20 stream cipher was released in 2008. ChaCha aims to increase
// the diffusion per round, but had slightly less cryptanalysis.
// https://cr.yp.to/chacha.html, http://cr.yp.to/chacha/chacha-20080128.pdf
// Left rotate for uint32
const rotl = (a, b) => (a << b) | (a >>> (32 - b));
/**
* ChaCha core function.
*/
// prettier-ignore
function chachaCore(c, k, n, out, cnt, rounds = 20) {
let y00 = c[0], y01 = c[1], y02 = c[2], y03 = c[3]; // "expa" "nd 3" "2-by" "te k"
let y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3]; // Key Key Key Key
let y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7]; // Key Key Key Key
let y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; // Counter Counter Nonce Nonce
// Save state to temporary variables
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
// Main loop
for (let i = 0; i < rounds; i += 2) {
x00 = (x00 + x04) | 0;
x12 = rotl(x12 ^ x00, 16);
x08 = (x08 + x12) | 0;
x04 = rotl(x04 ^ x08, 12);
x00 = (x00 + x04) | 0;
x12 = rotl(x12 ^ x00, 8);
x08 = (x08 + x12) | 0;
x04 = rotl(x04 ^ x08, 7);
x01 = (x01 + x05) | 0;
x13 = rotl(x13 ^ x01, 16);
x09 = (x09 + x13) | 0;
x05 = rotl(x05 ^ x09, 12);
x01 = (x01 + x05) | 0;
x13 = rotl(x13 ^ x01, 8);
x09 = (x09 + x13) | 0;
x05 = rotl(x05 ^ x09, 7);
x02 = (x02 + x06) | 0;
x14 = rotl(x14 ^ x02, 16);
x10 = (x10 + x14) | 0;
x06 = rotl(x06 ^ x10, 12);
x02 = (x02 + x06) | 0;
x14 = rotl(x14 ^ x02, 8);
x10 = (x10 + x14) | 0;
x06 = rotl(x06 ^ x10, 7);
x03 = (x03 + x07) | 0;
x15 = rotl(x15 ^ x03, 16);
x11 = (x11 + x15) | 0;
x07 = rotl(x07 ^ x11, 12);
x03 = (x03 + x07) | 0;
x15 = rotl(x15 ^ x03, 8);
x11 = (x11 + x15) | 0;
x07 = rotl(x07 ^ x11, 7);
x00 = (x00 + x05) | 0;
x15 = rotl(x15 ^ x00, 16);
x10 = (x10 + x15) | 0;
x05 = rotl(x05 ^ x10, 12);
x00 = (x00 + x05) | 0;
x15 = rotl(x15 ^ x00, 8);
x10 = (x10 + x15) | 0;
x05 = rotl(x05 ^ x10, 7);
x01 = (x01 + x06) | 0;
x12 = rotl(x12 ^ x01, 16);
x11 = (x11 + x12) | 0;
x06 = rotl(x06 ^ x11, 12);
x01 = (x01 + x06) | 0;
x12 = rotl(x12 ^ x01, 8);
x11 = (x11 + x12) | 0;
x06 = rotl(x06 ^ x11, 7);
x02 = (x02 + x07) | 0;
x13 = rotl(x13 ^ x02, 16);
x08 = (x08 + x13) | 0;
x07 = rotl(x07 ^ x08, 12);
x02 = (x02 + x07) | 0;
x13 = rotl(x13 ^ x02, 8);
x08 = (x08 + x13) | 0;
x07 = rotl(x07 ^ x08, 7);
x03 = (x03 + x04) | 0;
x14 = rotl(x14 ^ x03, 16);
x09 = (x09 + x14) | 0;
x04 = rotl(x04 ^ x09, 12);
x03 = (x03 + x04) | 0;
x14 = rotl(x14 ^ x03, 8);
x09 = (x09 + x14) | 0;
x04 = rotl(x04 ^ x09, 7);
}
// Write output
let oi = 0;
out[oi++] = (y00 + x00) | 0;
out[oi++] = (y01 + x01) | 0;
out[oi++] = (y02 + x02) | 0;
out[oi++] = (y03 + x03) | 0;
out[oi++] = (y04 + x04) | 0;
out[oi++] = (y05 + x05) | 0;
out[oi++] = (y06 + x06) | 0;
out[oi++] = (y07 + x07) | 0;
out[oi++] = (y08 + x08) | 0;
out[oi++] = (y09 + x09) | 0;
out[oi++] = (y10 + x10) | 0;
out[oi++] = (y11 + x11) | 0;
out[oi++] = (y12 + x12) | 0;
out[oi++] = (y13 + x13) | 0;
out[oi++] = (y14 + x14) | 0;
out[oi++] = (y15 + x15) | 0;
}
/**
* hchacha helper method, used primarily in xchacha, to hash
* key and nonce into key' and nonce'.
* Same as chachaCore, but there doesn't seem to be a way to move the block
* out without 25% performance hit.
*/
// prettier-ignore
function hchacha(c, key, src, out) {
const k32 = (0, utils_js_1.u32)(key);
const i32 = (0, utils_js_1.u32)(src);
const o32 = (0, utils_js_1.u32)(out);
let x00 = c[0], x01 = c[1], x02 = c[2], x03 = c[3];
let x04 = k32[0], x05 = k32[1], x06 = k32[2], x07 = k32[3];
let x08 = k32[4], x09 = k32[5], x10 = k32[6], x11 = k32[7];
let x12 = i32[0], x13 = i32[1], x14 = i32[2], x15 = i32[3];
for (let i = 0; i < 20; i += 2) {
x00 = (x00 + x04) | 0;
x12 = rotl(x12 ^ x00, 16);
x08 = (x08 + x12) | 0;
x04 = rotl(x04 ^ x08, 12);
x00 = (x00 + x04) | 0;
x12 = rotl(x12 ^ x00, 8);
x08 = (x08 + x12) | 0;
x04 = rotl(x04 ^ x08, 7);
x01 = (x01 + x05) | 0;
x13 = rotl(x13 ^ x01, 16);
x09 = (x09 + x13) | 0;
x05 = rotl(x05 ^ x09, 12);
x01 = (x01 + x05) | 0;
x13 = rotl(x13 ^ x01, 8);
x09 = (x09 + x13) | 0;
x05 = rotl(x05 ^ x09, 7);
x02 = (x02 + x06) | 0;
x14 = rotl(x14 ^ x02, 16);
x10 = (x10 + x14) | 0;
x06 = rotl(x06 ^ x10, 12);
x02 = (x02 + x06) | 0;
x14 = rotl(x14 ^ x02, 8);
x10 = (x10 + x14) | 0;
x06 = rotl(x06 ^ x10, 7);
x03 = (x03 + x07) | 0;
x15 = rotl(x15 ^ x03, 16);
x11 = (x11 + x15) | 0;
x07 = rotl(x07 ^ x11, 12);
x03 = (x03 + x07) | 0;
x15 = rotl(x15 ^ x03, 8);
x11 = (x11 + x15) | 0;
x07 = rotl(x07 ^ x11, 7);
x00 = (x00 + x05) | 0;
x15 = rotl(x15 ^ x00, 16);
x10 = (x10 + x15) | 0;
x05 = rotl(x05 ^ x10, 12);
x00 = (x00 + x05) | 0;
x15 = rotl(x15 ^ x00, 8);
x10 = (x10 + x15) | 0;
x05 = rotl(x05 ^ x10, 7);
x01 = (x01 + x06) | 0;
x12 = rotl(x12 ^ x01, 16);
x11 = (x11 + x12) | 0;
x06 = rotl(x06 ^ x11, 12);
x01 = (x01 + x06) | 0;
x12 = rotl(x12 ^ x01, 8);
x11 = (x11 + x12) | 0;
x06 = rotl(x06 ^ x11, 7);
x02 = (x02 + x07) | 0;
x13 = rotl(x13 ^ x02, 16);
x08 = (x08 + x13) | 0;
x07 = rotl(x07 ^ x08, 12);
x02 = (x02 + x07) | 0;
x13 = rotl(x13 ^ x02, 8);
x08 = (x08 + x13) | 0;
x07 = rotl(x07 ^ x08, 7);
x03 = (x03 + x04) | 0;
x14 = rotl(x14 ^ x03, 16);
x09 = (x09 + x14) | 0;
x04 = rotl(x04 ^ x09, 12);
x03 = (x03 + x04) | 0;
x14 = rotl(x14 ^ x03, 8);
x09 = (x09 + x14) | 0;
x04 = rotl(x04 ^ x09, 7);
}
o32[0] = x00;
o32[1] = x01;
o32[2] = x02;
o32[3] = x03;
o32[4] = x12;
o32[5] = x13;
o32[6] = x14;
o32[7] = x15;
return out;
}
exports.hchacha = hchacha;
/**
* Original, non-RFC chacha20 from DJB. 8-byte nonce, 8-byte counter.
*/
exports.chacha20orig = (0, _salsa_js_1.salsaBasic)({
core: chachaCore,
counterRight: false,
counterLen: 8,
});
/**
* ChaCha stream cipher. Conforms to RFC 8439 (IETF, TLS). 12-byte nonce, 4-byte counter.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
exports.chacha20 = (0, _salsa_js_1.salsaBasic)({
core: chachaCore,
counterRight: false,
counterLen: 4,
allow128bitKeys: false,
});
/**
* XChaCha eXtended-nonce ChaCha. 24-byte nonce.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
*/
exports.xchacha20 = (0, _salsa_js_1.salsaBasic)({
core: chachaCore,
counterRight: false,
counterLen: 8,
extendNonceFn: hchacha,
allow128bitKeys: false,
});
/**
* Reduced 8-round chacha, described in original paper.
*/
exports.chacha8 = (0, _salsa_js_1.salsaBasic)({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 8,
});
/**
* Reduced 12-round chacha, described in original paper.
*/
exports.chacha12 = (0, _salsa_js_1.salsaBasic)({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 12,
});
const ZERO = /* @__PURE__ */ new Uint8Array(16);
// Pad to digest size with zeros
const updatePadded = (h, msg) => {
h.update(msg);
const left = msg.length % 16;
if (left)
h.update(ZERO.subarray(left));
};
const computeTag = (fn, key, nonce, data, AAD) => {
const authKey = fn(key, nonce, new Uint8Array(32));
const h = _poly1305_js_1.poly1305.create(authKey);
if (AAD)
updatePadded(h, AAD);
updatePadded(h, data);
const num = new Uint8Array(16);
const view = (0, utils_js_1.createView)(num);
(0, utils_js_1.setBigUint64)(view, 0, BigInt(AAD ? AAD.length : 0), true);
(0, utils_js_1.setBigUint64)(view, 8, BigInt(data.length), true);
h.update(num);
const res = h.digest();
authKey.fill(0);
return res;
};
/**
* AEAD algorithm from RFC 8439.
* Salsa20 and chacha (RFC 8439) use poly1305 differently.
* We could have composed them similar to:
* https://github.com/paulmillr/scure-base/blob/b266c73dde977b1dd7ef40ef7a23cc15aab526b3/index.ts#L250
* But it's hard because of authKey:
* In salsa20, authKey changes position in salsa stream.
* In chacha, authKey can't be computed inside computeTag, it modifies the counter.
*/
const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
const tagLength = 16;
(0, utils_js_1.ensureBytes)(key, 32);
(0, utils_js_1.ensureBytes)(nonce);
return {
tagLength,
encrypt: (plaintext, output) => {
const plength = plaintext.length;
const clength = plength + tagLength;
if (output) {
(0, utils_js_1.ensureBytes)(output, clength);
}
else {
output = new Uint8Array(clength);
}
xorStream(key, nonce, plaintext, output, 1);
const tag = computeTag(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
output.set(tag, plength); // append tag
return output;
},
decrypt: (ciphertext, output) => {
const clength = ciphertext.length;
const plength = clength - tagLength;
if (clength < tagLength)
throw new Error(`encrypted data must be at least ${tagLength} bytes`);
if (output) {
(0, utils_js_1.ensureBytes)(output, plength);
}
else {
output = new Uint8Array(plength);
}
const data = ciphertext.subarray(0, -tagLength);
const passedTag = ciphertext.subarray(-tagLength);
const tag = computeTag(xorStream, key, nonce, data, AAD);
if (!(0, utils_js_1.equalBytes)(passedTag, tag))
throw new Error('invalid tag');
xorStream(key, nonce, data, output, 1);
return output;
},
};
};
exports._poly1305_aead = _poly1305_aead;
/**
* ChaCha20-Poly1305 from RFC 8439.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
exports.chacha20poly1305 = (0, exports._poly1305_aead)(exports.chacha20);
/**
* XChaCha20-Poly1305 extended-nonce chacha.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
exports.xchacha20poly1305 = (0, exports._poly1305_aead)(exports.xchacha20);
//# sourceMappingURL=chacha.js.map
+1
View File
File diff suppressed because one or more lines are too long
+37
View File
@@ -0,0 +1,37 @@
function number(n) {
if (!Number.isSafeInteger(n) || n < 0)
throw new Error(`Wrong positive integer: ${n}`);
}
function bool(b) {
if (typeof b !== 'boolean')
throw new Error(`Expected boolean, not ${b}`);
}
function bytes(b, ...lengths) {
if (!(b instanceof Uint8Array))
throw new Error('Expected Uint8Array');
if (lengths.length > 0 && !lengths.includes(b.length))
throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
}
function hash(hash) {
if (typeof hash !== 'function' || typeof hash.create !== 'function')
throw new Error('hash must be wrapped by utils.wrapConstructor');
number(hash.outputLen);
number(hash.blockLen);
}
function exists(instance, checkFinished = true) {
if (instance.destroyed)
throw new Error('Hash instance has been destroyed');
if (checkFinished && instance.finished)
throw new Error('Hash#digest() has already been called');
}
function output(out, instance) {
bytes(out);
const min = instance.outputLen;
if (out.length < min) {
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
}
}
export { number, bool, bytes, hash, exists, output };
const assert = { number, bool, bytes, hash, exists, output };
export default assert;
//# sourceMappingURL=_assert.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"_assert.js","sourceRoot":"","sources":["../src/_assert.ts"],"names":[],"mappings":"AAAA,SAAS,MAAM,CAAC,CAAS;IACvB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,EAAE,CAAC,CAAC;AACzF,CAAC;AAED,SAAS,IAAI,CAAC,CAAU;IACtB,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,KAAK,CAAC,CAAyB,EAAE,GAAG,OAAiB;IAC5D,IAAI,CAAC,CAAC,CAAC,YAAY,UAAU,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACvE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,iCAAiC,OAAO,mBAAmB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AAC3F,CAAC;AAQD,SAAS,IAAI,CAAC,IAAU;IACtB,IAAI,OAAO,IAAI,KAAK,UAAU,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU;QACjE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,MAAM,CAAC,QAAa,EAAE,aAAa,GAAG,IAAI;IACjD,IAAI,QAAQ,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAC5E,IAAI,aAAa,IAAI,QAAQ,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;AACnG,CAAC;AACD,SAAS,MAAM,CAAC,GAAQ,EAAE,QAAa;IACrC,KAAK,CAAC,GAAG,CAAC,CAAC;IACX,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC;IAC/B,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE;QACpB,MAAM,IAAI,KAAK,CAAC,yDAAyD,GAAG,EAAE,CAAC,CAAC;KACjF;AACH,CAAC;AAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACrD,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC7D,eAAe,MAAM,CAAC"}
+281
View File
@@ -0,0 +1,281 @@
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
// micro-noble-ciphers: more auditable, but slower version of salsa20, chacha & poly1305.
// Implements the same algorithms that are present in other files,
// but without unrolled loops (https://en.wikipedia.org/wiki/Loop_unrolling).
import * as u from './utils.js';
import { salsaBasic } from './_salsa.js';
// Utils
function hexToNumber(hex) {
if (typeof hex !== 'string')
throw new Error('hex string expected, got ' + typeof hex);
// Big Endian
return BigInt(hex === '' ? '0' : `0x${hex}`);
}
function bytesToNumberLE(bytes) {
return hexToNumber(u.bytesToHex(Uint8Array.from(bytes).reverse()));
}
function numberToBytesLE(n, len) {
return u.hexToBytes(n.toString(16).padStart(len * 2, '0')).reverse();
}
const rotl = (a, b) => (a << b) | (a >>> (32 - b));
// /Utils
function salsaQR(x, a, b, c, d) {
x[b] ^= rotl((x[a] + x[d]) | 0, 7);
x[c] ^= rotl((x[b] + x[a]) | 0, 9);
x[d] ^= rotl((x[c] + x[b]) | 0, 13);
x[a] ^= rotl((x[d] + x[c]) | 0, 18);
}
// prettier-ignore
function chachaQR(x, a, b, c, d) {
x[a] = (x[a] + x[b]) | 0;
x[d] = rotl(x[d] ^ x[a], 16);
x[c] = (x[c] + x[d]) | 0;
x[b] = rotl(x[b] ^ x[c], 12);
x[a] = (x[a] + x[b]) | 0;
x[d] = rotl(x[d] ^ x[a], 8);
x[c] = (x[c] + x[d]) | 0;
x[b] = rotl(x[b] ^ x[c], 7);
}
function salsaRound(x, rounds = 20) {
for (let i = 0; i < rounds; i += 2) {
salsaQR(x, 0, 4, 8, 12);
salsaQR(x, 5, 9, 13, 1);
salsaQR(x, 10, 14, 2, 6);
salsaQR(x, 15, 3, 7, 11);
salsaQR(x, 0, 1, 2, 3);
salsaQR(x, 5, 6, 7, 4);
salsaQR(x, 10, 11, 8, 9);
salsaQR(x, 15, 12, 13, 14);
}
}
function chachaRound(x, rounds = 20) {
for (let i = 0; i < rounds; i += 2) {
chachaQR(x, 0, 4, 8, 12);
chachaQR(x, 1, 5, 9, 13);
chachaQR(x, 2, 6, 10, 14);
chachaQR(x, 3, 7, 11, 15);
chachaQR(x, 0, 5, 10, 15);
chachaQR(x, 1, 6, 11, 12);
chachaQR(x, 2, 7, 8, 13);
chachaQR(x, 3, 4, 9, 14);
}
}
function salsaCore(c, k, n, out, cnt, rounds = 20) {
// prettier-ignore
const y = new Uint32Array([
c[0], k[0], k[1], k[2],
k[3], c[1], n[0], n[1],
cnt, 0, c[2], k[4],
k[5], k[6], k[7], c[3], // Key Key Key "te k"
]);
const x = y.slice();
salsaRound(x, rounds);
for (let i = 0; i < 16; i++)
out[i] = (y[i] + x[i]) | 0;
}
export function hsalsa(c, key, nonce) {
const k = u.u32(key);
const i = u.u32(nonce);
// prettier-ignore
const x = new Uint32Array([
c[0], k[0], k[1], k[2],
k[3], c[1], i[0], i[1],
i[2], i[3], c[2], k[4],
k[5], k[6], k[7], c[3]
]);
salsaRound(x);
return u.u8(new Uint32Array([x[0], x[5], x[10], x[15], x[6], x[7], x[8], x[9]]));
}
function chachaCore(c, k, n, out, cnt, rounds = 20) {
// prettier-ignore
const y = new Uint32Array([
c[0], c[1], c[2], c[3],
k[0], k[1], k[2], k[3],
k[4], k[5], k[6], k[7],
cnt, n[0], n[1], n[2], // Counter Counter Nonce Nonce
]);
const x = y.slice();
chachaRound(x, rounds);
for (let i = 0; i < 16; i++)
out[i] = (y[i] + x[i]) | 0;
}
export function hchacha(c, key, nonce) {
const k = u.u32(key);
const i = u.u32(nonce);
// prettier-ignore
const x = new Uint32Array([
c[0], c[1], c[2], c[3],
k[0], k[1], k[2], k[3],
k[4], k[5], k[6], k[7],
i[0], i[1], i[2], i[3],
]);
chachaRound(x);
return u.u8(new Uint32Array([x[0], x[1], x[2], x[3], x[12], x[13], x[14], x[15]]));
}
/**
* salsa20, 12-byte nonce.
*/
export const salsa20 = salsaBasic({ core: salsaCore, counterRight: true });
/**
* xsalsa20, 24-byte nonce.
*/
export const xsalsa20 = salsaBasic({
core: salsaCore,
counterRight: true,
extendNonceFn: hsalsa,
allow128bitKeys: false,
});
/**
* chacha20 non-RFC, original version by djb. 8-byte nonce, 8-byte counter.
*/
export const chacha20orig = salsaBasic({ core: chachaCore, counterRight: false, counterLen: 8 });
/**
* chacha20 RFC 8439 (IETF / TLS). 12-byte nonce, 4-byte counter.
*/
export const chacha20 = salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
allow128bitKeys: false,
});
/**
* xchacha20 eXtended-nonce. https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
*/
export const xchacha20 = salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 8,
extendNonceFn: hchacha,
allow128bitKeys: false,
});
/**
* 8-round chacha from the original paper.
*/
export const chacha8 = salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 8,
});
/**
* 12-round chacha from the original paper.
*/
export const chacha12 = salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 12,
});
const POW_2_130_5 = 2n ** 130n - 5n;
const POW_2_128_1 = 2n ** (16n * 8n) - 1n;
// Can be speed-up using BigUint64Array, but would be more complicated
export function poly1305(msg, key) {
u.ensureBytes(msg);
u.ensureBytes(key);
let acc = 0n;
const r = bytesToNumberLE(key.subarray(0, 16)) & 0x0ffffffc0ffffffc0ffffffc0fffffffn;
const s = bytesToNumberLE(key.subarray(16));
// Process by 16 byte chunks
for (let i = 0; i < msg.length; i += 16) {
const m = msg.subarray(i, i + 16);
const n = bytesToNumberLE(m) | (1n << BigInt(8 * m.length));
acc = ((acc + n) * r) % POW_2_130_5;
}
const res = (acc + s) & POW_2_128_1;
return numberToBytesLE(res, 16);
}
function computeTag(fn, key, nonce, ciphertext, AAD) {
const res = [];
if (AAD) {
res.push(AAD);
const leftover = AAD.length % 16;
if (leftover > 0)
res.push(new Uint8Array(16 - leftover));
}
res.push(ciphertext);
const leftover = ciphertext.length % 16;
if (leftover > 0)
res.push(new Uint8Array(16 - leftover));
// Lengths
const num = new Uint8Array(16);
const view = u.createView(num);
u.setBigUint64(view, 0, BigInt(AAD ? AAD.length : 0), true);
u.setBigUint64(view, 8, BigInt(ciphertext.length), true);
res.push(num);
const authKey = fn(key, nonce, new Uint8Array(32));
return poly1305(u.concatBytes(...res), authKey);
}
/**
* xsalsa20-poly1305 eXtended-nonce (24 bytes) salsa.
*/
export function xsalsa20poly1305(key, nonce) {
u.ensureBytes(key);
u.ensureBytes(nonce);
return {
encrypt: (plaintext) => {
u.ensureBytes(plaintext);
const m = u.concatBytes(new Uint8Array(32), plaintext);
const c = xsalsa20(key, nonce, m);
const authKey = c.subarray(0, 32);
const data = c.subarray(32);
const tag = poly1305(data, authKey);
return u.concatBytes(tag, data);
},
decrypt: (ciphertext) => {
u.ensureBytes(ciphertext);
if (ciphertext.length < 16)
throw new Error('encrypted data must be at least 16 bytes');
const c = u.concatBytes(new Uint8Array(16), ciphertext);
const authKey = xsalsa20(key, nonce, new Uint8Array(32));
const tag = poly1305(c.subarray(32), authKey);
if (!u.equalBytes(c.subarray(16, 32), tag))
throw new Error('invalid poly1305 tag');
return xsalsa20(key, nonce, c).subarray(32);
},
};
}
/**
* Alias to xsalsa20-poly1305
*/
export function secretbox(key, nonce) {
u.ensureBytes(key);
u.ensureBytes(nonce);
const xs = xsalsa20poly1305(key, nonce);
return { seal: xs.encrypt, open: xs.decrypt };
}
export const _poly1305_aead = (fn) => (key, nonce, AAD) => {
const tagLength = 16;
const keyLength = 32;
u.ensureBytes(key, keyLength);
u.ensureBytes(nonce);
return {
tagLength,
encrypt: (plaintext) => {
u.ensureBytes(plaintext);
const res = fn(key, nonce, plaintext, undefined, 1);
const tag = computeTag(fn, key, nonce, res, AAD);
return u.concatBytes(res, tag);
},
decrypt: (ciphertext) => {
u.ensureBytes(ciphertext);
if (ciphertext.length < tagLength)
throw new Error(`encrypted data must be at least ${tagLength} bytes`);
const passedTag = ciphertext.subarray(-tagLength);
const data = ciphertext.subarray(0, -tagLength);
const tag = computeTag(fn, key, nonce, data, AAD);
if (!u.equalBytes(passedTag, tag))
throw new Error('invalid poly1305 tag');
return fn(key, nonce, data, undefined, 1);
},
};
};
/**
* chacha20-poly1305 12-byte-nonce chacha.
*/
export const chacha20poly1305 = _poly1305_aead(chacha20);
/**
* xchacha20-poly1305 eXtended-nonce (24 bytes) chacha.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
export const xchacha20poly1305 = _poly1305_aead(xchacha20);
//# sourceMappingURL=_micro.js.map
File diff suppressed because one or more lines are too long
+264
View File
@@ -0,0 +1,264 @@
import { toBytes, ensureBytes } from './utils.js';
import assert from './_assert.js';
// Poly1305 is a fast and parallel secret-key message-authentication code.
// https://cr.yp.to/mac.html, https://cr.yp.to/mac/poly1305-20050329.pdf
// https://datatracker.ietf.org/doc/html/rfc8439
// Based on Public Domain poly1305-donna https://github.com/floodyberry/poly1305-donna
const u8to16 = (a, i) => (a[i++] & 0xff) | ((a[i++] & 0xff) << 8);
class Poly1305 {
constructor(key) {
this.blockLen = 16;
this.outputLen = 16;
this.buffer = new Uint8Array(16);
this.r = new Uint16Array(10);
this.h = new Uint16Array(10);
this.pad = new Uint16Array(8);
this.pos = 0;
this.finished = false;
key = toBytes(key);
ensureBytes(key, 32);
const t0 = u8to16(key, 0);
const t1 = u8to16(key, 2);
const t2 = u8to16(key, 4);
const t3 = u8to16(key, 6);
const t4 = u8to16(key, 8);
const t5 = u8to16(key, 10);
const t6 = u8to16(key, 12);
const t7 = u8to16(key, 14);
// https://github.com/floodyberry/poly1305-donna/blob/e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781/poly1305-donna-16.h#L47
this.r[0] = t0 & 0x1fff;
this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03;
this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff;
this.r[5] = (t4 >>> 1) & 0x1ffe;
this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81;
this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
this.r[9] = (t7 >>> 5) & 0x007f;
for (let i = 0; i < 8; i++)
this.pad[i] = u8to16(key, 16 + 2 * i);
}
process(data, offset, isLast = false) {
const hibit = isLast ? 0 : 1 << 11;
const { h, r } = this;
const r0 = r[0];
const r1 = r[1];
const r2 = r[2];
const r3 = r[3];
const r4 = r[4];
const r5 = r[5];
const r6 = r[6];
const r7 = r[7];
const r8 = r[8];
const r9 = r[9];
const t0 = u8to16(data, offset + 0);
const t1 = u8to16(data, offset + 2);
const t2 = u8to16(data, offset + 4);
const t3 = u8to16(data, offset + 6);
const t4 = u8to16(data, offset + 8);
const t5 = u8to16(data, offset + 10);
const t6 = u8to16(data, offset + 12);
const t7 = u8to16(data, offset + 14);
let h0 = h[0] + (t0 & 0x1fff);
let h1 = h[1] + (((t0 >>> 13) | (t1 << 3)) & 0x1fff);
let h2 = h[2] + (((t1 >>> 10) | (t2 << 6)) & 0x1fff);
let h3 = h[3] + (((t2 >>> 7) | (t3 << 9)) & 0x1fff);
let h4 = h[4] + (((t3 >>> 4) | (t4 << 12)) & 0x1fff);
let h5 = h[5] + ((t4 >>> 1) & 0x1fff);
let h6 = h[6] + (((t4 >>> 14) | (t5 << 2)) & 0x1fff);
let h7 = h[7] + (((t5 >>> 11) | (t6 << 5)) & 0x1fff);
let h8 = h[8] + (((t6 >>> 8) | (t7 << 8)) & 0x1fff);
let h9 = h[9] + ((t7 >>> 5) | hibit);
let c = 0;
let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
c = d0 >>> 13;
d0 &= 0x1fff;
d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
c += d0 >>> 13;
d0 &= 0x1fff;
let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
c = d1 >>> 13;
d1 &= 0x1fff;
d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
c += d1 >>> 13;
d1 &= 0x1fff;
let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
c = d2 >>> 13;
d2 &= 0x1fff;
d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
c += d2 >>> 13;
d2 &= 0x1fff;
let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
c = d3 >>> 13;
d3 &= 0x1fff;
d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
c += d3 >>> 13;
d3 &= 0x1fff;
let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
c = d4 >>> 13;
d4 &= 0x1fff;
d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
c += d4 >>> 13;
d4 &= 0x1fff;
let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
c = d5 >>> 13;
d5 &= 0x1fff;
d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
c += d5 >>> 13;
d5 &= 0x1fff;
let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
c = d6 >>> 13;
d6 &= 0x1fff;
d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
c += d6 >>> 13;
d6 &= 0x1fff;
let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
c = d7 >>> 13;
d7 &= 0x1fff;
d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
c += d7 >>> 13;
d7 &= 0x1fff;
let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
c = d8 >>> 13;
d8 &= 0x1fff;
d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
c += d8 >>> 13;
d8 &= 0x1fff;
let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
c = d9 >>> 13;
d9 &= 0x1fff;
d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
c += d9 >>> 13;
d9 &= 0x1fff;
c = ((c << 2) + c) | 0;
c = (c + d0) | 0;
d0 = c & 0x1fff;
c = c >>> 13;
d1 += c;
h[0] = d0;
h[1] = d1;
h[2] = d2;
h[3] = d3;
h[4] = d4;
h[5] = d5;
h[6] = d6;
h[7] = d7;
h[8] = d8;
h[9] = d9;
}
finalize() {
const { h, pad } = this;
const g = new Uint16Array(10);
let c = h[1] >>> 13;
h[1] &= 0x1fff;
for (let i = 2; i < 10; i++) {
h[i] += c;
c = h[i] >>> 13;
h[i] &= 0x1fff;
}
h[0] += c * 5;
c = h[0] >>> 13;
h[0] &= 0x1fff;
h[1] += c;
c = h[1] >>> 13;
h[1] &= 0x1fff;
h[2] += c;
g[0] = h[0] + 5;
c = g[0] >>> 13;
g[0] &= 0x1fff;
for (let i = 1; i < 10; i++) {
g[i] = h[i] + c;
c = g[i] >>> 13;
g[i] &= 0x1fff;
}
g[9] -= 1 << 13;
let mask = (c ^ 1) - 1;
for (let i = 0; i < 10; i++)
g[i] &= mask;
mask = ~mask;
for (let i = 0; i < 10; i++)
h[i] = (h[i] & mask) | g[i];
h[0] = (h[0] | (h[1] << 13)) & 0xffff;
h[1] = ((h[1] >>> 3) | (h[2] << 10)) & 0xffff;
h[2] = ((h[2] >>> 6) | (h[3] << 7)) & 0xffff;
h[3] = ((h[3] >>> 9) | (h[4] << 4)) & 0xffff;
h[4] = ((h[4] >>> 12) | (h[5] << 1) | (h[6] << 14)) & 0xffff;
h[5] = ((h[6] >>> 2) | (h[7] << 11)) & 0xffff;
h[6] = ((h[7] >>> 5) | (h[8] << 8)) & 0xffff;
h[7] = ((h[8] >>> 8) | (h[9] << 5)) & 0xffff;
let f = h[0] + pad[0];
h[0] = f & 0xffff;
for (let i = 1; i < 8; i++) {
f = (((h[i] + pad[i]) | 0) + (f >>> 16)) | 0;
h[i] = f & 0xffff;
}
}
update(data) {
assert.exists(this);
const { buffer, blockLen } = this;
data = toBytes(data);
const len = data.length;
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);
// Fast path: we have at least one block in input
if (take === blockLen) {
for (; blockLen <= len - pos; pos += blockLen)
this.process(data, pos);
continue;
}
buffer.set(data.subarray(pos, pos + take), this.pos);
this.pos += take;
pos += take;
if (this.pos === blockLen) {
this.process(buffer, 0, false);
this.pos = 0;
}
}
return this;
}
destroy() {
this.h.fill(0);
this.r.fill(0);
this.buffer.fill(0);
this.pad.fill(0);
}
digestInto(out) {
assert.exists(this);
assert.output(out, this);
this.finished = true;
const { buffer, h } = this;
let { pos } = this;
if (pos) {
buffer[pos++] = 1;
// buffer.subarray(pos).fill(0);
for (; pos < 16; pos++)
buffer[pos] = 0;
this.process(buffer, 0, true);
}
this.finalize();
let opos = 0;
for (let i = 0; i < 8; i++) {
out[opos++] = h[i] >>> 0;
out[opos++] = h[i] >>> 8;
}
return out;
}
digest() {
const { buffer, outputLen } = this;
this.digestInto(buffer);
const res = buffer.slice(0, outputLen);
this.destroy();
return res;
}
}
export function wrapConstructorWithKey(hashCons) {
const hashC = (msg, key) => hashCons(key).update(toBytes(msg)).digest();
const tmp = hashCons(new Uint8Array(32));
hashC.outputLen = tmp.outputLen;
hashC.blockLen = tmp.blockLen;
hashC.create = (key) => hashCons(key);
return hashC;
}
export const poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
//# sourceMappingURL=_poly1305.js.map
File diff suppressed because one or more lines are too long
+100
View File
@@ -0,0 +1,100 @@
import { u8, u32, ensureBytes } from './utils.js';
// AES-SIV polyval, little-endian "mirror image" of AES-GCM GHash
// polynomial hash function. Defined in RFC 8452.
// Reverse bits in u32, constant-time, precompute will be faster, but non-constant time
function rev32(x) {
x = ((x & 1431655765) << 1) | ((x >>> 1) & 1431655765);
x = ((x & 858993459) << 2) | ((x >>> 2) & 858993459);
x = ((x & 252645135) << 4) | ((x >>> 4) & 252645135);
x = ((x & 16711935) << 8) | ((x >>> 8) & 16711935);
return (x << 16) | (x >>> 16);
}
// wrapped 32 bit multiplication
const wrapMul = (a, b) => Math.imul(a, b) >>> 0;
// https://timtaubert.de/blog/2017/06/verified-binary-multiplication-for-ghash/
function bmul32(x, y) {
const x0 = x & 286331153;
const x1 = x & 572662306;
const x2 = x & 1145324612;
const x3 = x & 2290649224;
const y0 = y & 286331153;
const y1 = y & 572662306;
const y2 = y & 1145324612;
const y3 = y & 2290649224;
let res = (wrapMul(x0, y0) ^ wrapMul(x1, y3) ^ wrapMul(x2, y2) ^ wrapMul(x3, y1)) & 286331153;
res |= (wrapMul(x0, y1) ^ wrapMul(x1, y0) ^ wrapMul(x2, y3) ^ wrapMul(x3, y2)) & 572662306;
res |= (wrapMul(x0, y2) ^ wrapMul(x1, y1) ^ wrapMul(x2, y0) ^ wrapMul(x3, y3)) & 1145324612;
res |= (wrapMul(x0, y3) ^ wrapMul(x1, y2) ^ wrapMul(x2, y1) ^ wrapMul(x3, y0)) & 2290649224;
return res >>> 0;
}
function mulPart(arr) {
const a = new Uint32Array(18);
a[0] = arr[0];
a[1] = arr[1];
a[2] = arr[2];
a[3] = arr[3];
a[4] = a[0] ^ a[1];
a[5] = a[2] ^ a[3];
a[6] = a[0] ^ a[2];
a[7] = a[1] ^ a[3];
a[8] = a[6] ^ a[7];
a[9] = rev32(arr[0]);
a[10] = rev32(arr[1]);
a[11] = rev32(arr[2]);
a[12] = rev32(arr[3]);
a[13] = a[9] ^ a[10];
a[14] = a[11] ^ a[12];
a[15] = a[9] ^ a[11];
a[16] = a[10] ^ a[12];
a[17] = a[15] ^ a[16];
return a;
}
export function polyval(h, data) {
ensureBytes(h);
ensureBytes(data);
const s = new Uint32Array(4);
// Precompute for multiplication
const a = mulPart(u32(h));
if (data.length % 16)
throw new Error('polyval: data must be padded to 16 bytes');
const data32 = u32(data);
for (let i = 0; i < data32.length; i += 4) {
// Xor
s[0] ^= data32[i + 0];
s[1] ^= data32[i + 1];
s[2] ^= data32[i + 2];
s[3] ^= data32[i + 3];
// Dot via Karatsuba multiplication, based on MIT-licensed
// https://bearssl.org/gitweb/?p=BearSSL;a=blob;f=src/hash/ghash_ctmul32.c;hb=4b6046412
const b = mulPart(s);
const c = new Uint32Array(18);
for (let i = 0; i < 18; i++)
c[i] = bmul32(a[i], b[i]);
c[4] ^= c[0] ^ c[1];
c[5] ^= c[2] ^ c[3];
c[8] ^= c[6] ^ c[7];
c[13] ^= c[9] ^ c[10];
c[14] ^= c[11] ^ c[12];
c[17] ^= c[15] ^ c[16];
const zw = new Uint32Array(8);
zw[0] = c[0];
zw[1] = c[4] ^ (rev32(c[9]) >>> 1);
zw[2] = c[1] ^ c[0] ^ c[2] ^ c[6] ^ (rev32(c[13]) >>> 1);
zw[3] = c[4] ^ c[5] ^ c[8] ^ (rev32(c[10] ^ c[9] ^ c[11] ^ c[15]) >>> 1);
zw[4] = c[2] ^ c[1] ^ c[3] ^ c[7] ^ (rev32(c[13] ^ c[14] ^ c[17]) >>> 1);
zw[5] = c[5] ^ (rev32(c[11] ^ c[10] ^ c[12] ^ c[16]) >>> 1);
zw[6] = c[3] ^ (rev32(c[14]) >>> 1);
zw[7] = rev32(c[12]) >>> 1;
for (let i = 0; i < 4; i++) {
const lw = zw[i];
zw[i + 4] ^= lw ^ (lw >>> 1) ^ (lw >>> 2) ^ (lw >>> 7);
zw[i + 3] ^= (lw << 31) ^ (lw << 30) ^ (lw << 25);
}
s[0] = zw[4];
s[1] = zw[5];
s[2] = zw[6];
s[3] = zw[7];
}
return u8(s);
}
//# sourceMappingURL=_polyval.js.map
File diff suppressed because one or more lines are too long
+154
View File
@@ -0,0 +1,154 @@
// Basic utils for salsa-like ciphers
// Check out _micro.ts for descriptive documentation.
import assert from './_assert.js';
import { u32, utf8ToBytes, checkOpts } from './utils.js';
/*
RFC8439 requires multi-step cipher stream, where
authKey starts with counter: 0, actual msg with counter: 1.
For this, we need a way to re-use nonce / counter:
const counter = new Uint8Array(4);
chacha(..., counter, ...); // counter is now 1
chacha(..., counter, ...); // counter is now 2
This is complicated:
- Original papers don't allow mutating counters
- Counter overflow is undefined: https://mailarchive.ietf.org/arch/msg/cfrg/gsOnTJzcbgG6OqD8Sc0GO5aR_tU/
- 3rd-party library stablelib implementation uses an approach where you can provide
nonce and counter instead of just nonce - and it will re-use it
- We could have did something similar, but ChaCha has different counter position
(counter | nonce), which is not composable with XChaCha, because full counter
is (nonce16 | counter | nonce16). Stablelib doesn't support in-place counter for XChaCha.
- We could separate nonce & counter and provide separate API for counter re-use, but
there are different counter sizes depending on an algorithm.
- Salsa & ChaCha also differ in structures of key / sigma:
salsa: c0 | k(4) | c1 | nonce(2) | ctr(2) | c2 | k(4) | c4
chacha: c(4) | k(8) | ctr(1) | nonce(3)
chachaDJB: c(4) | k(8) | ctr(2) | nonce(2)
- Creating function such as `setSalsaState(key, nonce, sigma, data)` won't work,
because we can't re-use counter array
- 32-bit nonce is `2 ** 32 * 64` = 256GB with 32-bit counter
- JS does not allow UintArrays bigger than 4GB, so supporting 64-bit counters doesn't matter
Structure is as following:
key=16 -> sigma16, k=key|key
key=32 -> sigma32, k=key
nonces:
salsa20: 8 (8-byte counter)
chacha20djb: 8 (8-byte counter)
chacha20tls: 12 (4-byte counter)
xsalsa: 24 (16 -> hsalsa, 8 -> old nonce)
xchacha: 24 (16 -> hchacha, 8 -> old nonce)
https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha#appendix-A.2
Use the subkey and remaining 8 byte nonce with ChaCha20 as normal
(prefixed by 4 NUL bytes, since [RFC8439] specifies a 12-byte nonce).
*/
const sigma16 = utf8ToBytes('expand 16-byte k');
const sigma32 = utf8ToBytes('expand 32-byte k');
const sigma16_32 = u32(sigma16);
const sigma32_32 = u32(sigma32);
// Is byte array aligned to 4 byte offset (u32)?
const isAligned32 = (b) => !(b.byteOffset % 4);
export const salsaBasic = (opts) => {
const { core, rounds, counterRight, counterLen, allow128bitKeys, extendNonceFn, blockLen } = checkOpts({ rounds: 20, counterRight: false, counterLen: 8, allow128bitKeys: true, blockLen: 64 }, opts);
assert.number(counterLen);
assert.number(rounds);
assert.number(blockLen);
assert.bool(counterRight);
assert.bool(allow128bitKeys);
const blockLen32 = blockLen / 4;
if (blockLen % 4 !== 0)
throw new Error('Salsa/ChaCha: blockLen must be aligned to 4 bytes');
return (key, nonce, data, output, counter = 0) => {
assert.bytes(key);
assert.bytes(nonce);
assert.bytes(data);
if (!output)
output = new Uint8Array(data.length);
assert.bytes(output);
assert.number(counter);
// > new Uint32Array([2**32])
// Uint32Array(1) [ 0 ]
// > new Uint32Array([2**32-1])
// Uint32Array(1) [ 4294967295 ]
if (counter < 0 || counter >= 2 ** 32 - 1)
throw new Error('Salsa/ChaCha: counter overflow');
if (output.length < data.length) {
throw new Error(`Salsa/ChaCha: output (${output.length}) is shorter than data (${data.length})`);
}
const toClean = [];
let k, sigma;
// Handle 128 byte keys
if (key.length === 32) {
k = key;
sigma = sigma32_32;
}
else if (key.length === 16 && allow128bitKeys) {
k = new Uint8Array(32);
k.set(key);
k.set(key, 16);
sigma = sigma16_32;
toClean.push(k);
}
else
throw new Error(`Salsa/ChaCha: invalid 32-byte key, got length=${key.length}`);
// Handle extended nonce (HChaCha/HSalsa)
if (extendNonceFn) {
if (nonce.length <= 16)
throw new Error(`Salsa/ChaCha: extended nonce must be bigger than 16 bytes`);
k = extendNonceFn(sigma, k, nonce.subarray(0, 16), new Uint8Array(32));
toClean.push(k);
nonce = nonce.subarray(16);
}
// Handle nonce counter
const nonceLen = 16 - counterLen;
if (nonce.length !== nonceLen)
throw new Error(`Salsa/ChaCha: nonce must be ${nonceLen} or 16 bytes`);
// Pad counter when nonce is 64 bit
if (nonceLen !== 12) {
const nc = new Uint8Array(12);
nc.set(nonce, counterRight ? 0 : 12 - nonce.length);
toClean.push((nonce = nc));
}
// Counter positions
const block = new Uint8Array(blockLen);
// Cast to Uint32Array for speed
const b32 = u32(block);
const k32 = u32(k);
const n32 = u32(nonce);
// Make sure that buffers aligned to 4 bytes
const d32 = isAligned32(data) && u32(data);
const o32 = isAligned32(output) && u32(output);
toClean.push(b32);
const len = data.length;
for (let pos = 0, ctr = counter; pos < len; ctr++) {
core(sigma, k32, n32, b32, ctr, rounds);
if (ctr >= 2 ** 32 - 1)
throw new Error('Salsa/ChaCha: counter overflow');
const take = Math.min(blockLen, len - pos);
// full block && aligned to 4 bytes
if (take === blockLen && o32 && d32) {
const pos32 = pos / 4;
if (pos % 4 !== 0)
throw new Error('Salsa/ChaCha: invalid block position');
for (let j = 0; j < blockLen32; j++)
o32[pos32 + j] = d32[pos32 + j] ^ b32[j];
pos += blockLen;
continue;
}
for (let j = 0; j < take; j++)
output[pos + j] = data[pos + j] ^ block[j];
pos += take;
}
for (let i = 0; i < toClean.length; i++)
toClean[i].fill(0);
return output;
};
};
//# sourceMappingURL=_salsa.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"_salsa.js","sourceRoot":"","sources":["../src/_salsa.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,qDAAqD;AACrD,OAAO,MAAM,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8CE;AAEF,MAAM,OAAO,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,OAAO,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;AAChC,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;AAmBhC,gDAAgD;AAChD,MAAM,WAAW,GAAG,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;AAE3D,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAe,EAAE,EAAE;IAC5C,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,QAAQ,EAAE,GACxF,SAAS,CACP,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EACvF,IAAI,CACL,CAAC;IACJ,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACxB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC1B,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC7B,MAAM,UAAU,GAAG,QAAQ,GAAG,CAAC,CAAC;IAChC,IAAI,QAAQ,GAAG,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAC7F,OAAO,CACL,GAAe,EACf,KAAiB,EACjB,IAAgB,EAChB,MAAmB,EACnB,OAAO,GAAG,CAAC,EACC,EAAE;QACd,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC,MAAM;YAAE,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACvB,6BAA6B;QAC7B,uBAAuB;QACvB,+BAA+B;QAC/B,gCAAgC;QAChC,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QAC7F,IAAI,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE;YAC/B,MAAM,IAAI,KAAK,CACb,yBAAyB,MAAM,CAAC,MAAM,2BAA2B,IAAI,CAAC,MAAM,GAAG,CAChF,CAAC;SACH;QACD,MAAM,OAAO,GAAG,EAAE,CAAC;QACnB,IAAI,CAAC,EAAE,KAAK,CAAC;QACb,uBAAuB;QACvB,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,EAAE;YACrB,CAAC,GAAG,GAAG,CAAC;YACR,KAAK,GAAG,UAAU,CAAC;SACpB;aAAM,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,IAAI,eAAe,EAAE;YAC/C,CAAC,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;YACvB,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACX,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACf,KAAK,GAAG,UAAU,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SACjB;;YAAM,MAAM,IAAI,KAAK,CAAC,iDAAiD,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACtF,yCAAyC;QACzC,IAAI,aAAa,EAAE;YACjB,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE;gBACpB,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;YAC/E,CAAC,GAAG,aAAa,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;YACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChB,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;SAC5B;QACD,uBAAuB;QACvB,MAAM,QAAQ,GAAG,EAAE,GAAG,UAAU,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ;YAC3B,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,cAAc,CAAC,CAAC;QACzE,mCAAmC;QACnC,IAAI,QAAQ,KAAK,EAAE,EAAE;YACnB,MAAM,EAAE,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;YAC9B,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC;SAC5B;QACD,oBAAoB;QACpB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC;QACvC,gCAAgC;QAChC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;QACvB,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACnB,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;QACvB,4CAA4C;QAC5C,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;QACxB,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,OAAO,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,EAAE;YACjD,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;YACxC,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;YAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC;YAC3C,mCAAmC;YACnC,IAAI,IAAI,KAAK,QAAQ,IAAI,GAAG,IAAI,GAAG,EAAE;gBACnC,MAAM,KAAK,GAAG,GAAG,GAAG,CAAC,CAAC;gBACtB,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC;oBAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;gBAC3E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE;oBAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC9E,GAAG,IAAI,QAAQ,CAAC;gBAChB,SAAS;aACV;YACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE;gBAAE,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1E,GAAG,IAAI,IAAI,CAAC;SACb;QACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE;YAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5D,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC,CAAC"}
+329
View File
@@ -0,0 +1,329 @@
import { createView, ensureBytes, equalBytes, setBigUint64, u32 } from './utils.js';
import { poly1305 } from './_poly1305.js';
import { salsaBasic } from './_salsa.js';
// ChaCha20 stream cipher was released in 2008. ChaCha aims to increase
// the diffusion per round, but had slightly less cryptanalysis.
// https://cr.yp.to/chacha.html, http://cr.yp.to/chacha/chacha-20080128.pdf
// Left rotate for uint32
const rotl = (a, b) => (a << b) | (a >>> (32 - b));
/**
* ChaCha core function.
*/
// prettier-ignore
function chachaCore(c, k, n, out, cnt, rounds = 20) {
let y00 = c[0], y01 = c[1], y02 = c[2], y03 = c[3]; // "expa" "nd 3" "2-by" "te k"
let y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3]; // Key Key Key Key
let y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7]; // Key Key Key Key
let y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; // Counter Counter Nonce Nonce
// Save state to temporary variables
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
// Main loop
for (let i = 0; i < rounds; i += 2) {
x00 = (x00 + x04) | 0;
x12 = rotl(x12 ^ x00, 16);
x08 = (x08 + x12) | 0;
x04 = rotl(x04 ^ x08, 12);
x00 = (x00 + x04) | 0;
x12 = rotl(x12 ^ x00, 8);
x08 = (x08 + x12) | 0;
x04 = rotl(x04 ^ x08, 7);
x01 = (x01 + x05) | 0;
x13 = rotl(x13 ^ x01, 16);
x09 = (x09 + x13) | 0;
x05 = rotl(x05 ^ x09, 12);
x01 = (x01 + x05) | 0;
x13 = rotl(x13 ^ x01, 8);
x09 = (x09 + x13) | 0;
x05 = rotl(x05 ^ x09, 7);
x02 = (x02 + x06) | 0;
x14 = rotl(x14 ^ x02, 16);
x10 = (x10 + x14) | 0;
x06 = rotl(x06 ^ x10, 12);
x02 = (x02 + x06) | 0;
x14 = rotl(x14 ^ x02, 8);
x10 = (x10 + x14) | 0;
x06 = rotl(x06 ^ x10, 7);
x03 = (x03 + x07) | 0;
x15 = rotl(x15 ^ x03, 16);
x11 = (x11 + x15) | 0;
x07 = rotl(x07 ^ x11, 12);
x03 = (x03 + x07) | 0;
x15 = rotl(x15 ^ x03, 8);
x11 = (x11 + x15) | 0;
x07 = rotl(x07 ^ x11, 7);
x00 = (x00 + x05) | 0;
x15 = rotl(x15 ^ x00, 16);
x10 = (x10 + x15) | 0;
x05 = rotl(x05 ^ x10, 12);
x00 = (x00 + x05) | 0;
x15 = rotl(x15 ^ x00, 8);
x10 = (x10 + x15) | 0;
x05 = rotl(x05 ^ x10, 7);
x01 = (x01 + x06) | 0;
x12 = rotl(x12 ^ x01, 16);
x11 = (x11 + x12) | 0;
x06 = rotl(x06 ^ x11, 12);
x01 = (x01 + x06) | 0;
x12 = rotl(x12 ^ x01, 8);
x11 = (x11 + x12) | 0;
x06 = rotl(x06 ^ x11, 7);
x02 = (x02 + x07) | 0;
x13 = rotl(x13 ^ x02, 16);
x08 = (x08 + x13) | 0;
x07 = rotl(x07 ^ x08, 12);
x02 = (x02 + x07) | 0;
x13 = rotl(x13 ^ x02, 8);
x08 = (x08 + x13) | 0;
x07 = rotl(x07 ^ x08, 7);
x03 = (x03 + x04) | 0;
x14 = rotl(x14 ^ x03, 16);
x09 = (x09 + x14) | 0;
x04 = rotl(x04 ^ x09, 12);
x03 = (x03 + x04) | 0;
x14 = rotl(x14 ^ x03, 8);
x09 = (x09 + x14) | 0;
x04 = rotl(x04 ^ x09, 7);
}
// Write output
let oi = 0;
out[oi++] = (y00 + x00) | 0;
out[oi++] = (y01 + x01) | 0;
out[oi++] = (y02 + x02) | 0;
out[oi++] = (y03 + x03) | 0;
out[oi++] = (y04 + x04) | 0;
out[oi++] = (y05 + x05) | 0;
out[oi++] = (y06 + x06) | 0;
out[oi++] = (y07 + x07) | 0;
out[oi++] = (y08 + x08) | 0;
out[oi++] = (y09 + x09) | 0;
out[oi++] = (y10 + x10) | 0;
out[oi++] = (y11 + x11) | 0;
out[oi++] = (y12 + x12) | 0;
out[oi++] = (y13 + x13) | 0;
out[oi++] = (y14 + x14) | 0;
out[oi++] = (y15 + x15) | 0;
}
/**
* hchacha helper method, used primarily in xchacha, to hash
* key and nonce into key' and nonce'.
* Same as chachaCore, but there doesn't seem to be a way to move the block
* out without 25% performance hit.
*/
// prettier-ignore
export function hchacha(c, key, src, out) {
const k32 = u32(key);
const i32 = u32(src);
const o32 = u32(out);
let x00 = c[0], x01 = c[1], x02 = c[2], x03 = c[3];
let x04 = k32[0], x05 = k32[1], x06 = k32[2], x07 = k32[3];
let x08 = k32[4], x09 = k32[5], x10 = k32[6], x11 = k32[7];
let x12 = i32[0], x13 = i32[1], x14 = i32[2], x15 = i32[3];
for (let i = 0; i < 20; i += 2) {
x00 = (x00 + x04) | 0;
x12 = rotl(x12 ^ x00, 16);
x08 = (x08 + x12) | 0;
x04 = rotl(x04 ^ x08, 12);
x00 = (x00 + x04) | 0;
x12 = rotl(x12 ^ x00, 8);
x08 = (x08 + x12) | 0;
x04 = rotl(x04 ^ x08, 7);
x01 = (x01 + x05) | 0;
x13 = rotl(x13 ^ x01, 16);
x09 = (x09 + x13) | 0;
x05 = rotl(x05 ^ x09, 12);
x01 = (x01 + x05) | 0;
x13 = rotl(x13 ^ x01, 8);
x09 = (x09 + x13) | 0;
x05 = rotl(x05 ^ x09, 7);
x02 = (x02 + x06) | 0;
x14 = rotl(x14 ^ x02, 16);
x10 = (x10 + x14) | 0;
x06 = rotl(x06 ^ x10, 12);
x02 = (x02 + x06) | 0;
x14 = rotl(x14 ^ x02, 8);
x10 = (x10 + x14) | 0;
x06 = rotl(x06 ^ x10, 7);
x03 = (x03 + x07) | 0;
x15 = rotl(x15 ^ x03, 16);
x11 = (x11 + x15) | 0;
x07 = rotl(x07 ^ x11, 12);
x03 = (x03 + x07) | 0;
x15 = rotl(x15 ^ x03, 8);
x11 = (x11 + x15) | 0;
x07 = rotl(x07 ^ x11, 7);
x00 = (x00 + x05) | 0;
x15 = rotl(x15 ^ x00, 16);
x10 = (x10 + x15) | 0;
x05 = rotl(x05 ^ x10, 12);
x00 = (x00 + x05) | 0;
x15 = rotl(x15 ^ x00, 8);
x10 = (x10 + x15) | 0;
x05 = rotl(x05 ^ x10, 7);
x01 = (x01 + x06) | 0;
x12 = rotl(x12 ^ x01, 16);
x11 = (x11 + x12) | 0;
x06 = rotl(x06 ^ x11, 12);
x01 = (x01 + x06) | 0;
x12 = rotl(x12 ^ x01, 8);
x11 = (x11 + x12) | 0;
x06 = rotl(x06 ^ x11, 7);
x02 = (x02 + x07) | 0;
x13 = rotl(x13 ^ x02, 16);
x08 = (x08 + x13) | 0;
x07 = rotl(x07 ^ x08, 12);
x02 = (x02 + x07) | 0;
x13 = rotl(x13 ^ x02, 8);
x08 = (x08 + x13) | 0;
x07 = rotl(x07 ^ x08, 7);
x03 = (x03 + x04) | 0;
x14 = rotl(x14 ^ x03, 16);
x09 = (x09 + x14) | 0;
x04 = rotl(x04 ^ x09, 12);
x03 = (x03 + x04) | 0;
x14 = rotl(x14 ^ x03, 8);
x09 = (x09 + x14) | 0;
x04 = rotl(x04 ^ x09, 7);
}
o32[0] = x00;
o32[1] = x01;
o32[2] = x02;
o32[3] = x03;
o32[4] = x12;
o32[5] = x13;
o32[6] = x14;
o32[7] = x15;
return out;
}
/**
* Original, non-RFC chacha20 from DJB. 8-byte nonce, 8-byte counter.
*/
export const chacha20orig = /* @__PURE__ */ salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 8,
});
/**
* ChaCha stream cipher. Conforms to RFC 8439 (IETF, TLS). 12-byte nonce, 4-byte counter.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
export const chacha20 = /* @__PURE__ */ salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
allow128bitKeys: false,
});
/**
* XChaCha eXtended-nonce ChaCha. 24-byte nonce.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
*/
export const xchacha20 = /* @__PURE__ */ salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 8,
extendNonceFn: hchacha,
allow128bitKeys: false,
});
/**
* Reduced 8-round chacha, described in original paper.
*/
export const chacha8 = /* @__PURE__ */ salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 8,
});
/**
* Reduced 12-round chacha, described in original paper.
*/
export const chacha12 = /* @__PURE__ */ salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 12,
});
const ZERO = /* @__PURE__ */ new Uint8Array(16);
// Pad to digest size with zeros
const updatePadded = (h, msg) => {
h.update(msg);
const left = msg.length % 16;
if (left)
h.update(ZERO.subarray(left));
};
const computeTag = (fn, key, nonce, data, AAD) => {
const authKey = fn(key, nonce, new Uint8Array(32));
const h = poly1305.create(authKey);
if (AAD)
updatePadded(h, AAD);
updatePadded(h, data);
const num = new Uint8Array(16);
const view = createView(num);
setBigUint64(view, 0, BigInt(AAD ? AAD.length : 0), true);
setBigUint64(view, 8, BigInt(data.length), true);
h.update(num);
const res = h.digest();
authKey.fill(0);
return res;
};
/**
* AEAD algorithm from RFC 8439.
* Salsa20 and chacha (RFC 8439) use poly1305 differently.
* We could have composed them similar to:
* https://github.com/paulmillr/scure-base/blob/b266c73dde977b1dd7ef40ef7a23cc15aab526b3/index.ts#L250
* But it's hard because of authKey:
* In salsa20, authKey changes position in salsa stream.
* In chacha, authKey can't be computed inside computeTag, it modifies the counter.
*/
export const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
const tagLength = 16;
ensureBytes(key, 32);
ensureBytes(nonce);
return {
tagLength,
encrypt: (plaintext, output) => {
const plength = plaintext.length;
const clength = plength + tagLength;
if (output) {
ensureBytes(output, clength);
}
else {
output = new Uint8Array(clength);
}
xorStream(key, nonce, plaintext, output, 1);
const tag = computeTag(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
output.set(tag, plength); // append tag
return output;
},
decrypt: (ciphertext, output) => {
const clength = ciphertext.length;
const plength = clength - tagLength;
if (clength < tagLength)
throw new Error(`encrypted data must be at least ${tagLength} bytes`);
if (output) {
ensureBytes(output, plength);
}
else {
output = new Uint8Array(plength);
}
const data = ciphertext.subarray(0, -tagLength);
const passedTag = ciphertext.subarray(-tagLength);
const tag = computeTag(xorStream, key, nonce, data, AAD);
if (!equalBytes(passedTag, tag))
throw new Error('invalid tag');
xorStream(key, nonce, data, output, 1);
return output;
},
};
};
/**
* ChaCha20-Poly1305 from RFC 8439.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
export const chacha20poly1305 = /* @__PURE__ */ _poly1305_aead(chacha20);
/**
* XChaCha20-Poly1305 extended-nonce chacha.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
export const xchacha20poly1305 = /* @__PURE__ */ _poly1305_aead(xchacha20);
//# sourceMappingURL=chacha.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
"use strict";
throw new Error('noble-ciphers have no entry-point: consult README for usage');
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC"}
+9
View File
@@ -0,0 +1,9 @@
{
"type": "module",
"browser": {
"node:crypto": false
},
"node": {
"./crypto": "./esm/cryptoNode.js"
}
}
+207
View File
@@ -0,0 +1,207 @@
import { ensureBytes, u32, equalBytes } from './utils.js';
import { salsaBasic } from './_salsa.js';
import { poly1305 } from './_poly1305.js';
// Salsa20 stream cipher was released in 2005.
// Salsa's goal was to implement AES replacement that does not rely on S-Boxes,
// which are hard to implement in a constant-time manner.
// https://cr.yp.to/snuffle.html, https://cr.yp.to/snuffle/salsafamily-20071225.pdf
// Left rotate for uint32
const rotl = (a, b) => (a << b) | (a >>> (32 - b));
/**
* Salsa20 core function.
*/
// prettier-ignore
function salsaCore(c, k, i, out, cnt, rounds = 20) {
// Based on https://cr.yp.to/salsa20.html
let y00 = c[0], y01 = k[0], y02 = k[1], y03 = k[2]; // "expa" Key Key Key
let y04 = k[3], y05 = c[1], y06 = i[0], y07 = i[1]; // Key "nd 3" Nonce Nonce
let y08 = cnt, y09 = 0, y10 = c[2], y11 = k[4]; // Pos. Pos. "2-by" Key
let y12 = k[5], y13 = k[6], y14 = k[7], y15 = c[3]; // Key Key Key "te k"
// Save state to temporary variables
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
// Main loop
for (let i = 0; i < rounds; i += 2) {
x04 ^= rotl(x00 + x12 | 0, 7);
x08 ^= rotl(x04 + x00 | 0, 9);
x12 ^= rotl(x08 + x04 | 0, 13);
x00 ^= rotl(x12 + x08 | 0, 18);
x09 ^= rotl(x05 + x01 | 0, 7);
x13 ^= rotl(x09 + x05 | 0, 9);
x01 ^= rotl(x13 + x09 | 0, 13);
x05 ^= rotl(x01 + x13 | 0, 18);
x14 ^= rotl(x10 + x06 | 0, 7);
x02 ^= rotl(x14 + x10 | 0, 9);
x06 ^= rotl(x02 + x14 | 0, 13);
x10 ^= rotl(x06 + x02 | 0, 18);
x03 ^= rotl(x15 + x11 | 0, 7);
x07 ^= rotl(x03 + x15 | 0, 9);
x11 ^= rotl(x07 + x03 | 0, 13);
x15 ^= rotl(x11 + x07 | 0, 18);
x01 ^= rotl(x00 + x03 | 0, 7);
x02 ^= rotl(x01 + x00 | 0, 9);
x03 ^= rotl(x02 + x01 | 0, 13);
x00 ^= rotl(x03 + x02 | 0, 18);
x06 ^= rotl(x05 + x04 | 0, 7);
x07 ^= rotl(x06 + x05 | 0, 9);
x04 ^= rotl(x07 + x06 | 0, 13);
x05 ^= rotl(x04 + x07 | 0, 18);
x11 ^= rotl(x10 + x09 | 0, 7);
x08 ^= rotl(x11 + x10 | 0, 9);
x09 ^= rotl(x08 + x11 | 0, 13);
x10 ^= rotl(x09 + x08 | 0, 18);
x12 ^= rotl(x15 + x14 | 0, 7);
x13 ^= rotl(x12 + x15 | 0, 9);
x14 ^= rotl(x13 + x12 | 0, 13);
x15 ^= rotl(x14 + x13 | 0, 18);
}
// Write output
let oi = 0;
out[oi++] = (y00 + x00) | 0;
out[oi++] = (y01 + x01) | 0;
out[oi++] = (y02 + x02) | 0;
out[oi++] = (y03 + x03) | 0;
out[oi++] = (y04 + x04) | 0;
out[oi++] = (y05 + x05) | 0;
out[oi++] = (y06 + x06) | 0;
out[oi++] = (y07 + x07) | 0;
out[oi++] = (y08 + x08) | 0;
out[oi++] = (y09 + x09) | 0;
out[oi++] = (y10 + x10) | 0;
out[oi++] = (y11 + x11) | 0;
out[oi++] = (y12 + x12) | 0;
out[oi++] = (y13 + x13) | 0;
out[oi++] = (y14 + x14) | 0;
out[oi++] = (y15 + x15) | 0;
}
/**
* hsalsa hashing function, used primarily in xsalsa, to hash
* key and nonce into key' and nonce'.
* Same as salsaCore, but there doesn't seem to be a way to move the block
* out without 25% performance hit.
*/
// prettier-ignore
export function hsalsa(c, key, nonce, out) {
const k32 = u32(key);
const i32 = u32(nonce);
const o32 = u32(out);
let x00 = c[0], x01 = k32[0], x02 = k32[1], x03 = k32[2], x04 = k32[3];
let x05 = c[1], x06 = i32[0], x07 = i32[1], x08 = i32[2], x09 = i32[3];
let x10 = c[2], x11 = k32[4], x12 = k32[5], x13 = k32[6], x14 = k32[7];
let x15 = c[3];
// Main loop
for (let i = 0; i < 20; i += 2) {
x04 ^= rotl(x00 + x12 | 0, 7);
x08 ^= rotl(x04 + x00 | 0, 9);
x12 ^= rotl(x08 + x04 | 0, 13);
x00 ^= rotl(x12 + x08 | 0, 18);
x09 ^= rotl(x05 + x01 | 0, 7);
x13 ^= rotl(x09 + x05 | 0, 9);
x01 ^= rotl(x13 + x09 | 0, 13);
x05 ^= rotl(x01 + x13 | 0, 18);
x14 ^= rotl(x10 + x06 | 0, 7);
x02 ^= rotl(x14 + x10 | 0, 9);
x06 ^= rotl(x02 + x14 | 0, 13);
x10 ^= rotl(x06 + x02 | 0, 18);
x03 ^= rotl(x15 + x11 | 0, 7);
x07 ^= rotl(x03 + x15 | 0, 9);
x11 ^= rotl(x07 + x03 | 0, 13);
x15 ^= rotl(x11 + x07 | 0, 18);
x01 ^= rotl(x00 + x03 | 0, 7);
x02 ^= rotl(x01 + x00 | 0, 9);
x03 ^= rotl(x02 + x01 | 0, 13);
x00 ^= rotl(x03 + x02 | 0, 18);
x06 ^= rotl(x05 + x04 | 0, 7);
x07 ^= rotl(x06 + x05 | 0, 9);
x04 ^= rotl(x07 + x06 | 0, 13);
x05 ^= rotl(x04 + x07 | 0, 18);
x11 ^= rotl(x10 + x09 | 0, 7);
x08 ^= rotl(x11 + x10 | 0, 9);
x09 ^= rotl(x08 + x11 | 0, 13);
x10 ^= rotl(x09 + x08 | 0, 18);
x12 ^= rotl(x15 + x14 | 0, 7);
x13 ^= rotl(x12 + x15 | 0, 9);
x14 ^= rotl(x13 + x12 | 0, 13);
x15 ^= rotl(x14 + x13 | 0, 18);
}
o32[0] = x00;
o32[1] = x05;
o32[2] = x10;
o32[3] = x15;
o32[4] = x06;
o32[5] = x07;
o32[6] = x08;
o32[7] = x09;
return out;
}
/**
* Salsa20 from original paper.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
export const salsa20 = /* @__PURE__ */ salsaBasic({ core: salsaCore, counterRight: true });
/**
* xsalsa20 eXtended-nonce salsa.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
export const xsalsa20 = /* @__PURE__ */ salsaBasic({
core: salsaCore,
counterRight: true,
extendNonceFn: hsalsa,
allow128bitKeys: false,
});
/**
* xsalsa20-poly1305 eXtended-nonce salsa.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
* Also known as secretbox from libsodium / nacl.
*/
export const xsalsa20poly1305 = (key, nonce) => {
const tagLength = 16;
ensureBytes(key, 32);
ensureBytes(nonce, 24);
return {
tagLength,
encrypt: (plaintext, output) => {
ensureBytes(plaintext);
// This is small optimization (calculate auth key with same call as encryption itself) makes it hard
// to separate tag calculation and encryption itself, since 32 byte is half-block of salsa (64 byte)
const clength = plaintext.length + 32;
if (output) {
ensureBytes(output, clength);
}
else {
output = new Uint8Array(clength);
}
output.set(plaintext, 32);
xsalsa20(key, nonce, output, output);
const authKey = output.subarray(0, 32);
const tag = poly1305(output.subarray(32), authKey);
// Clean auth key, even though JS provides no guarantees about memory cleaning
output.set(tag, tagLength);
output.subarray(0, tagLength).fill(0);
return output.subarray(tagLength);
},
decrypt: (ciphertext) => {
ensureBytes(ciphertext);
const clength = ciphertext.length;
if (clength < tagLength)
throw new Error('encrypted data should be at least 16 bytes');
// Create new ciphertext array:
// auth tag auth tag from ciphertext ciphertext
// [bytes 0..16] [bytes 16..32] [bytes 32..]
// 16 instead of 32, because we already have 16 byte tag
const ciphertext_ = new Uint8Array(clength + tagLength); // alloc
ciphertext_.set(ciphertext, tagLength);
// Each xsalsa20 calls to hsalsa to calculate key, but seems not much perf difference
// Separate call to calculate authkey, since first bytes contains tag
const authKey = xsalsa20(key, nonce, new Uint8Array(32)); // alloc(32)
const tag = poly1305(ciphertext_.subarray(32), authKey);
if (!equalBytes(ciphertext_.subarray(16, 32), tag))
throw new Error('invalid tag');
const plaintext = xsalsa20(key, nonce, ciphertext_); // alloc
// Clean auth key, even though JS provides no guarantees about memory cleaning
plaintext.subarray(0, 32).fill(0);
authKey.fill(0);
return plaintext.subarray(32);
},
};
};
//# sourceMappingURL=salsa.js.map
+1
View File
File diff suppressed because one or more lines are too long
+58
View File
@@ -0,0 +1,58 @@
import { xchacha20poly1305 } from './chacha.js';
import { xsalsa20poly1305 } from './salsa.js';
import { concatBytes, ensureBytes, utf8ToBytes } from './utils.js';
import { aes_256_gcm } from './webcrypto/aes.js';
import { randomBytes } from './webcrypto/utils.js';
export { utf8ToBytes };
/**
* Alias to xsalsa20poly1305, for compatibility with libsodium / nacl
*/
export function secretbox(key, nonce) {
ensureBytes(key);
ensureBytes(nonce);
const xs = xsalsa20poly1305(key, nonce);
return { seal: xs.encrypt, open: xs.decrypt };
}
export function randomKey() {
return randomBytes(32);
}
/**
* Encrypt plaintext under key with random nonce, using xchacha20poly1305.
* User never touches nonce: it is prepended to ciphertext.
*/
export function encrypt(key, plaintext) {
ensureBytes(key);
const nonce = randomBytes(24);
const ciphertext = xchacha20poly1305(key, nonce).encrypt(plaintext);
return concatBytes(nonce, ciphertext);
}
/**
* Decrypt plaintext under key with random nonce, using xchacha20poly1305.
* User never touches nonce: it is prepended to ciphertext.
*/
export function decrypt(key, ciphertext) {
const nonceLength = 24;
ensureBytes(ciphertext);
if (ciphertext.length <= nonceLength)
throw new Error('invalid ciphertext length');
const nonce = ciphertext.subarray(0, nonceLength);
const ciphertextWithoutNonce = ciphertext.subarray(nonceLength);
return xchacha20poly1305(key, nonce).decrypt(ciphertextWithoutNonce);
}
export async function aes_encrypt(key, plaintext) {
const nonceLength = 12;
ensureBytes(key);
const nonce = randomBytes(nonceLength);
const ciphertext = await aes_256_gcm(key, nonce).encrypt(plaintext);
return concatBytes(nonce, ciphertext);
}
export async function aes_decrypt(key, ciphertext) {
const nonceLength = 12;
ensureBytes(ciphertext);
if (ciphertext.length <= nonceLength)
throw new Error('invalid ciphertext length');
const nonce = ciphertext.subarray(0, nonceLength);
const ciphertextWithoutNonce = ciphertext.subarray(nonceLength);
return aes_256_gcm(key, nonce).decrypt(ciphertextWithoutNonce);
}
//# sourceMappingURL=simple.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"simple.js","sourceRoot":"","sources":["../src/simple.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,GAAe,EAAE,KAAiB;IAC1D,WAAW,CAAC,GAAG,CAAC,CAAC;IACjB,WAAW,CAAC,KAAK,CAAC,CAAC;IACnB,MAAM,EAAE,GAAG,gBAAgB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACxC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC;AACzB,CAAC;AACD;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,GAAe,EAAE,SAAqB;IAC5D,WAAW,CAAC,GAAG,CAAC,CAAC;IACjB,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACpE,OAAO,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,GAAe,EAAE,UAAsB;IAC7D,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,WAAW,CAAC,UAAU,CAAC,CAAC;IACxB,IAAI,UAAU,CAAC,MAAM,IAAI,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IACnF,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAClD,MAAM,sBAAsB,GAAG,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAChE,OAAO,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAe,EAAE,SAAqB;IACtE,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,WAAW,CAAC,GAAG,CAAC,CAAC;IACjB,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACpE,OAAO,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAe,EAAE,UAAsB;IACvE,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,WAAW,CAAC,UAAU,CAAC,CAAC;IACxB,IAAI,UAAU,CAAC,MAAM,IAAI,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IACnF,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAClD,MAAM,sBAAsB,GAAG,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAChE,OAAO,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;AACjE,CAAC"}
+143
View File
@@ -0,0 +1,143 @@
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
const u8a = (a) => a instanceof Uint8Array;
// Cast array to different type
export const u8 = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
export const u16 = (arr) => new Uint16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
export const u32 = (arr) => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
// Cast array to view
export const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
// big-endian hardware is rare. Just in case someone still decides to run ciphers:
// early-throw an error because we don't support BE yet.
export const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
if (!isLE)
throw new Error('Non little-endian hardware is not supported');
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
export function bytesToHex(bytes) {
if (!u8a(bytes))
throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
}
/**
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
*/
export function hexToBytes(hex) {
if (typeof hex !== 'string')
throw new Error('hex string expected, got ' + typeof hex);
const len = hex.length;
if (len % 2)
throw new Error('padded hex string expected, got unpadded hex of length ' + len);
const array = new Uint8Array(len / 2);
for (let i = 0; i < array.length; i++) {
const j = i * 2;
const hexByte = hex.slice(j, j + 2);
const byte = Number.parseInt(hexByte, 16);
if (Number.isNaN(byte) || byte < 0)
throw new Error('Invalid byte sequence');
array[i] = byte;
}
return array;
}
// There is no setImmediate in browser and setTimeout is slow.
// call of async fn will return Promise, which will be fullfiled only on
// next scheduler queue processing step and this is exactly what we need.
export const nextTick = async () => { };
// Returns control to thread each 'tick' ms to avoid blocking
export async function asyncLoop(iters, tick, cb) {
let ts = Date.now();
for (let i = 0; i < iters; i++) {
cb(i);
// Date.now() is not monotonic, so in case if clock goes backwards we return return control too
const diff = Date.now() - ts;
if (diff >= 0 && diff < tick)
continue;
await nextTick();
ts += diff;
}
}
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
export function utf8ToBytes(str) {
if (typeof str !== 'string')
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
export function bytesToUtf8(bytes) {
return new TextDecoder().decode(bytes);
}
/**
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
* Warning: when Uint8Array is passed, it would NOT get copied.
* Keep in mind for future mutable operations.
*/
export function toBytes(data) {
if (typeof data === 'string')
data = utf8ToBytes(data);
if (!u8a(data))
throw new Error(`expected Uint8Array, got ${typeof data}`);
return data;
}
/**
* Copies several Uint8Arrays into one.
*/
export function concatBytes(...arrays) {
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
let pad = 0; // walk through each item, ensure they have proper type
arrays.forEach((a) => {
if (!u8a(a))
throw new Error('Uint8Array expected');
r.set(a, pad);
pad += a.length;
});
return r;
}
// Check if object doens't have custom constructor (like Uint8Array/Array)
const isPlainObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]' && obj.constructor === Object;
export function checkOpts(defaults, opts) {
if (opts !== undefined && (typeof opts !== 'object' || !isPlainObject(opts)))
throw new Error('options must be object or undefined');
const merged = Object.assign(defaults, opts);
return merged;
}
export function ensureBytes(b, len) {
if (!(b instanceof Uint8Array))
throw new Error('Uint8Array expected');
if (typeof len === 'number')
if (b.length !== len)
throw new Error(`Uint8Array length ${len} expected`);
}
// Constant-time equality
export function equalBytes(a, b) {
// Should not happen
if (a.length !== b.length)
throw new Error('equalBytes: Different size of Uint8Arrays');
let isSame = true;
for (let i = 0; i < a.length; i++)
isSame && (isSame = a[i] === b[i]); // Lets hope JIT won't optimize away.
return isSame;
}
// For runtime check if class implements interface
export class Hash {
}
// Polyfill for Safari 14
export function setBigUint64(view, byteOffset, value, isLE) {
if (typeof view.setBigUint64 === 'function')
return view.setBigUint64(byteOffset, value, isLE);
const _32n = BigInt(32);
const _u32_max = BigInt(0xffffffff);
const wh = Number((value >> _32n) & _u32_max);
const wl = Number(value & _u32_max);
const h = isLE ? 4 : 0;
const l = isLE ? 0 : 4;
view.setUint32(byteOffset + h, wh, isLE);
view.setUint32(byteOffset + l, wl, isLE);
}
//# sourceMappingURL=utils.js.map
+1
View File
File diff suppressed because one or more lines are too long
+42
View File
@@ -0,0 +1,42 @@
import { ensureBytes } from '../utils.js';
import { getWebcryptoSubtle } from './utils.js';
function generate(algo, length) {
const keyLength = length / 8;
const keyParams = { name: algo, length };
const cryptParams = { name: algo };
// const params: Record<string, any> = ({ e: algo, i: { name: algo, length } });
return (key, nonce) => {
ensureBytes(key, keyLength);
if (algo === 'AES-CTR') {
cryptParams.counter = nonce;
cryptParams.length = 64;
}
else {
cryptParams.iv = nonce;
}
return {
keyLength,
async encrypt(plaintext) {
ensureBytes(plaintext);
const cr = getWebcryptoSubtle();
const iKey = await cr.importKey('raw', key, keyParams, true, ['encrypt']);
const cipher = await cr.encrypt(cryptParams, iKey, plaintext);
return new Uint8Array(cipher);
},
async decrypt(ciphertext) {
ensureBytes(ciphertext);
const cr = getWebcryptoSubtle();
const iKey = await cr.importKey('raw', key, keyParams, true, ['decrypt']);
const plaintext = await cr.decrypt(cryptParams, iKey, ciphertext);
return new Uint8Array(plaintext);
},
};
};
}
export const aes_128_ctr = generate('AES-CTR', 128);
export const aes_256_ctr = generate('AES-CTR', 256);
export const aes_128_cbc = generate('AES-CBC', 128);
export const aes_256_cbc = generate('AES-CBC', 256);
export const aes_128_gcm = generate('AES-GCM', 128);
export const aes_256_gcm = generate('AES-GCM', 256);
//# sourceMappingURL=aes.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"aes.js","sourceRoot":"","sources":["../../src/webcrypto/aes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc;IAC5C,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,CAAC;IAC7B,MAAM,SAAS,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACzC,MAAM,WAAW,GAAwB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxD,gFAAgF;IAEhF,OAAO,CAAC,GAAe,EAAE,KAAiB,EAAE,EAAE;QAC5C,WAAW,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC5B,IAAI,IAAI,KAAK,SAAS,EAAE;YACtB,WAAW,CAAC,OAAO,GAAG,KAAK,CAAC;YAC5B,WAAW,CAAC,MAAM,GAAG,EAAE,CAAC;SACzB;aAAM;YACL,WAAW,CAAC,EAAE,GAAG,KAAK,CAAC;SACxB;QAED,OAAO;YACL,SAAS;YAET,KAAK,CAAC,OAAO,CAAC,SAAqB;gBACjC,WAAW,CAAC,SAAS,CAAC,CAAC;gBACvB,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAAC;gBAChC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC1E,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;gBAC9D,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;YAChC,CAAC;YAED,KAAK,CAAC,OAAO,CAAC,UAAsB;gBAClC,WAAW,CAAC,UAAU,CAAC,CAAC;gBACxB,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAAC;gBAChC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC1E,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;gBAClE,OAAO,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;YACnC,CAAC;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AAEpD,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AAEpD,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC"}
+2
View File
@@ -0,0 +1,2 @@
export const crypto = typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
//# sourceMappingURL=crypto.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/webcrypto/crypto.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,MAAM,GACjB,OAAO,UAAU,KAAK,QAAQ,IAAI,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC"}
+7
View File
@@ -0,0 +1,7 @@
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
// See utils.ts for details.
// The file will throw on node.js 14 and earlier.
// @ts-ignore
import * as nc from 'node:crypto';
export const crypto = nc && typeof nc === 'object' && 'webcrypto' in nc ? nc.webcrypto : undefined;
//# sourceMappingURL=cryptoNode.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"cryptoNode.js","sourceRoot":"","sources":["../../src/webcrypto/cryptoNode.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,4BAA4B;AAC5B,iDAAiD;AACjD,aAAa;AACb,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,MAAM,CAAC,MAAM,MAAM,GACjB,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,WAAW,IAAI,EAAE,CAAC,CAAC,CAAE,EAAE,CAAC,SAAiB,CAAC,CAAC,CAAC,SAAS,CAAC"}
+171
View File
@@ -0,0 +1,171 @@
import { getWebcryptoSubtle } from './utils.js';
// Format-preserving encryption algorithm (FPE-FF1) specified in NIST Special Publication 800-38G.
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38G.pdf
// Utils
function toBytesBE(num, length) {
let hex = num.toString(16);
hex = hex.length & 1 ? `0${hex}` : hex;
if (length)
hex = hex.padStart(length * 2, '00');
const len = hex.length / 2;
const u8 = new Uint8Array(len);
for (let j = 0, i = 0; i < hex.length && i < len * 2; i += 2, j++)
u8[j] = parseInt(hex[i] + hex[i + 1], 16);
return u8;
}
function fromBytesBE(bytes) {
let value = 0n;
for (let i = bytes.length - 1, j = 0; i >= 0; i--, j++)
value += (BigInt(bytes[i]) & 255n) << (8n * BigInt(j));
return value;
}
function mod(a, b) {
const result = a % b;
return result >= 0 ? result : b + result;
}
// AES stuff
const BLOCK_LEN = 16;
const IV = new Uint8Array(BLOCK_LEN);
export async function encryptBlock(msg, key) {
if (key.length !== 16 && key.length !== 32)
throw new Error('Invalid key length');
const cr = getWebcryptoSubtle();
const mode = { name: `AES-CBC`, length: key.length * 8 };
const wKey = await cr.importKey('raw', key, mode, true, ['encrypt']);
const cipher = await cr.encrypt({ name: `aes-cbc`, iv: IV, counter: IV, length: 64 }, wKey, msg);
return new Uint8Array(cipher).subarray(0, 16);
}
function NUMradix(radix, data) {
let res = 0n;
for (let i of data)
res = res * BigInt(radix) + BigInt(i);
return res;
}
async function getRound(radix, key, tweak, x) {
if (radix > 2 ** 16 - 1)
throw new Error(`Invalid radix: ${radix}`);
// radix**minlen ≥ 100
const minLen = Math.ceil(Math.log(100) / Math.log(radix));
const maxLen = 2 ** 32 - 1;
// 2 ≤ minlen ≤ maxlen < 2**32
if (2 > minLen || minLen > maxLen || maxLen >= 2 ** 32)
throw new Error('Invalid radix: 2 ≤ minlen ≤ maxlen < 2**32');
if (x.length < minLen || x.length > maxLen)
throw new Error('X is outside minLen..maxLen bounds');
const u = Math.floor(x.length / 2);
const v = x.length - u;
const b = Math.ceil(Math.ceil(v * Math.log2(radix)) / 8);
const d = 4 * Math.ceil(b / 4) + 4;
const padding = mod(-tweak.length - b - 1, 16);
// P = [1]1 || [2]1 || [1]1 || [radix]3 || [10]1 || [u mod 256]1 || [n]4 || [t]4.
const P = new Uint8Array([1, 2, 1, 0, 0, 0, 10, u, 0, 0, 0, 0, 0, 0, 0, 0]);
const view = new DataView(P.buffer);
view.setUint16(4, radix, false);
view.setUint32(8, x.length, false);
view.setUint32(12, tweak.length, false);
// Q = T || [0](tb1) mod 16 || [i]1 || [NUMradix(B)]b.
const PQ = new Uint8Array(P.length + tweak.length + padding + 1 + b);
PQ.set(P);
P.fill(0);
PQ.set(tweak, P.length);
const round = async (A, B, i, decrypt = false) => {
// Q = ... || [i]1 || [NUMradix(B)]b.
PQ[PQ.length - b - 1] = i;
if (b)
PQ.set(toBytesBE(NUMradix(radix, B), b), PQ.length - b);
// PRF
let r = new Uint8Array(16);
for (let j = 0; j < PQ.length / BLOCK_LEN; j++) {
for (let i = 0; i < BLOCK_LEN; i++)
r[i] ^= PQ[j * BLOCK_LEN + i];
r.set(await encryptBlock(r, key));
}
// Let S be the first d bytes of the following string of ⎡d/16⎤ blocks:
// R || CIPHK(R ⊕[1]16) || CIPHK(R ⊕[2]16) ...CIPHK(R ⊕[⎡d / 16⎤ 1]16).
let s = Array.from(r);
for (let j = 1; s.length < d; j++) {
const block = toBytesBE(BigInt(j), 16);
for (let k = 0; k < BLOCK_LEN; k++)
block[k] ^= r[k];
s.push(...Array.from(await encryptBlock(block, key)));
}
let y = fromBytesBE(Uint8Array.from(s.slice(0, d)));
s.fill(0);
if (decrypt)
y = -y;
const m = i % 2 === 0 ? u : v;
let c = mod(NUMradix(radix, A) + y, BigInt(radix) ** BigInt(m));
// STR(radix, m, c)
const C = Array(m).fill(0);
for (let i = 0; i < m; i++, c /= BigInt(radix))
C[m - 1 - i] = Number(c % BigInt(radix));
A.fill(0);
A = B;
B = C;
return [A, B];
};
const destroy = () => PQ.fill(0);
return { u, round, destroy };
}
const EMPTY_BUF = new Uint8Array([]);
export function FF1(radix, key, tweak = EMPTY_BUF) {
const PQ = getRound.bind(null, radix, key, tweak);
return {
async encrypt(x) {
const { u, round, destroy } = await PQ(x);
let [A, B] = [x.slice(0, u), x.slice(u)];
for (let i = 0; i < 10; i++)
[A, B] = await round(A, B, i);
destroy();
const res = A.concat(B);
A.fill(0);
B.fill(0);
return res;
},
async decrypt(x) {
const { u, round, destroy } = await PQ(x);
// The FF1.Decrypt algorithm is similar to the FF1.Encrypt algorithm;
// the differences are in Step 6, where:
// 1) the order of the indices is reversed,
// 2) the roles of A and B are swapped
// 3) modular addition is replaced by modular subtraction, in Step 6vi.
let [B, A] = [x.slice(0, u), x.slice(u)];
for (let i = 9; i >= 0; i--)
[A, B] = await round(A, B, i, true);
destroy();
const res = B.concat(A);
A.fill(0);
B.fill(0);
return res;
},
};
}
// Binary string which encodes each byte in little-endian byte order
const binLE = {
encode(bytes) {
const x = [];
for (let i = 0; i < bytes.length; i++) {
for (let j = 0, tmp = bytes[i]; j < 8; j++, tmp >>= 1)
x.push(tmp & 1);
}
return x;
},
decode(b) {
if (b.length % 8)
throw new Error('Invalid binary string');
const res = new Uint8Array(b.length / 8);
for (let i = 0, j = 0; i < res.length; i++) {
res[i] = b[j++] | (b[j++] << 1) | (b[j++] << 2) | (b[j++] << 3);
res[i] |= (b[j++] << 4) | (b[j++] << 5) | (b[j++] << 6) | (b[j++] << 7);
}
return res;
},
};
export function BinaryFF1(key, tweak = EMPTY_BUF) {
const ff1 = FF1(2, key, tweak);
return {
encrypt: async (x) => binLE.decode(await ff1.encrypt(binLE.encode(x))),
decrypt: async (x) => binLE.decode(await ff1.decrypt(binLE.encode(x))),
};
}
//# sourceMappingURL=ff1.js.map
File diff suppressed because one or more lines are too long
+122
View File
@@ -0,0 +1,122 @@
import { createView, setBigUint64 } from '../utils.js';
import { polyval } from '../_polyval.js';
import { getWebcryptoSubtle } from './utils.js';
/**
* AES-GCM-SIV: classic AES-GCM with nonce-misuse resistance.
* RFC 8452, https://datatracker.ietf.org/doc/html/rfc8452
*/
// AES stuff (same as ff1)
const BLOCK_LEN = 16;
const IV = new Uint8Array(BLOCK_LEN);
async function encryptBlock(msg, key) {
if (key.length !== 16 && key.length !== 32)
throw new Error('Invalid key length');
const mode = { name: `AES-CBC`, length: key.length * 8 };
const cr = getWebcryptoSubtle();
const wKey = await cr.importKey('raw', key, mode, true, ['encrypt']);
const cipher = await cr.encrypt({ name: `aes-cbc`, iv: IV, counter: IV, length: 64 }, wKey, msg);
return new Uint8Array(cipher).subarray(0, 16);
}
// Kinda constant-time equality
function equalBytes(a, b) {
// Should not happen
if (a.length !== b.length)
throw new Error('equalBytes: Different size of Uint8Arrays');
let flag = true;
for (let i = 0; i < a.length; i++)
if (a[i] !== b[i])
flag && (flag = false);
return flag;
}
// Wrap position so it will be in padded to blockSize
const wrapPos = (pos, blockSize) => Math.ceil(pos / blockSize) * blockSize;
const limit = (name, min, max) => (value) => {
if (!Number.isSafeInteger(value) || min > value || value > max)
throw new Error(`${name}: invalid value=${value}, must be [${min}..${max}]`);
};
// From RFC 8452: Section 6
const AAD_LIMIT = limit('AAD', 0, 2 ** 36);
const PLAIN_LIMIT = limit('Plaintext', 0, 2 ** 36);
const NONCE_LIMIT = limit('Nonce', 12, 12);
const CIPHER_LIMIT = limit('Ciphertext', 16, 2 ** 36 + 16);
// nodejs api doesn't support 32bit counters, browser does
async function ctr(key, tag, input) {
// The initial counter block is the tag with the most significant bit of the last byte set to one.
let block = tag.slice();
block[15] |= 0x80;
let view = createView(block);
let output = new Uint8Array(input.length);
for (let pos = 0; pos < input.length;) {
const encryptedBlock = await encryptBlock(block, key);
view.setUint32(0, view.getUint32(0, true) + 1, true);
const take = Math.min(input.length, encryptedBlock.length);
for (let j = 0; j < take; j++, pos++)
output[pos] = encryptedBlock[j] ^ input[pos];
}
return new Uint8Array(output);
}
export async function deriveKeys(key, nonce) {
NONCE_LIMIT(nonce.length);
const len = key.length;
if (len !== 16 && len !== 32)
throw new Error(`key length must be 16 or 32 bytes, got: ${len} bytes`);
const encKey = new Uint8Array(len);
const authKey = new Uint8Array(16);
let counter = 0;
const deriveBlock = new Uint8Array(nonce.length + 4);
deriveBlock.set(nonce, 4);
const view = createView(deriveBlock);
for (const derivedKey of [authKey, encKey]) {
for (let i = 0; i < derivedKey.length; i += 8) {
view.setUint32(0, counter++, true);
const block = await encryptBlock(deriveBlock, key);
derivedKey.set(block.subarray(0, 8), i);
}
}
return { authKey, encKey };
}
export async function aes_256_gcm_siv(key, nonce, AAD) {
const { encKey, authKey } = await deriveKeys(key, nonce);
const computeTag = async (data, AAD) => {
const dataPos = wrapPos(AAD.length, 16);
const lenPos = wrapPos(dataPos + data.length, 16);
const block = new Uint8Array(lenPos + 16);
const view = createView(block);
block.set(AAD);
block.set(data, dataPos);
setBigUint64(view, lenPos, BigInt(AAD.length * 8), true);
setBigUint64(view, lenPos + 8, BigInt(data.length * 8), true);
// Compute the expected tag by XORing S_s and the nonce, clearing the
// most significant bit of the last byte and encrypting with the
// message-encryption key.
const tag = polyval(authKey, block);
for (let i = 0; i < 12; i++)
tag[i] ^= nonce[i];
// Clear the highest bit
tag[15] &= 0x7f;
return await encryptBlock(tag, encKey);
};
return {
// computeTag,
encrypt: async (plaintext) => {
AAD_LIMIT(AAD.length);
PLAIN_LIMIT(plaintext.length);
const tag = await computeTag(plaintext, AAD);
const out = new Uint8Array(plaintext.length + 16);
out.set(tag, plaintext.length);
out.set(await ctr(encKey, tag, plaintext));
return out;
},
decrypt: async (ciphertext) => {
CIPHER_LIMIT(ciphertext.length);
AAD_LIMIT(AAD.length);
const tag = ciphertext.subarray(-16);
const plaintext = await ctr(encKey, tag, ciphertext.subarray(0, -16));
const expectedTag = await computeTag(plaintext, AAD);
if (!equalBytes(tag, expectedTag))
throw new Error('invalid poly1305 tag');
return plaintext;
},
};
}
//# sourceMappingURL=siv.js.map
File diff suppressed because one or more lines are too long
+23
View File
@@ -0,0 +1,23 @@
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
// node.js versions earlier than v19 don't declare it in global scope.
// For node.js, package.js on#exports field mapping rewrites import
// from `crypto` to `cryptoNode`, which imports native module.
// Makes the utils un-importable in browsers without a bundler.
// Once node.js 18 is deprecated, we can just drop the import.
import { crypto } from '@noble/ciphers/webcrypto/crypto';
/**
* Secure PRNG. Uses `crypto.getRandomValues`, which defers to OS.
*/
export function randomBytes(bytesLength = 32) {
if (crypto && typeof crypto.getRandomValues === 'function') {
return crypto.getRandomValues(new Uint8Array(bytesLength));
}
throw new Error('crypto.getRandomValues must be defined');
}
export function getWebcryptoSubtle() {
if (crypto && typeof crypto.subtle === 'object' && crypto.subtle != null) {
return crypto.subtle;
}
throw new Error('crypto.subtle must be defined');
}
//# sourceMappingURL=utils.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/webcrypto/utils.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,sEAAsE;AACtE,mEAAmE;AACnE,8DAA8D;AAC9D,+DAA+D;AAC/D,8DAA8D;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;AAEzD;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,WAAW,GAAG,EAAE;IAC1C,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,eAAe,KAAK,UAAU,EAAE;QAC1D,OAAO,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;KAC5D;IACD,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE;QACxE,OAAO,MAAM,CAAC,MAAM,CAAC;KACtB;IACD,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;AACnD,CAAC"}
+1
View File
@@ -0,0 +1 @@
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":""}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
throw new Error('noble-ciphers have no entry-point: consult README for usage');
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":";AAAA,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC"}
+136
View File
@@ -0,0 +1,136 @@
{
"name": "@noble/ciphers",
"version": "0.2.0",
"description": "Auditable & minimal JS implementation of Salsa20, ChaCha, Poly1305 & AES-SIV",
"files": [
"esm",
"src",
"webcrypto",
"*.js",
"*.js.map",
"*.d.ts",
"*.d.ts.map"
],
"scripts": {
"bench": "node benchmark/aead.js noble && node benchmark/ciphers.js noble",
"bench:all": "node benchmark/{aead,ciphers,poly}.js",
"bench:install": "cd benchmark && npm install && cd ../../",
"build": "npm run build:clean; tsc && tsc -p tsconfig.esm.json",
"build:release": "cd build; npm i; npm run build",
"build:clean": "rm *.{js,d.ts,js.map,d.ts.map} esm/*.{js,d.ts,js.map,d.ts.map} 2> /dev/null; rm -r esm/webcrypto 2> /dev/null",
"lint": "prettier --check 'src/**/*.{js,ts}' 'test/**/*.{js,ts,mjs}'",
"format": "prettier --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts,mjs}'",
"test": "node test/index.js"
},
"author": "Paul Miller (https://paulmillr.com)",
"homepage": "https://paulmillr.com/noble/",
"repository": {
"type": "git",
"url": "https://github.com/paulmillr/noble-ciphers.git"
},
"license": "MIT",
"devDependencies": {
"@scure/base": "1.1.1",
"fast-check": "3.0.0",
"micro-bmark": "0.3.1",
"micro-should": "0.4.0",
"prettier": "2.8.4",
"typescript": "5.0.2"
},
"main": "index.js",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./esm/index.js",
"default": "./index.js"
},
"./webcrypto/crypto": {
"types": "./webcrypto/crypto.d.ts",
"node": {
"import": "./esm/webcrypto/cryptoNode.js",
"default": "./webcrypto/cryptoNode.js"
},
"import": "./esm/webcrypto/crypto.js",
"default": "./webcrypto/crypto.js"
},
"./_micro": {
"types": "./_micro.d.ts",
"import": "./esm/_micro.js",
"default": "./_micro.js"
},
"./_poly1305": {
"types": "./_poly1305.d.ts",
"import": "./esm/_poly1305.js",
"default": "./_poly1305.js"
},
"./chacha": {
"types": "./chacha.d.ts",
"import": "./esm/chacha.js",
"default": "./chacha.js"
},
"./salsa": {
"types": "./salsa.d.ts",
"import": "./esm/salsa.js",
"default": "./salsa.js"
},
"./simple": {
"types": "./simple.d.ts",
"import": "./esm/simple.js",
"default": "./simple.js"
},
"./utils": {
"types": "./utils.d.ts",
"import": "./esm/utils.js",
"default": "./utils.js"
},
"./index": {
"types": "./index.d.ts",
"import": "./esm/index.js",
"default": "./index.js"
},
"./webcrypto/aes": {
"types": "./webcrypto/aes.d.ts",
"import": "./esm/webcrypto/aes.js",
"default": "./webcrypto/aes.js"
},
"./webcrypto/siv": {
"types": "./webcrypto/siv.d.ts",
"import": "./esm/webcrypto/siv.js",
"default": "./webcrypto/siv.js"
},
"./webcrypto/ff1": {
"types": "./webcrypto/ff1.d.ts",
"import": "./esm/webcrypto/ff1.js",
"default": "./webcrypto/ff1.js"
},
"./webcrypto/utils": {
"types": "./webcrypto/utils.d.ts",
"import": "./esm/webcrypto/utils.js",
"default": "./webcrypto/utils.js"
}
},
"browser": {
"node:crypto": false,
"./webcrypto/crypto": "./webcrypto/crypto.js"
},
"keywords": [
"salsa20",
"chacha",
"aes",
"cryptography",
"crypto",
"noble",
"cipher",
"ciphers",
"xsalsa20",
"xchacha20",
"poly1305",
"xsalsa20poly1305",
"chacha20poly1305",
"xchacha20poly1305",
"secretbox",
"rijndael",
"siv"
],
"funding": "https://paulmillr.com/funding/"
}
+25
View File
@@ -0,0 +1,25 @@
import { Cipher } from './utils.js';
/**
* hsalsa hashing function, used primarily in xsalsa, to hash
* key and nonce into key' and nonce'.
* Same as salsaCore, but there doesn't seem to be a way to move the block
* out without 25% performance hit.
*/
export declare function hsalsa(c: Uint32Array, key: Uint8Array, nonce: Uint8Array, out: Uint8Array): Uint8Array;
/**
* Salsa20 from original paper.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
export declare const salsa20: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* xsalsa20 eXtended-nonce salsa.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
export declare const xsalsa20: (key: Uint8Array, nonce: Uint8Array, data: Uint8Array, output?: Uint8Array | undefined, counter?: number) => Uint8Array;
/**
* xsalsa20-poly1305 eXtended-nonce salsa.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
* Also known as secretbox from libsodium / nacl.
*/
export declare const xsalsa20poly1305: (key: Uint8Array, nonce: Uint8Array) => Cipher;
//# sourceMappingURL=salsa.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"salsa.d.ts","sourceRoot":"","sources":["src/salsa.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,MAAM,EAAE,MAAM,YAAY,CAAC;AA4DlE;;;;;GAKG;AAEH,wBAAgB,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,GAAG,UAAU,CAoCtG;AAED;;;GAGG;AACH,eAAO,MAAM,OAAO,yHAAsE,CAAC;AAE3F;;;GAGG;AACH,eAAO,MAAM,QAAQ,yHAKnB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAS,UAAU,SAAS,UAAU,KAAG,MAgDrE,CAAC"}
+212
View File
@@ -0,0 +1,212 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.xsalsa20poly1305 = exports.xsalsa20 = exports.salsa20 = exports.hsalsa = void 0;
const utils_js_1 = require("./utils.js");
const _salsa_js_1 = require("./_salsa.js");
const _poly1305_js_1 = require("./_poly1305.js");
// Salsa20 stream cipher was released in 2005.
// Salsa's goal was to implement AES replacement that does not rely on S-Boxes,
// which are hard to implement in a constant-time manner.
// https://cr.yp.to/snuffle.html, https://cr.yp.to/snuffle/salsafamily-20071225.pdf
// Left rotate for uint32
const rotl = (a, b) => (a << b) | (a >>> (32 - b));
/**
* Salsa20 core function.
*/
// prettier-ignore
function salsaCore(c, k, i, out, cnt, rounds = 20) {
// Based on https://cr.yp.to/salsa20.html
let y00 = c[0], y01 = k[0], y02 = k[1], y03 = k[2]; // "expa" Key Key Key
let y04 = k[3], y05 = c[1], y06 = i[0], y07 = i[1]; // Key "nd 3" Nonce Nonce
let y08 = cnt, y09 = 0, y10 = c[2], y11 = k[4]; // Pos. Pos. "2-by" Key
let y12 = k[5], y13 = k[6], y14 = k[7], y15 = c[3]; // Key Key Key "te k"
// Save state to temporary variables
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
// Main loop
for (let i = 0; i < rounds; i += 2) {
x04 ^= rotl(x00 + x12 | 0, 7);
x08 ^= rotl(x04 + x00 | 0, 9);
x12 ^= rotl(x08 + x04 | 0, 13);
x00 ^= rotl(x12 + x08 | 0, 18);
x09 ^= rotl(x05 + x01 | 0, 7);
x13 ^= rotl(x09 + x05 | 0, 9);
x01 ^= rotl(x13 + x09 | 0, 13);
x05 ^= rotl(x01 + x13 | 0, 18);
x14 ^= rotl(x10 + x06 | 0, 7);
x02 ^= rotl(x14 + x10 | 0, 9);
x06 ^= rotl(x02 + x14 | 0, 13);
x10 ^= rotl(x06 + x02 | 0, 18);
x03 ^= rotl(x15 + x11 | 0, 7);
x07 ^= rotl(x03 + x15 | 0, 9);
x11 ^= rotl(x07 + x03 | 0, 13);
x15 ^= rotl(x11 + x07 | 0, 18);
x01 ^= rotl(x00 + x03 | 0, 7);
x02 ^= rotl(x01 + x00 | 0, 9);
x03 ^= rotl(x02 + x01 | 0, 13);
x00 ^= rotl(x03 + x02 | 0, 18);
x06 ^= rotl(x05 + x04 | 0, 7);
x07 ^= rotl(x06 + x05 | 0, 9);
x04 ^= rotl(x07 + x06 | 0, 13);
x05 ^= rotl(x04 + x07 | 0, 18);
x11 ^= rotl(x10 + x09 | 0, 7);
x08 ^= rotl(x11 + x10 | 0, 9);
x09 ^= rotl(x08 + x11 | 0, 13);
x10 ^= rotl(x09 + x08 | 0, 18);
x12 ^= rotl(x15 + x14 | 0, 7);
x13 ^= rotl(x12 + x15 | 0, 9);
x14 ^= rotl(x13 + x12 | 0, 13);
x15 ^= rotl(x14 + x13 | 0, 18);
}
// Write output
let oi = 0;
out[oi++] = (y00 + x00) | 0;
out[oi++] = (y01 + x01) | 0;
out[oi++] = (y02 + x02) | 0;
out[oi++] = (y03 + x03) | 0;
out[oi++] = (y04 + x04) | 0;
out[oi++] = (y05 + x05) | 0;
out[oi++] = (y06 + x06) | 0;
out[oi++] = (y07 + x07) | 0;
out[oi++] = (y08 + x08) | 0;
out[oi++] = (y09 + x09) | 0;
out[oi++] = (y10 + x10) | 0;
out[oi++] = (y11 + x11) | 0;
out[oi++] = (y12 + x12) | 0;
out[oi++] = (y13 + x13) | 0;
out[oi++] = (y14 + x14) | 0;
out[oi++] = (y15 + x15) | 0;
}
/**
* hsalsa hashing function, used primarily in xsalsa, to hash
* key and nonce into key' and nonce'.
* Same as salsaCore, but there doesn't seem to be a way to move the block
* out without 25% performance hit.
*/
// prettier-ignore
function hsalsa(c, key, nonce, out) {
const k32 = (0, utils_js_1.u32)(key);
const i32 = (0, utils_js_1.u32)(nonce);
const o32 = (0, utils_js_1.u32)(out);
let x00 = c[0], x01 = k32[0], x02 = k32[1], x03 = k32[2], x04 = k32[3];
let x05 = c[1], x06 = i32[0], x07 = i32[1], x08 = i32[2], x09 = i32[3];
let x10 = c[2], x11 = k32[4], x12 = k32[5], x13 = k32[6], x14 = k32[7];
let x15 = c[3];
// Main loop
for (let i = 0; i < 20; i += 2) {
x04 ^= rotl(x00 + x12 | 0, 7);
x08 ^= rotl(x04 + x00 | 0, 9);
x12 ^= rotl(x08 + x04 | 0, 13);
x00 ^= rotl(x12 + x08 | 0, 18);
x09 ^= rotl(x05 + x01 | 0, 7);
x13 ^= rotl(x09 + x05 | 0, 9);
x01 ^= rotl(x13 + x09 | 0, 13);
x05 ^= rotl(x01 + x13 | 0, 18);
x14 ^= rotl(x10 + x06 | 0, 7);
x02 ^= rotl(x14 + x10 | 0, 9);
x06 ^= rotl(x02 + x14 | 0, 13);
x10 ^= rotl(x06 + x02 | 0, 18);
x03 ^= rotl(x15 + x11 | 0, 7);
x07 ^= rotl(x03 + x15 | 0, 9);
x11 ^= rotl(x07 + x03 | 0, 13);
x15 ^= rotl(x11 + x07 | 0, 18);
x01 ^= rotl(x00 + x03 | 0, 7);
x02 ^= rotl(x01 + x00 | 0, 9);
x03 ^= rotl(x02 + x01 | 0, 13);
x00 ^= rotl(x03 + x02 | 0, 18);
x06 ^= rotl(x05 + x04 | 0, 7);
x07 ^= rotl(x06 + x05 | 0, 9);
x04 ^= rotl(x07 + x06 | 0, 13);
x05 ^= rotl(x04 + x07 | 0, 18);
x11 ^= rotl(x10 + x09 | 0, 7);
x08 ^= rotl(x11 + x10 | 0, 9);
x09 ^= rotl(x08 + x11 | 0, 13);
x10 ^= rotl(x09 + x08 | 0, 18);
x12 ^= rotl(x15 + x14 | 0, 7);
x13 ^= rotl(x12 + x15 | 0, 9);
x14 ^= rotl(x13 + x12 | 0, 13);
x15 ^= rotl(x14 + x13 | 0, 18);
}
o32[0] = x00;
o32[1] = x05;
o32[2] = x10;
o32[3] = x15;
o32[4] = x06;
o32[5] = x07;
o32[6] = x08;
o32[7] = x09;
return out;
}
exports.hsalsa = hsalsa;
/**
* Salsa20 from original paper.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
exports.salsa20 = (0, _salsa_js_1.salsaBasic)({ core: salsaCore, counterRight: true });
/**
* xsalsa20 eXtended-nonce salsa.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
exports.xsalsa20 = (0, _salsa_js_1.salsaBasic)({
core: salsaCore,
counterRight: true,
extendNonceFn: hsalsa,
allow128bitKeys: false,
});
/**
* xsalsa20-poly1305 eXtended-nonce salsa.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
* Also known as secretbox from libsodium / nacl.
*/
const xsalsa20poly1305 = (key, nonce) => {
const tagLength = 16;
(0, utils_js_1.ensureBytes)(key, 32);
(0, utils_js_1.ensureBytes)(nonce, 24);
return {
tagLength,
encrypt: (plaintext, output) => {
(0, utils_js_1.ensureBytes)(plaintext);
// This is small optimization (calculate auth key with same call as encryption itself) makes it hard
// to separate tag calculation and encryption itself, since 32 byte is half-block of salsa (64 byte)
const clength = plaintext.length + 32;
if (output) {
(0, utils_js_1.ensureBytes)(output, clength);
}
else {
output = new Uint8Array(clength);
}
output.set(plaintext, 32);
(0, exports.xsalsa20)(key, nonce, output, output);
const authKey = output.subarray(0, 32);
const tag = (0, _poly1305_js_1.poly1305)(output.subarray(32), authKey);
// Clean auth key, even though JS provides no guarantees about memory cleaning
output.set(tag, tagLength);
output.subarray(0, tagLength).fill(0);
return output.subarray(tagLength);
},
decrypt: (ciphertext) => {
(0, utils_js_1.ensureBytes)(ciphertext);
const clength = ciphertext.length;
if (clength < tagLength)
throw new Error('encrypted data should be at least 16 bytes');
// Create new ciphertext array:
// auth tag auth tag from ciphertext ciphertext
// [bytes 0..16] [bytes 16..32] [bytes 32..]
// 16 instead of 32, because we already have 16 byte tag
const ciphertext_ = new Uint8Array(clength + tagLength); // alloc
ciphertext_.set(ciphertext, tagLength);
// Each xsalsa20 calls to hsalsa to calculate key, but seems not much perf difference
// Separate call to calculate authkey, since first bytes contains tag
const authKey = (0, exports.xsalsa20)(key, nonce, new Uint8Array(32)); // alloc(32)
const tag = (0, _poly1305_js_1.poly1305)(ciphertext_.subarray(32), authKey);
if (!(0, utils_js_1.equalBytes)(ciphertext_.subarray(16, 32), tag))
throw new Error('invalid tag');
const plaintext = (0, exports.xsalsa20)(key, nonce, ciphertext_); // alloc
// Clean auth key, even though JS provides no guarantees about memory cleaning
plaintext.subarray(0, 32).fill(0);
authKey.fill(0);
return plaintext.subarray(32);
},
};
};
exports.xsalsa20poly1305 = xsalsa20poly1305;
//# sourceMappingURL=salsa.js.map
+1
View File
File diff suppressed because one or more lines are too long
+23
View File
@@ -0,0 +1,23 @@
import { utf8ToBytes } from './utils.js';
export { utf8ToBytes };
/**
* Alias to xsalsa20poly1305, for compatibility with libsodium / nacl
*/
export declare function secretbox(key: Uint8Array, nonce: Uint8Array): {
seal: (plaintext: Uint8Array) => Uint8Array;
open: (ciphertext: Uint8Array) => Uint8Array;
};
export declare function randomKey(): Uint8Array;
/**
* Encrypt plaintext under key with random nonce, using xchacha20poly1305.
* User never touches nonce: it is prepended to ciphertext.
*/
export declare function encrypt(key: Uint8Array, plaintext: Uint8Array): Uint8Array;
/**
* Decrypt plaintext under key with random nonce, using xchacha20poly1305.
* User never touches nonce: it is prepended to ciphertext.
*/
export declare function decrypt(key: Uint8Array, ciphertext: Uint8Array): Uint8Array;
export declare function aes_encrypt(key: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array>;
export declare function aes_decrypt(key: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array>;
//# sourceMappingURL=simple.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"simple.d.ts","sourceRoot":"","sources":["src/simple.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4B,WAAW,EAAE,MAAM,YAAY,CAAC;AAInE,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;;;EAK3D;AAED,wBAAgB,SAAS,IAAI,UAAU,CAEtC;AACD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,UAAU,CAK1E;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,GAAG,UAAU,CAO3E;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAM7F;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAO9F"}
+67
View File
@@ -0,0 +1,67 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.aes_decrypt = exports.aes_encrypt = exports.decrypt = exports.encrypt = exports.randomKey = exports.secretbox = exports.utf8ToBytes = void 0;
const chacha_js_1 = require("./chacha.js");
const salsa_js_1 = require("./salsa.js");
const utils_js_1 = require("./utils.js");
Object.defineProperty(exports, "utf8ToBytes", { enumerable: true, get: function () { return utils_js_1.utf8ToBytes; } });
const aes_js_1 = require("./webcrypto/aes.js");
const utils_js_2 = require("./webcrypto/utils.js");
/**
* Alias to xsalsa20poly1305, for compatibility with libsodium / nacl
*/
function secretbox(key, nonce) {
(0, utils_js_1.ensureBytes)(key);
(0, utils_js_1.ensureBytes)(nonce);
const xs = (0, salsa_js_1.xsalsa20poly1305)(key, nonce);
return { seal: xs.encrypt, open: xs.decrypt };
}
exports.secretbox = secretbox;
function randomKey() {
return (0, utils_js_2.randomBytes)(32);
}
exports.randomKey = randomKey;
/**
* Encrypt plaintext under key with random nonce, using xchacha20poly1305.
* User never touches nonce: it is prepended to ciphertext.
*/
function encrypt(key, plaintext) {
(0, utils_js_1.ensureBytes)(key);
const nonce = (0, utils_js_2.randomBytes)(24);
const ciphertext = (0, chacha_js_1.xchacha20poly1305)(key, nonce).encrypt(plaintext);
return (0, utils_js_1.concatBytes)(nonce, ciphertext);
}
exports.encrypt = encrypt;
/**
* Decrypt plaintext under key with random nonce, using xchacha20poly1305.
* User never touches nonce: it is prepended to ciphertext.
*/
function decrypt(key, ciphertext) {
const nonceLength = 24;
(0, utils_js_1.ensureBytes)(ciphertext);
if (ciphertext.length <= nonceLength)
throw new Error('invalid ciphertext length');
const nonce = ciphertext.subarray(0, nonceLength);
const ciphertextWithoutNonce = ciphertext.subarray(nonceLength);
return (0, chacha_js_1.xchacha20poly1305)(key, nonce).decrypt(ciphertextWithoutNonce);
}
exports.decrypt = decrypt;
async function aes_encrypt(key, plaintext) {
const nonceLength = 12;
(0, utils_js_1.ensureBytes)(key);
const nonce = (0, utils_js_2.randomBytes)(nonceLength);
const ciphertext = await (0, aes_js_1.aes_256_gcm)(key, nonce).encrypt(plaintext);
return (0, utils_js_1.concatBytes)(nonce, ciphertext);
}
exports.aes_encrypt = aes_encrypt;
async function aes_decrypt(key, ciphertext) {
const nonceLength = 12;
(0, utils_js_1.ensureBytes)(ciphertext);
if (ciphertext.length <= nonceLength)
throw new Error('invalid ciphertext length');
const nonce = ciphertext.subarray(0, nonceLength);
const ciphertextWithoutNonce = ciphertext.subarray(nonceLength);
return (0, aes_js_1.aes_256_gcm)(key, nonce).decrypt(ciphertextWithoutNonce);
}
exports.aes_decrypt = aes_decrypt;
//# sourceMappingURL=simple.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"simple.js","sourceRoot":"","sources":["src/simple.ts"],"names":[],"mappings":";;;AAAA,2CAAgD;AAChD,yCAA8C;AAC9C,yCAAmE;AAI1D,4FAJ0B,sBAAW,OAI1B;AAHpB,+CAAiD;AACjD,mDAAmD;AAInD;;GAEG;AACH,SAAgB,SAAS,CAAC,GAAe,EAAE,KAAiB;IAC1D,IAAA,sBAAW,EAAC,GAAG,CAAC,CAAC;IACjB,IAAA,sBAAW,EAAC,KAAK,CAAC,CAAC;IACnB,MAAM,EAAE,GAAG,IAAA,2BAAgB,EAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACxC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC;AAChD,CAAC;AALD,8BAKC;AAED,SAAgB,SAAS;IACvB,OAAO,IAAA,sBAAW,EAAC,EAAE,CAAC,CAAC;AACzB,CAAC;AAFD,8BAEC;AACD;;;GAGG;AACH,SAAgB,OAAO,CAAC,GAAe,EAAE,SAAqB;IAC5D,IAAA,sBAAW,EAAC,GAAG,CAAC,CAAC;IACjB,MAAM,KAAK,GAAG,IAAA,sBAAW,EAAC,EAAE,CAAC,CAAC;IAC9B,MAAM,UAAU,GAAG,IAAA,6BAAiB,EAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACpE,OAAO,IAAA,sBAAW,EAAC,KAAK,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AALD,0BAKC;AAED;;;GAGG;AACH,SAAgB,OAAO,CAAC,GAAe,EAAE,UAAsB;IAC7D,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,IAAA,sBAAW,EAAC,UAAU,CAAC,CAAC;IACxB,IAAI,UAAU,CAAC,MAAM,IAAI,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IACnF,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAClD,MAAM,sBAAsB,GAAG,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAChE,OAAO,IAAA,6BAAiB,EAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;AACvE,CAAC;AAPD,0BAOC;AAEM,KAAK,UAAU,WAAW,CAAC,GAAe,EAAE,SAAqB;IACtE,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,IAAA,sBAAW,EAAC,GAAG,CAAC,CAAC;IACjB,MAAM,KAAK,GAAG,IAAA,sBAAW,EAAC,WAAW,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,MAAM,IAAA,oBAAW,EAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACpE,OAAO,IAAA,sBAAW,EAAC,KAAK,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AAND,kCAMC;AAEM,KAAK,UAAU,WAAW,CAAC,GAAe,EAAE,UAAsB;IACvE,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,IAAA,sBAAW,EAAC,UAAU,CAAC,CAAC;IACxB,IAAI,UAAU,CAAC,MAAM,IAAI,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IACnF,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAClD,MAAM,sBAAsB,GAAG,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAChE,OAAO,IAAA,oBAAW,EAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;AACjE,CAAC;AAPD,kCAOC"}
+42
View File
@@ -0,0 +1,42 @@
function number(n: number) {
if (!Number.isSafeInteger(n) || n < 0) throw new Error(`Wrong positive integer: ${n}`);
}
function bool(b: boolean) {
if (typeof b !== 'boolean') throw new Error(`Expected boolean, not ${b}`);
}
function bytes(b: Uint8Array | undefined, ...lengths: number[]) {
if (!(b instanceof Uint8Array)) throw new Error('Expected Uint8Array');
if (lengths.length > 0 && !lengths.includes(b.length))
throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
}
export type Hash = {
(data: Uint8Array): Uint8Array;
blockLen: number;
outputLen: number;
create: any;
};
function hash(hash: Hash) {
if (typeof hash !== 'function' || typeof hash.create !== 'function')
throw new Error('hash must be wrapped by utils.wrapConstructor');
number(hash.outputLen);
number(hash.blockLen);
}
function exists(instance: any, checkFinished = true) {
if (instance.destroyed) throw new Error('Hash instance has been destroyed');
if (checkFinished && instance.finished) throw new Error('Hash#digest() has already been called');
}
function output(out: any, instance: any) {
bytes(out);
const min = instance.outputLen;
if (out.length < min) {
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
}
}
export { number, bool, bytes, hash, exists, output };
const assert = { number, bool, bytes, hash, exists, output };
export default assert;
+313
View File
@@ -0,0 +1,313 @@
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
// micro-noble-ciphers: more auditable, but slower version of salsa20, chacha & poly1305.
// Implements the same algorithms that are present in other files,
// but without unrolled loops (https://en.wikipedia.org/wiki/Loop_unrolling).
import * as u from './utils.js';
import { salsaBasic } from './_salsa.js';
// Utils
function hexToNumber(hex: string): bigint {
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
// Big Endian
return BigInt(hex === '' ? '0' : `0x${hex}`);
}
function bytesToNumberLE(bytes: Uint8Array): bigint {
return hexToNumber(u.bytesToHex(Uint8Array.from(bytes).reverse()));
}
function numberToBytesLE(n: number | bigint, len: number): Uint8Array {
return u.hexToBytes(n.toString(16).padStart(len * 2, '0')).reverse();
}
const rotl = (a: number, b: number) => (a << b) | (a >>> (32 - b));
// /Utils
function salsaQR(x: Uint32Array, a: number, b: number, c: number, d: number) {
x[b] ^= rotl((x[a] + x[d]) | 0, 7);
x[c] ^= rotl((x[b] + x[a]) | 0, 9);
x[d] ^= rotl((x[c] + x[b]) | 0, 13);
x[a] ^= rotl((x[d] + x[c]) | 0, 18);
}
// prettier-ignore
function chachaQR(x: Uint32Array, a: number, b: number, c: number, d: number) {
x[a] = (x[a] + x[b]) | 0; x[d] = rotl(x[d] ^ x[a], 16);
x[c] = (x[c] + x[d]) | 0; x[b] = rotl(x[b] ^ x[c], 12);
x[a] = (x[a] + x[b]) | 0; x[d] = rotl(x[d] ^ x[a], 8);
x[c] = (x[c] + x[d]) | 0; x[b] = rotl(x[b] ^ x[c], 7);
}
function salsaRound(x: Uint32Array, rounds = 20) {
for (let i = 0; i < rounds; i += 2) {
salsaQR(x, 0, 4, 8, 12);
salsaQR(x, 5, 9, 13, 1);
salsaQR(x, 10, 14, 2, 6);
salsaQR(x, 15, 3, 7, 11);
salsaQR(x, 0, 1, 2, 3);
salsaQR(x, 5, 6, 7, 4);
salsaQR(x, 10, 11, 8, 9);
salsaQR(x, 15, 12, 13, 14);
}
}
function chachaRound(x: Uint32Array, rounds = 20) {
for (let i = 0; i < rounds; i += 2) {
chachaQR(x, 0, 4, 8, 12);
chachaQR(x, 1, 5, 9, 13);
chachaQR(x, 2, 6, 10, 14);
chachaQR(x, 3, 7, 11, 15);
chachaQR(x, 0, 5, 10, 15);
chachaQR(x, 1, 6, 11, 12);
chachaQR(x, 2, 7, 8, 13);
chachaQR(x, 3, 4, 9, 14);
}
}
function salsaCore(
c: Uint32Array,
k: Uint32Array,
n: Uint32Array,
out: Uint32Array,
cnt: number,
rounds = 20
): void {
// prettier-ignore
const y = new Uint32Array([
c[0], k[0], k[1], k[2], // "expa" Key Key Key
k[3], c[1], n[0], n[1], // Key "nd 3" Nonce Nonce
cnt, 0 , c[2], k[4], // Pos. Pos. "2-by" Key
k[5], k[6], k[7], c[3], // Key Key Key "te k"
]);
const x = y.slice();
salsaRound(x, rounds);
for (let i = 0; i < 16; i++) out[i] = (y[i] + x[i]) | 0;
}
export function hsalsa(c: Uint32Array, key: Uint8Array, nonce: Uint8Array): Uint8Array {
const k = u.u32(key);
const i = u.u32(nonce);
// prettier-ignore
const x = new Uint32Array([
c[0], k[0], k[1], k[2],
k[3], c[1], i[0], i[1],
i[2], i[3], c[2], k[4],
k[5], k[6], k[7], c[3]
]);
salsaRound(x);
return u.u8(new Uint32Array([x[0], x[5], x[10], x[15], x[6], x[7], x[8], x[9]]));
}
function chachaCore(
c: Uint32Array,
k: Uint32Array,
n: Uint32Array,
out: Uint32Array,
cnt: number,
rounds = 20
): void {
// prettier-ignore
const y = new Uint32Array([
c[0], c[1], c[2], c[3], // "expa" "nd 3" "2-by" "te k"
k[0], k[1], k[2], k[3], // Key Key Key Key
k[4], k[5], k[6], k[7], // Key Key Key Key
cnt, n[0], n[1], n[2], // Counter Counter Nonce Nonce
]);
const x = y.slice();
chachaRound(x, rounds);
for (let i = 0; i < 16; i++) out[i] = (y[i] + x[i]) | 0;
}
export function hchacha(c: Uint32Array, key: Uint8Array, nonce: Uint8Array): Uint8Array {
const k = u.u32(key);
const i = u.u32(nonce);
// prettier-ignore
const x = new Uint32Array([
c[0], c[1], c[2], c[3],
k[0], k[1], k[2], k[3],
k[4], k[5], k[6], k[7],
i[0], i[1], i[2], i[3],
]);
chachaRound(x);
return u.u8(new Uint32Array([x[0], x[1], x[2], x[3], x[12], x[13], x[14], x[15]]));
}
/**
* salsa20, 12-byte nonce.
*/
export const salsa20 = salsaBasic({ core: salsaCore, counterRight: true });
/**
* xsalsa20, 24-byte nonce.
*/
export const xsalsa20 = salsaBasic({
core: salsaCore,
counterRight: true,
extendNonceFn: hsalsa,
allow128bitKeys: false,
});
/**
* chacha20 non-RFC, original version by djb. 8-byte nonce, 8-byte counter.
*/
export const chacha20orig = salsaBasic({ core: chachaCore, counterRight: false, counterLen: 8 });
/**
* chacha20 RFC 8439 (IETF / TLS). 12-byte nonce, 4-byte counter.
*/
export const chacha20 = salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
allow128bitKeys: false,
});
/**
* xchacha20 eXtended-nonce. https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
*/
export const xchacha20 = salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 8,
extendNonceFn: hchacha,
allow128bitKeys: false,
});
/**
* 8-round chacha from the original paper.
*/
export const chacha8 = salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 8,
});
/**
* 12-round chacha from the original paper.
*/
export const chacha12 = salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 12,
});
const POW_2_130_5 = 2n ** 130n - 5n;
const POW_2_128_1 = 2n ** (16n * 8n) - 1n;
// Can be speed-up using BigUint64Array, but would be more complicated
export function poly1305(msg: Uint8Array, key: Uint8Array): Uint8Array {
u.ensureBytes(msg);
u.ensureBytes(key);
let acc = 0n;
const r = bytesToNumberLE(key.subarray(0, 16)) & 0x0ffffffc0ffffffc0ffffffc0fffffffn;
const s = bytesToNumberLE(key.subarray(16));
// Process by 16 byte chunks
for (let i = 0; i < msg.length; i += 16) {
const m = msg.subarray(i, i + 16);
const n = bytesToNumberLE(m) | (1n << BigInt(8 * m.length));
acc = ((acc + n) * r) % POW_2_130_5;
}
const res = (acc + s) & POW_2_128_1;
return numberToBytesLE(res, 16);
}
function computeTag(
fn: typeof chacha20,
key: Uint8Array,
nonce: Uint8Array,
ciphertext: Uint8Array,
AAD?: Uint8Array
): Uint8Array {
const res = [];
if (AAD) {
res.push(AAD);
const leftover = AAD.length % 16;
if (leftover > 0) res.push(new Uint8Array(16 - leftover));
}
res.push(ciphertext);
const leftover = ciphertext.length % 16;
if (leftover > 0) res.push(new Uint8Array(16 - leftover));
// Lengths
const num = new Uint8Array(16);
const view = u.createView(num);
u.setBigUint64(view, 0, BigInt(AAD ? AAD.length : 0), true);
u.setBigUint64(view, 8, BigInt(ciphertext.length), true);
res.push(num);
const authKey = fn(key, nonce, new Uint8Array(32));
return poly1305(u.concatBytes(...res), authKey);
}
/**
* xsalsa20-poly1305 eXtended-nonce (24 bytes) salsa.
*/
export function xsalsa20poly1305(key: Uint8Array, nonce: Uint8Array) {
u.ensureBytes(key);
u.ensureBytes(nonce);
return {
encrypt: (plaintext: Uint8Array) => {
u.ensureBytes(plaintext);
const m = u.concatBytes(new Uint8Array(32), plaintext);
const c = xsalsa20(key, nonce, m);
const authKey = c.subarray(0, 32);
const data = c.subarray(32);
const tag = poly1305(data, authKey);
return u.concatBytes(tag, data);
},
decrypt: (ciphertext: Uint8Array) => {
u.ensureBytes(ciphertext);
if (ciphertext.length < 16) throw new Error('encrypted data must be at least 16 bytes');
const c = u.concatBytes(new Uint8Array(16), ciphertext);
const authKey = xsalsa20(key, nonce, new Uint8Array(32));
const tag = poly1305(c.subarray(32), authKey);
if (!u.equalBytes(c.subarray(16, 32), tag)) throw new Error('invalid poly1305 tag');
return xsalsa20(key, nonce, c).subarray(32);
},
};
}
/**
* Alias to xsalsa20-poly1305
*/
export function secretbox(key: Uint8Array, nonce: Uint8Array) {
u.ensureBytes(key);
u.ensureBytes(nonce);
const xs = xsalsa20poly1305(key, nonce);
return { seal: xs.encrypt, open: xs.decrypt };
}
export const _poly1305_aead =
(fn: typeof chacha20) =>
(key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array): u.Cipher => {
const tagLength = 16;
const keyLength = 32;
u.ensureBytes(key, keyLength);
u.ensureBytes(nonce);
return {
tagLength,
encrypt: (plaintext: Uint8Array) => {
u.ensureBytes(plaintext);
const res = fn(key, nonce, plaintext, undefined, 1);
const tag = computeTag(fn, key, nonce, res, AAD);
return u.concatBytes(res, tag);
},
decrypt: (ciphertext: Uint8Array) => {
u.ensureBytes(ciphertext);
if (ciphertext.length < tagLength)
throw new Error(`encrypted data must be at least ${tagLength} bytes`);
const passedTag = ciphertext.subarray(-tagLength);
const data = ciphertext.subarray(0, -tagLength);
const tag = computeTag(fn, key, nonce, data, AAD);
if (!u.equalBytes(passedTag, tag)) throw new Error('invalid poly1305 tag');
return fn(key, nonce, data, undefined, 1);
},
};
};
/**
* chacha20-poly1305 12-byte-nonce chacha.
*/
export const chacha20poly1305 = _poly1305_aead(chacha20);
/**
* xchacha20-poly1305 eXtended-nonce (24 bytes) chacha.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
export const xchacha20poly1305 = _poly1305_aead(xchacha20);
+286
View File
@@ -0,0 +1,286 @@
import { toBytes, Input, ensureBytes, Hash } from './utils.js';
import assert from './_assert.js';
// Poly1305 is a fast and parallel secret-key message-authentication code.
// https://cr.yp.to/mac.html, https://cr.yp.to/mac/poly1305-20050329.pdf
// https://datatracker.ietf.org/doc/html/rfc8439
// Based on Public Domain poly1305-donna https://github.com/floodyberry/poly1305-donna
const u8to16 = (a: Uint8Array, i: number) => (a[i++] & 0xff) | ((a[i++] & 0xff) << 8);
class Poly1305 implements Hash<Poly1305> {
readonly blockLen = 16;
readonly outputLen = 16;
private buffer = new Uint8Array(16);
private r = new Uint16Array(10);
private h = new Uint16Array(10);
private pad = new Uint16Array(8);
private pos = 0;
protected finished = false;
constructor(key: Input) {
key = toBytes(key);
ensureBytes(key, 32);
const t0 = u8to16(key, 0);
const t1 = u8to16(key, 2);
const t2 = u8to16(key, 4);
const t3 = u8to16(key, 6);
const t4 = u8to16(key, 8);
const t5 = u8to16(key, 10);
const t6 = u8to16(key, 12);
const t7 = u8to16(key, 14);
// https://github.com/floodyberry/poly1305-donna/blob/e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781/poly1305-donna-16.h#L47
this.r[0] = t0 & 0x1fff;
this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03;
this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff;
this.r[5] = (t4 >>> 1) & 0x1ffe;
this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81;
this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
this.r[9] = (t7 >>> 5) & 0x007f;
for (let i = 0; i < 8; i++) this.pad[i] = u8to16(key, 16 + 2 * i);
}
private process(data: Uint8Array, offset: number, isLast = false) {
const hibit = isLast ? 0 : 1 << 11;
const { h, r } = this;
const r0 = r[0];
const r1 = r[1];
const r2 = r[2];
const r3 = r[3];
const r4 = r[4];
const r5 = r[5];
const r6 = r[6];
const r7 = r[7];
const r8 = r[8];
const r9 = r[9];
const t0 = u8to16(data, offset + 0);
const t1 = u8to16(data, offset + 2);
const t2 = u8to16(data, offset + 4);
const t3 = u8to16(data, offset + 6);
const t4 = u8to16(data, offset + 8);
const t5 = u8to16(data, offset + 10);
const t6 = u8to16(data, offset + 12);
const t7 = u8to16(data, offset + 14);
let h0 = h[0] + (t0 & 0x1fff);
let h1 = h[1] + (((t0 >>> 13) | (t1 << 3)) & 0x1fff);
let h2 = h[2] + (((t1 >>> 10) | (t2 << 6)) & 0x1fff);
let h3 = h[3] + (((t2 >>> 7) | (t3 << 9)) & 0x1fff);
let h4 = h[4] + (((t3 >>> 4) | (t4 << 12)) & 0x1fff);
let h5 = h[5] + ((t4 >>> 1) & 0x1fff);
let h6 = h[6] + (((t4 >>> 14) | (t5 << 2)) & 0x1fff);
let h7 = h[7] + (((t5 >>> 11) | (t6 << 5)) & 0x1fff);
let h8 = h[8] + (((t6 >>> 8) | (t7 << 8)) & 0x1fff);
let h9 = h[9] + ((t7 >>> 5) | hibit);
let c = 0;
let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
c = d0 >>> 13;
d0 &= 0x1fff;
d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
c += d0 >>> 13;
d0 &= 0x1fff;
let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
c = d1 >>> 13;
d1 &= 0x1fff;
d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
c += d1 >>> 13;
d1 &= 0x1fff;
let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
c = d2 >>> 13;
d2 &= 0x1fff;
d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
c += d2 >>> 13;
d2 &= 0x1fff;
let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
c = d3 >>> 13;
d3 &= 0x1fff;
d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
c += d3 >>> 13;
d3 &= 0x1fff;
let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
c = d4 >>> 13;
d4 &= 0x1fff;
d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
c += d4 >>> 13;
d4 &= 0x1fff;
let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
c = d5 >>> 13;
d5 &= 0x1fff;
d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
c += d5 >>> 13;
d5 &= 0x1fff;
let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
c = d6 >>> 13;
d6 &= 0x1fff;
d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
c += d6 >>> 13;
d6 &= 0x1fff;
let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
c = d7 >>> 13;
d7 &= 0x1fff;
d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
c += d7 >>> 13;
d7 &= 0x1fff;
let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
c = d8 >>> 13;
d8 &= 0x1fff;
d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
c += d8 >>> 13;
d8 &= 0x1fff;
let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
c = d9 >>> 13;
d9 &= 0x1fff;
d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
c += d9 >>> 13;
d9 &= 0x1fff;
c = ((c << 2) + c) | 0;
c = (c + d0) | 0;
d0 = c & 0x1fff;
c = c >>> 13;
d1 += c;
h[0] = d0;
h[1] = d1;
h[2] = d2;
h[3] = d3;
h[4] = d4;
h[5] = d5;
h[6] = d6;
h[7] = d7;
h[8] = d8;
h[9] = d9;
}
private finalize() {
const { h, pad } = this;
const g = new Uint16Array(10);
let c = h[1] >>> 13;
h[1] &= 0x1fff;
for (let i = 2; i < 10; i++) {
h[i] += c;
c = h[i] >>> 13;
h[i] &= 0x1fff;
}
h[0] += c * 5;
c = h[0] >>> 13;
h[0] &= 0x1fff;
h[1] += c;
c = h[1] >>> 13;
h[1] &= 0x1fff;
h[2] += c;
g[0] = h[0] + 5;
c = g[0] >>> 13;
g[0] &= 0x1fff;
for (let i = 1; i < 10; i++) {
g[i] = h[i] + c;
c = g[i] >>> 13;
g[i] &= 0x1fff;
}
g[9] -= 1 << 13;
let mask = (c ^ 1) - 1;
for (let i = 0; i < 10; i++) g[i] &= mask;
mask = ~mask;
for (let i = 0; i < 10; i++) h[i] = (h[i] & mask) | g[i];
h[0] = (h[0] | (h[1] << 13)) & 0xffff;
h[1] = ((h[1] >>> 3) | (h[2] << 10)) & 0xffff;
h[2] = ((h[2] >>> 6) | (h[3] << 7)) & 0xffff;
h[3] = ((h[3] >>> 9) | (h[4] << 4)) & 0xffff;
h[4] = ((h[4] >>> 12) | (h[5] << 1) | (h[6] << 14)) & 0xffff;
h[5] = ((h[6] >>> 2) | (h[7] << 11)) & 0xffff;
h[6] = ((h[7] >>> 5) | (h[8] << 8)) & 0xffff;
h[7] = ((h[8] >>> 8) | (h[9] << 5)) & 0xffff;
let f = h[0] + pad[0];
h[0] = f & 0xffff;
for (let i = 1; i < 8; i++) {
f = (((h[i] + pad[i]) | 0) + (f >>> 16)) | 0;
h[i] = f & 0xffff;
}
}
update(data: Input): this {
assert.exists(this);
const { buffer, blockLen } = this;
data = toBytes(data);
const len = data.length;
for (let pos = 0; pos < len; ) {
const take = Math.min(blockLen - this.pos, len - pos);
// Fast path: we have at least one block in input
if (take === blockLen) {
for (; blockLen <= len - pos; pos += blockLen) this.process(data, pos);
continue;
}
buffer.set(data.subarray(pos, pos + take), this.pos);
this.pos += take;
pos += take;
if (this.pos === blockLen) {
this.process(buffer, 0, false);
this.pos = 0;
}
}
return this;
}
destroy() {
this.h.fill(0);
this.r.fill(0);
this.buffer.fill(0);
this.pad.fill(0);
}
digestInto(out: Uint8Array) {
assert.exists(this);
assert.output(out, this);
this.finished = true;
const { buffer, h } = this;
let { pos } = this;
if (pos) {
buffer[pos++] = 1;
// buffer.subarray(pos).fill(0);
for (; pos < 16; pos++) buffer[pos] = 0;
this.process(buffer, 0, true);
}
this.finalize();
let opos = 0;
for (let i = 0; i < 8; i++) {
out[opos++] = h[i] >>> 0;
out[opos++] = h[i] >>> 8;
}
return out;
}
digest(): Uint8Array {
const { buffer, outputLen } = this;
this.digestInto(buffer);
const res = buffer.slice(0, outputLen);
this.destroy();
return res;
}
}
export type CHash = ReturnType<typeof wrapConstructorWithKey>;
export function wrapConstructorWithKey<H extends Hash<H>>(hashCons: (key: Input) => Hash<H>) {
const hashC = (msg: Input, key: Input): Uint8Array => hashCons(key).update(toBytes(msg)).digest();
const tmp = hashCons(new Uint8Array(32));
hashC.outputLen = tmp.outputLen;
hashC.blockLen = tmp.blockLen;
hashC.create = (key: Input) => hashCons(key);
return hashC;
}
export const poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
+107
View File
@@ -0,0 +1,107 @@
import { u8, u32, ensureBytes } from './utils.js';
// AES-SIV polyval, little-endian "mirror image" of AES-GCM GHash
// polynomial hash function. Defined in RFC 8452.
// Reverse bits in u32, constant-time, precompute will be faster, but non-constant time
function rev32(x: number) {
x = ((x & 0x5555_5555) << 1) | ((x >>> 1) & 0x5555_5555);
x = ((x & 0x3333_3333) << 2) | ((x >>> 2) & 0x3333_3333);
x = ((x & 0x0f0f_0f0f) << 4) | ((x >>> 4) & 0x0f0f_0f0f);
x = ((x & 0x00ff_00ff) << 8) | ((x >>> 8) & 0x00ff_00ff);
return (x << 16) | (x >>> 16);
}
// wrapped 32 bit multiplication
const wrapMul = (a: number, b: number) => Math.imul(a, b) >>> 0;
// https://timtaubert.de/blog/2017/06/verified-binary-multiplication-for-ghash/
function bmul32(x: number, y: number) {
const x0 = x & 0x1111_1111;
const x1 = x & 0x2222_2222;
const x2 = x & 0x4444_4444;
const x3 = x & 0x8888_8888;
const y0 = y & 0x1111_1111;
const y1 = y & 0x2222_2222;
const y2 = y & 0x4444_4444;
const y3 = y & 0x8888_8888;
let res = (wrapMul(x0, y0) ^ wrapMul(x1, y3) ^ wrapMul(x2, y2) ^ wrapMul(x3, y1)) & 0x1111_1111;
res |= (wrapMul(x0, y1) ^ wrapMul(x1, y0) ^ wrapMul(x2, y3) ^ wrapMul(x3, y2)) & 0x2222_2222;
res |= (wrapMul(x0, y2) ^ wrapMul(x1, y1) ^ wrapMul(x2, y0) ^ wrapMul(x3, y3)) & 0x4444_4444;
res |= (wrapMul(x0, y3) ^ wrapMul(x1, y2) ^ wrapMul(x2, y1) ^ wrapMul(x3, y0)) & 0x8888_8888;
return res >>> 0;
}
function mulPart(arr: Uint32Array) {
const a = new Uint32Array(18);
a[0] = arr[0];
a[1] = arr[1];
a[2] = arr[2];
a[3] = arr[3];
a[4] = a[0] ^ a[1];
a[5] = a[2] ^ a[3];
a[6] = a[0] ^ a[2];
a[7] = a[1] ^ a[3];
a[8] = a[6] ^ a[7];
a[9] = rev32(arr[0]);
a[10] = rev32(arr[1]);
a[11] = rev32(arr[2]);
a[12] = rev32(arr[3]);
a[13] = a[9] ^ a[10];
a[14] = a[11] ^ a[12];
a[15] = a[9] ^ a[11];
a[16] = a[10] ^ a[12];
a[17] = a[15] ^ a[16];
return a;
}
export function polyval(h: Uint8Array, data: Uint8Array) {
ensureBytes(h);
ensureBytes(data);
const s = new Uint32Array(4);
// Precompute for multiplication
const a = mulPart(u32(h));
if (data.length % 16) throw new Error('polyval: data must be padded to 16 bytes');
const data32 = u32(data);
for (let i = 0; i < data32.length; i += 4) {
// Xor
s[0] ^= data32[i + 0];
s[1] ^= data32[i + 1];
s[2] ^= data32[i + 2];
s[3] ^= data32[i + 3];
// Dot via Karatsuba multiplication, based on MIT-licensed
// https://bearssl.org/gitweb/?p=BearSSL;a=blob;f=src/hash/ghash_ctmul32.c;hb=4b6046412
const b = mulPart(s);
const c = new Uint32Array(18);
for (let i = 0; i < 18; i++) c[i] = bmul32(a[i], b[i]);
c[4] ^= c[0] ^ c[1];
c[5] ^= c[2] ^ c[3];
c[8] ^= c[6] ^ c[7];
c[13] ^= c[9] ^ c[10];
c[14] ^= c[11] ^ c[12];
c[17] ^= c[15] ^ c[16];
const zw = new Uint32Array(8);
zw[0] = c[0];
zw[1] = c[4] ^ (rev32(c[9]) >>> 1);
zw[2] = c[1] ^ c[0] ^ c[2] ^ c[6] ^ (rev32(c[13]) >>> 1);
zw[3] = c[4] ^ c[5] ^ c[8] ^ (rev32(c[10] ^ c[9] ^ c[11] ^ c[15]) >>> 1);
zw[4] = c[2] ^ c[1] ^ c[3] ^ c[7] ^ (rev32(c[13] ^ c[14] ^ c[17]) >>> 1);
zw[5] = c[5] ^ (rev32(c[11] ^ c[10] ^ c[12] ^ c[16]) >>> 1);
zw[6] = c[3] ^ (rev32(c[14]) >>> 1);
zw[7] = rev32(c[12]) >>> 1;
for (let i = 0; i < 4; i++) {
const lw = zw[i];
zw[i + 4] ^= lw ^ (lw >>> 1) ^ (lw >>> 2) ^ (lw >>> 7);
zw[i + 3] ^= (lw << 31) ^ (lw << 30) ^ (lw << 25);
}
s[0] = zw[4];
s[1] = zw[5];
s[2] = zw[6];
s[3] = zw[7];
}
return u8(s);
}
+175
View File
@@ -0,0 +1,175 @@
// Basic utils for salsa-like ciphers
// Check out _micro.ts for descriptive documentation.
import assert from './_assert.js';
import { u32, utf8ToBytes, checkOpts } from './utils.js';
/*
RFC8439 requires multi-step cipher stream, where
authKey starts with counter: 0, actual msg with counter: 1.
For this, we need a way to re-use nonce / counter:
const counter = new Uint8Array(4);
chacha(..., counter, ...); // counter is now 1
chacha(..., counter, ...); // counter is now 2
This is complicated:
- Original papers don't allow mutating counters
- Counter overflow is undefined: https://mailarchive.ietf.org/arch/msg/cfrg/gsOnTJzcbgG6OqD8Sc0GO5aR_tU/
- 3rd-party library stablelib implementation uses an approach where you can provide
nonce and counter instead of just nonce - and it will re-use it
- We could have did something similar, but ChaCha has different counter position
(counter | nonce), which is not composable with XChaCha, because full counter
is (nonce16 | counter | nonce16). Stablelib doesn't support in-place counter for XChaCha.
- We could separate nonce & counter and provide separate API for counter re-use, but
there are different counter sizes depending on an algorithm.
- Salsa & ChaCha also differ in structures of key / sigma:
salsa: c0 | k(4) | c1 | nonce(2) | ctr(2) | c2 | k(4) | c4
chacha: c(4) | k(8) | ctr(1) | nonce(3)
chachaDJB: c(4) | k(8) | ctr(2) | nonce(2)
- Creating function such as `setSalsaState(key, nonce, sigma, data)` won't work,
because we can't re-use counter array
- 32-bit nonce is `2 ** 32 * 64` = 256GB with 32-bit counter
- JS does not allow UintArrays bigger than 4GB, so supporting 64-bit counters doesn't matter
Structure is as following:
key=16 -> sigma16, k=key|key
key=32 -> sigma32, k=key
nonces:
salsa20: 8 (8-byte counter)
chacha20djb: 8 (8-byte counter)
chacha20tls: 12 (4-byte counter)
xsalsa: 24 (16 -> hsalsa, 8 -> old nonce)
xchacha: 24 (16 -> hchacha, 8 -> old nonce)
https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha#appendix-A.2
Use the subkey and remaining 8 byte nonce with ChaCha20 as normal
(prefixed by 4 NUL bytes, since [RFC8439] specifies a 12-byte nonce).
*/
const sigma16 = utf8ToBytes('expand 16-byte k');
const sigma32 = utf8ToBytes('expand 32-byte k');
const sigma16_32 = u32(sigma16);
const sigma32_32 = u32(sigma32);
export type SalsaOpts = {
core: (
c: Uint32Array,
key: Uint32Array,
nonce: Uint32Array,
out: Uint32Array,
counter: number,
rounds?: number
) => void;
rounds?: number;
counterRight?: boolean; // counterRight ? nonce | counter : counter | nonce;
counterLen?: number;
blockLen?: number; // NOTE: not tested with different blockLens!
allow128bitKeys?: boolean; // Original salsa/chacha allows these, but not tested!
extendNonceFn?: (c: Uint32Array, key: Uint8Array, src: Uint8Array, dst: Uint8Array) => Uint8Array;
};
// Is byte array aligned to 4 byte offset (u32)?
const isAligned32 = (b: Uint8Array) => !(b.byteOffset % 4);
export const salsaBasic = (opts: SalsaOpts) => {
const { core, rounds, counterRight, counterLen, allow128bitKeys, extendNonceFn, blockLen } =
checkOpts(
{ rounds: 20, counterRight: false, counterLen: 8, allow128bitKeys: true, blockLen: 64 },
opts
);
assert.number(counterLen);
assert.number(rounds);
assert.number(blockLen);
assert.bool(counterRight);
assert.bool(allow128bitKeys);
const blockLen32 = blockLen / 4;
if (blockLen % 4 !== 0) throw new Error('Salsa/ChaCha: blockLen must be aligned to 4 bytes');
return (
key: Uint8Array,
nonce: Uint8Array,
data: Uint8Array,
output?: Uint8Array,
counter = 0
): Uint8Array => {
assert.bytes(key);
assert.bytes(nonce);
assert.bytes(data);
if (!output) output = new Uint8Array(data.length);
assert.bytes(output);
assert.number(counter);
// > new Uint32Array([2**32])
// Uint32Array(1) [ 0 ]
// > new Uint32Array([2**32-1])
// Uint32Array(1) [ 4294967295 ]
if (counter < 0 || counter >= 2 ** 32 - 1) throw new Error('Salsa/ChaCha: counter overflow');
if (output.length < data.length) {
throw new Error(
`Salsa/ChaCha: output (${output.length}) is shorter than data (${data.length})`
);
}
const toClean = [];
let k, sigma;
// Handle 128 byte keys
if (key.length === 32) {
k = key;
sigma = sigma32_32;
} else if (key.length === 16 && allow128bitKeys) {
k = new Uint8Array(32);
k.set(key);
k.set(key, 16);
sigma = sigma16_32;
toClean.push(k);
} else throw new Error(`Salsa/ChaCha: invalid 32-byte key, got length=${key.length}`);
// Handle extended nonce (HChaCha/HSalsa)
if (extendNonceFn) {
if (nonce.length <= 16)
throw new Error(`Salsa/ChaCha: extended nonce must be bigger than 16 bytes`);
k = extendNonceFn(sigma, k, nonce.subarray(0, 16), new Uint8Array(32));
toClean.push(k);
nonce = nonce.subarray(16);
}
// Handle nonce counter
const nonceLen = 16 - counterLen;
if (nonce.length !== nonceLen)
throw new Error(`Salsa/ChaCha: nonce must be ${nonceLen} or 16 bytes`);
// Pad counter when nonce is 64 bit
if (nonceLen !== 12) {
const nc = new Uint8Array(12);
nc.set(nonce, counterRight ? 0 : 12 - nonce.length);
toClean.push((nonce = nc));
}
// Counter positions
const block = new Uint8Array(blockLen);
// Cast to Uint32Array for speed
const b32 = u32(block);
const k32 = u32(k);
const n32 = u32(nonce);
// Make sure that buffers aligned to 4 bytes
const d32 = isAligned32(data) && u32(data);
const o32 = isAligned32(output) && u32(output);
toClean.push(b32);
const len = data.length;
for (let pos = 0, ctr = counter; pos < len; ctr++) {
core(sigma, k32, n32, b32, ctr, rounds);
if (ctr >= 2 ** 32 - 1) throw new Error('Salsa/ChaCha: counter overflow');
const take = Math.min(blockLen, len - pos);
// full block && aligned to 4 bytes
if (take === blockLen && o32 && d32) {
const pos32 = pos / 4;
if (pos % 4 !== 0) throw new Error('Salsa/ChaCha: invalid block position');
for (let j = 0; j < blockLen32; j++) o32[pos32 + j] = d32[pos32 + j] ^ b32[j];
pos += blockLen;
continue;
}
for (let j = 0; j < take; j++) output[pos + j] = data[pos + j] ^ block[j];
pos += take;
}
for (let i = 0; i < toClean.length; i++) toClean[i].fill(0);
return output;
};
};
+290
View File
@@ -0,0 +1,290 @@
import { Cipher, createView, ensureBytes, equalBytes, setBigUint64, u32 } from './utils.js';
import { poly1305 } from './_poly1305.js';
import { salsaBasic } from './_salsa.js';
// ChaCha20 stream cipher was released in 2008. ChaCha aims to increase
// the diffusion per round, but had slightly less cryptanalysis.
// https://cr.yp.to/chacha.html, http://cr.yp.to/chacha/chacha-20080128.pdf
// Left rotate for uint32
const rotl = (a: number, b: number) => (a << b) | (a >>> (32 - b));
/**
* ChaCha core function.
*/
// prettier-ignore
function chachaCore(
c: Uint32Array, k: Uint32Array, n: Uint32Array, out: Uint32Array, cnt: number, rounds = 20
): void {
let y00 = c[0], y01 = c[1], y02 = c[2], y03 = c[3]; // "expa" "nd 3" "2-by" "te k"
let y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3]; // Key Key Key Key
let y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7]; // Key Key Key Key
let y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; // Counter Counter Nonce Nonce
// Save state to temporary variables
let x00 = y00, x01 = y01, x02 = y02, x03 = y03,
x04 = y04, x05 = y05, x06 = y06, x07 = y07,
x08 = y08, x09 = y09, x10 = y10, x11 = y11,
x12 = y12, x13 = y13, x14 = y14, x15 = y15;
// Main loop
for (let i = 0; i < rounds; i += 2) {
x00 = (x00 + x04) | 0; x12 = rotl(x12 ^ x00, 16);
x08 = (x08 + x12) | 0; x04 = rotl(x04 ^ x08, 12);
x00 = (x00 + x04) | 0; x12 = rotl(x12 ^ x00, 8);
x08 = (x08 + x12) | 0; x04 = rotl(x04 ^ x08, 7);
x01 = (x01 + x05) | 0; x13 = rotl(x13 ^ x01, 16);
x09 = (x09 + x13) | 0; x05 = rotl(x05 ^ x09, 12);
x01 = (x01 + x05) | 0; x13 = rotl(x13 ^ x01, 8);
x09 = (x09 + x13) | 0; x05 = rotl(x05 ^ x09, 7);
x02 = (x02 + x06) | 0; x14 = rotl(x14 ^ x02, 16);
x10 = (x10 + x14) | 0; x06 = rotl(x06 ^ x10, 12);
x02 = (x02 + x06) | 0; x14 = rotl(x14 ^x02, 8);
x10 = (x10 + x14) | 0; x06 = rotl(x06 ^ x10, 7);
x03 = (x03 + x07) | 0; x15 = rotl(x15 ^ x03, 16);
x11 = (x11 + x15) | 0; x07 = rotl(x07 ^ x11, 12);
x03 = (x03 + x07) | 0; x15 = rotl(x15 ^ x03, 8)
x11 = (x11 + x15) | 0; x07 = rotl(x07 ^ x11, 7);
x00 = (x00 + x05) | 0; x15 = rotl(x15 ^ x00, 16);
x10 = (x10 + x15) | 0; x05 = rotl(x05 ^ x10, 12);
x00 = (x00 + x05) | 0; x15 = rotl(x15 ^ x00, 8);
x10 = (x10 + x15) | 0; x05 = rotl(x05 ^ x10, 7);
x01 = (x01 + x06) | 0; x12 = rotl(x12 ^ x01, 16);
x11 = (x11 + x12) | 0; x06 = rotl(x06 ^ x11, 12);
x01 = (x01 + x06) | 0; x12 = rotl(x12 ^ x01, 8);
x11 = (x11 + x12) | 0; x06 = rotl(x06 ^ x11, 7);
x02 = (x02 + x07) | 0; x13 = rotl(x13 ^ x02, 16);
x08 = (x08 + x13) | 0; x07 = rotl(x07 ^ x08, 12);
x02 = (x02 + x07) | 0; x13 = rotl(x13 ^ x02, 8);
x08 = (x08 + x13) | 0; x07 = rotl(x07 ^ x08, 7);
x03 = (x03 + x04) | 0; x14 = rotl(x14 ^ x03, 16)
x09 = (x09 + x14) | 0; x04 = rotl(x04 ^ x09, 12);
x03 = (x03 + x04) | 0; x14 = rotl(x14 ^ x03, 8);
x09 = (x09 + x14) | 0; x04 = rotl(x04 ^ x09, 7);
}
// Write output
let oi = 0;
out[oi++] = (y00 + x00) | 0; out[oi++] = (y01 + x01) | 0;
out[oi++] = (y02 + x02) | 0; out[oi++] = (y03 + x03) | 0;
out[oi++] = (y04 + x04) | 0; out[oi++] = (y05 + x05) | 0;
out[oi++] = (y06 + x06) | 0; out[oi++] = (y07 + x07) | 0;
out[oi++] = (y08 + x08) | 0; out[oi++] = (y09 + x09) | 0;
out[oi++] = (y10 + x10) | 0; out[oi++] = (y11 + x11) | 0;
out[oi++] = (y12 + x12) | 0; out[oi++] = (y13 + x13) | 0;
out[oi++] = (y14 + x14) | 0; out[oi++] = (y15 + x15) | 0;
}
/**
* hchacha helper method, used primarily in xchacha, to hash
* key and nonce into key' and nonce'.
* Same as chachaCore, but there doesn't seem to be a way to move the block
* out without 25% performance hit.
*/
// prettier-ignore
export function hchacha(
c: Uint32Array, key: Uint8Array, src: Uint8Array, out: Uint8Array
): Uint8Array {
const k32 = u32(key);
const i32 = u32(src);
const o32 = u32(out);
let x00 = c[0], x01 = c[1], x02 = c[2], x03 = c[3];
let x04 = k32[0], x05 = k32[1], x06 = k32[2], x07 = k32[3];
let x08 = k32[4], x09 = k32[5], x10 = k32[6], x11 = k32[7]
let x12 = i32[0], x13 = i32[1], x14 = i32[2], x15 = i32[3];
for (let i = 0; i < 20; i += 2) {
x00 = (x00 + x04) | 0; x12 = rotl(x12 ^ x00, 16);
x08 = (x08 + x12) | 0; x04 = rotl(x04 ^ x08, 12);
x00 = (x00 + x04) | 0; x12 = rotl(x12 ^ x00, 8);
x08 = (x08 + x12) | 0; x04 = rotl(x04 ^ x08, 7);
x01 = (x01 + x05) | 0; x13 = rotl(x13 ^ x01, 16);
x09 = (x09 + x13) | 0; x05 = rotl(x05 ^ x09, 12);
x01 = (x01 + x05) | 0; x13 = rotl(x13 ^ x01, 8);
x09 = (x09 + x13) | 0; x05 = rotl(x05 ^ x09, 7);
x02 = (x02 + x06) | 0; x14 = rotl(x14 ^ x02, 16);
x10 = (x10 + x14) | 0; x06 = rotl(x06 ^ x10, 12);
x02 = (x02 + x06) | 0; x14 = rotl(x14 ^ x02, 8);
x10 = (x10 + x14) | 0; x06 = rotl(x06 ^ x10, 7);
x03 = (x03 + x07) | 0; x15 = rotl(x15 ^ x03, 16);
x11 = (x11 + x15) | 0; x07 = rotl(x07 ^ x11, 12);
x03 = (x03 + x07) | 0; x15 = rotl(x15 ^ x03, 8)
x11 = (x11 + x15) | 0; x07 = rotl(x07 ^ x11, 7);
x00 = (x00 + x05) | 0; x15 = rotl(x15 ^ x00, 16);
x10 = (x10 + x15) | 0; x05 = rotl(x05 ^ x10, 12);
x00 = (x00 + x05) | 0; x15 = rotl(x15 ^ x00, 8);
x10 = (x10 + x15) | 0; x05 = rotl(x05 ^ x10, 7);
x01 = (x01 + x06) | 0; x12 = rotl(x12 ^ x01, 16);
x11 = (x11 + x12) | 0; x06 = rotl(x06 ^ x11, 12);
x01 = (x01 + x06) | 0; x12 = rotl(x12 ^ x01, 8);
x11 = (x11 + x12) | 0; x06 = rotl(x06 ^ x11, 7);
x02 = (x02 + x07) | 0; x13 = rotl(x13 ^ x02, 16);
x08 = (x08 + x13) | 0; x07 = rotl(x07 ^ x08, 12);
x02 = (x02 + x07) | 0; x13 = rotl(x13 ^ x02, 8);
x08 = (x08 + x13) | 0; x07 = rotl(x07 ^ x08, 7);
x03 = (x03 + x04) | 0; x14 = rotl(x14 ^ x03, 16)
x09 = (x09 + x14) | 0; x04 = rotl(x04 ^ x09, 12);
x03 = (x03 + x04) | 0; x14 = rotl(x14 ^ x03, 8);
x09 = (x09 + x14) | 0; x04 = rotl(x04 ^ x09, 7);
}
o32[0] = x00;
o32[1] = x01;
o32[2] = x02;
o32[3] = x03;
o32[4] = x12;
o32[5] = x13;
o32[6] = x14;
o32[7] = x15;
return out;
}
/**
* Original, non-RFC chacha20 from DJB. 8-byte nonce, 8-byte counter.
*/
export const chacha20orig = /* @__PURE__ */ salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 8,
});
/**
* ChaCha stream cipher. Conforms to RFC 8439 (IETF, TLS). 12-byte nonce, 4-byte counter.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
export const chacha20 = /* @__PURE__ */ salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
allow128bitKeys: false,
});
/**
* XChaCha eXtended-nonce ChaCha. 24-byte nonce.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
*/
export const xchacha20 = /* @__PURE__ */ salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 8,
extendNonceFn: hchacha,
allow128bitKeys: false,
});
/**
* Reduced 8-round chacha, described in original paper.
*/
export const chacha8 = /* @__PURE__ */ salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 8,
});
/**
* Reduced 12-round chacha, described in original paper.
*/
export const chacha12 = /* @__PURE__ */ salsaBasic({
core: chachaCore,
counterRight: false,
counterLen: 4,
rounds: 12,
});
const ZERO = /* @__PURE__ */ new Uint8Array(16);
// Pad to digest size with zeros
const updatePadded = (h: ReturnType<typeof poly1305.create>, msg: Uint8Array) => {
h.update(msg);
const left = msg.length % 16;
if (left) h.update(ZERO.subarray(left));
};
const computeTag = (
fn: typeof chacha20,
key: Uint8Array,
nonce: Uint8Array,
data: Uint8Array,
AAD?: Uint8Array
) => {
const authKey = fn(key, nonce, new Uint8Array(32));
const h = poly1305.create(authKey);
if (AAD) updatePadded(h, AAD);
updatePadded(h, data);
const num = new Uint8Array(16);
const view = createView(num);
setBigUint64(view, 0, BigInt(AAD ? AAD.length : 0), true);
setBigUint64(view, 8, BigInt(data.length), true);
h.update(num);
const res = h.digest();
authKey.fill(0);
return res;
};
/**
* AEAD algorithm from RFC 8439.
* Salsa20 and chacha (RFC 8439) use poly1305 differently.
* We could have composed them similar to:
* https://github.com/paulmillr/scure-base/blob/b266c73dde977b1dd7ef40ef7a23cc15aab526b3/index.ts#L250
* But it's hard because of authKey:
* In salsa20, authKey changes position in salsa stream.
* In chacha, authKey can't be computed inside computeTag, it modifies the counter.
*/
export const _poly1305_aead =
(xorStream: typeof chacha20) =>
(key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array): Cipher => {
const tagLength = 16;
ensureBytes(key, 32);
ensureBytes(nonce);
return {
tagLength,
encrypt: (plaintext: Uint8Array, output?: Uint8Array) => {
const plength = plaintext.length;
const clength = plength + tagLength;
if (output) {
ensureBytes(output, clength);
} else {
output = new Uint8Array(clength);
}
xorStream(key, nonce, plaintext, output, 1);
const tag = computeTag(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
output.set(tag, plength); // append tag
return output;
},
decrypt: (ciphertext: Uint8Array, output?: Uint8Array) => {
const clength = ciphertext.length;
const plength = clength - tagLength;
if (clength < tagLength)
throw new Error(`encrypted data must be at least ${tagLength} bytes`);
if (output) {
ensureBytes(output, plength);
} else {
output = new Uint8Array(plength);
}
const data = ciphertext.subarray(0, -tagLength);
const passedTag = ciphertext.subarray(-tagLength);
const tag = computeTag(xorStream, key, nonce, data, AAD);
if (!equalBytes(passedTag, tag)) throw new Error('invalid tag');
xorStream(key, nonce, data, output, 1);
return output;
},
};
};
/**
* ChaCha20-Poly1305 from RFC 8439.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
export const chacha20poly1305 = /* @__PURE__ */ _poly1305_aead(chacha20);
/**
* XChaCha20-Poly1305 extended-nonce chacha.
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
export const xchacha20poly1305 = /* @__PURE__ */ _poly1305_aead(xchacha20);
+1
View File
@@ -0,0 +1 @@
throw new Error('noble-ciphers have no entry-point: consult README for usage');
+176
View File
@@ -0,0 +1,176 @@
import { ensureBytes, u32, equalBytes, Cipher } from './utils.js';
import { salsaBasic } from './_salsa.js';
import { poly1305 } from './_poly1305.js';
// Salsa20 stream cipher was released in 2005.
// Salsa's goal was to implement AES replacement that does not rely on S-Boxes,
// which are hard to implement in a constant-time manner.
// https://cr.yp.to/snuffle.html, https://cr.yp.to/snuffle/salsafamily-20071225.pdf
// Left rotate for uint32
const rotl = (a: number, b: number) => (a << b) | (a >>> (32 - b));
/**
* Salsa20 core function.
*/
// prettier-ignore
function salsaCore(
c: Uint32Array, k: Uint32Array, i: Uint32Array, out: Uint32Array, cnt: number, rounds = 20
): void {
// Based on https://cr.yp.to/salsa20.html
let y00 = c[0], y01 = k[0], y02 = k[1], y03 = k[2]; // "expa" Key Key Key
let y04 = k[3], y05 = c[1], y06 = i[0], y07 = i[1]; // Key "nd 3" Nonce Nonce
let y08 = cnt, y09 = 0 , y10 = c[2], y11 = k[4]; // Pos. Pos. "2-by" Key
let y12 = k[5], y13 = k[6], y14 = k[7], y15 = c[3]; // Key Key Key "te k"
// Save state to temporary variables
let x00 = y00, x01 = y01, x02 = y02, x03 = y03,
x04 = y04, x05 = y05, x06 = y06, x07 = y07,
x08 = y08, x09 = y09, x10 = y10, x11 = y11,
x12 = y12, x13 = y13, x14 = y14, x15 = y15;
// Main loop
for (let i = 0; i < rounds; i += 2) {
x04 ^= rotl(x00 + x12 | 0, 7); x08 ^= rotl(x04 + x00 | 0, 9);
x12 ^= rotl(x08 + x04 | 0, 13); x00 ^= rotl(x12 + x08 | 0, 18);
x09 ^= rotl(x05 + x01 | 0, 7); x13 ^= rotl(x09 + x05 | 0, 9);
x01 ^= rotl(x13 + x09 | 0, 13); x05 ^= rotl(x01 + x13 | 0, 18);
x14 ^= rotl(x10 + x06 | 0, 7); x02 ^= rotl(x14 + x10 | 0, 9);
x06 ^= rotl(x02 + x14 | 0, 13); x10 ^= rotl(x06 + x02 | 0, 18);
x03 ^= rotl(x15 + x11 | 0, 7); x07 ^= rotl(x03 + x15 | 0, 9);
x11 ^= rotl(x07 + x03 | 0, 13); x15 ^= rotl(x11 + x07 | 0, 18);
x01 ^= rotl(x00 + x03 | 0, 7); x02 ^= rotl(x01 + x00 | 0, 9);
x03 ^= rotl(x02 + x01 | 0, 13); x00 ^= rotl(x03 + x02 | 0, 18);
x06 ^= rotl(x05 + x04 | 0, 7); x07 ^= rotl(x06 + x05 | 0, 9);
x04 ^= rotl(x07 + x06 | 0, 13); x05 ^= rotl(x04 + x07 | 0, 18);
x11 ^= rotl(x10 + x09 | 0, 7); x08 ^= rotl(x11 + x10 | 0, 9);
x09 ^= rotl(x08 + x11 | 0, 13); x10 ^= rotl(x09 + x08 | 0, 18);
x12 ^= rotl(x15 + x14 | 0, 7); x13 ^= rotl(x12 + x15 | 0, 9);
x14 ^= rotl(x13 + x12 | 0, 13); x15 ^= rotl(x14 + x13 | 0, 18);
}
// Write output
let oi = 0;
out[oi++] = (y00 + x00) | 0; out[oi++] = (y01 + x01) | 0;
out[oi++] = (y02 + x02) | 0; out[oi++] = (y03 + x03) | 0;
out[oi++] = (y04 + x04) | 0; out[oi++] = (y05 + x05) | 0;
out[oi++] = (y06 + x06) | 0; out[oi++] = (y07 + x07) | 0;
out[oi++] = (y08 + x08) | 0; out[oi++] = (y09 + x09) | 0;
out[oi++] = (y10 + x10) | 0; out[oi++] = (y11 + x11) | 0;
out[oi++] = (y12 + x12) | 0; out[oi++] = (y13 + x13) | 0;
out[oi++] = (y14 + x14) | 0; out[oi++] = (y15 + x15) | 0;
}
/**
* hsalsa hashing function, used primarily in xsalsa, to hash
* key and nonce into key' and nonce'.
* Same as salsaCore, but there doesn't seem to be a way to move the block
* out without 25% performance hit.
*/
// prettier-ignore
export function hsalsa(c: Uint32Array, key: Uint8Array, nonce: Uint8Array, out: Uint8Array): Uint8Array {
const k32 = u32(key);
const i32 = u32(nonce);
const o32 = u32(out);
let x00 = c[0], x01 = k32[0], x02 = k32[1], x03 = k32[2], x04 = k32[3];
let x05 = c[1], x06 = i32[0], x07 = i32[1], x08 = i32[2], x09 = i32[3];
let x10 = c[2], x11 = k32[4], x12 = k32[5], x13 = k32[6], x14 = k32[7];
let x15 = c[3];
// Main loop
for (let i = 0; i < 20; i += 2) {
x04 ^= rotl(x00 + x12 | 0, 7); x08 ^= rotl(x04 + x00 | 0, 9);
x12 ^= rotl(x08 + x04 | 0, 13); x00 ^= rotl(x12 + x08 | 0, 18);
x09 ^= rotl(x05 + x01 | 0, 7); x13 ^= rotl(x09 + x05 | 0, 9);
x01 ^= rotl(x13 + x09 | 0, 13); x05 ^= rotl(x01 + x13 | 0, 18);
x14 ^= rotl(x10 + x06 | 0, 7); x02 ^= rotl(x14 + x10 | 0, 9);
x06 ^= rotl(x02 + x14 | 0, 13); x10 ^= rotl(x06 + x02 | 0, 18);
x03 ^= rotl(x15 + x11 | 0, 7); x07 ^= rotl(x03 + x15 | 0, 9);
x11 ^= rotl(x07 + x03 | 0, 13); x15 ^= rotl(x11 + x07 | 0, 18);
x01 ^= rotl(x00 + x03 | 0, 7); x02 ^= rotl(x01 + x00 | 0, 9);
x03 ^= rotl(x02 + x01 | 0, 13); x00 ^= rotl(x03 + x02 | 0, 18);
x06 ^= rotl(x05 + x04 | 0, 7); x07 ^= rotl(x06 + x05 | 0, 9);
x04 ^= rotl(x07 + x06 | 0, 13); x05 ^= rotl(x04 + x07 | 0, 18);
x11 ^= rotl(x10 + x09 | 0, 7); x08 ^= rotl(x11 + x10 | 0, 9);
x09 ^= rotl(x08 + x11 | 0, 13); x10 ^= rotl(x09 + x08 | 0, 18);
x12 ^= rotl(x15 + x14 | 0, 7); x13 ^= rotl(x12 + x15 | 0, 9);
x14 ^= rotl(x13 + x12 | 0, 13); x15 ^= rotl(x14 + x13 | 0, 18);
}
o32[0] = x00;
o32[1] = x05;
o32[2] = x10;
o32[3] = x15;
o32[4] = x06;
o32[5] = x07;
o32[6] = x08;
o32[7] = x09;
return out;
}
/**
* Salsa20 from original paper.
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
*/
export const salsa20 = /* @__PURE__ */ salsaBasic({ core: salsaCore, counterRight: true });
/**
* xsalsa20 eXtended-nonce salsa.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
export const xsalsa20 = /* @__PURE__ */ salsaBasic({
core: salsaCore,
counterRight: true,
extendNonceFn: hsalsa,
allow128bitKeys: false,
});
/**
* xsalsa20-poly1305 eXtended-nonce salsa.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
* Also known as secretbox from libsodium / nacl.
*/
export const xsalsa20poly1305 = (key: Uint8Array, nonce: Uint8Array): Cipher => {
const tagLength = 16;
ensureBytes(key, 32);
ensureBytes(nonce, 24);
return {
tagLength,
encrypt: (plaintext: Uint8Array, output?: Uint8Array) => {
ensureBytes(plaintext);
// This is small optimization (calculate auth key with same call as encryption itself) makes it hard
// to separate tag calculation and encryption itself, since 32 byte is half-block of salsa (64 byte)
const clength = plaintext.length + 32;
if (output) {
ensureBytes(output, clength);
} else {
output = new Uint8Array(clength);
}
output.set(plaintext, 32);
xsalsa20(key, nonce, output, output);
const authKey = output.subarray(0, 32);
const tag = poly1305(output.subarray(32), authKey);
// Clean auth key, even though JS provides no guarantees about memory cleaning
output.set(tag, tagLength);
output.subarray(0, tagLength).fill(0);
return output.subarray(tagLength);
},
decrypt: (ciphertext: Uint8Array) => {
ensureBytes(ciphertext);
const clength = ciphertext.length;
if (clength < tagLength) throw new Error('encrypted data should be at least 16 bytes');
// Create new ciphertext array:
// auth tag auth tag from ciphertext ciphertext
// [bytes 0..16] [bytes 16..32] [bytes 32..]
// 16 instead of 32, because we already have 16 byte tag
const ciphertext_ = new Uint8Array(clength + tagLength); // alloc
ciphertext_.set(ciphertext, tagLength);
// Each xsalsa20 calls to hsalsa to calculate key, but seems not much perf difference
// Separate call to calculate authkey, since first bytes contains tag
const authKey = xsalsa20(key, nonce, new Uint8Array(32)); // alloc(32)
const tag = poly1305(ciphertext_.subarray(32), authKey);
if (!equalBytes(ciphertext_.subarray(16, 32), tag)) throw new Error('invalid tag');
const plaintext = xsalsa20(key, nonce, ciphertext_); // alloc
// Clean auth key, even though JS provides no guarantees about memory cleaning
plaintext.subarray(0, 32).fill(0);
authKey.fill(0);
return plaintext.subarray(32);
},
};
};
+61
View File
@@ -0,0 +1,61 @@
import { xchacha20poly1305 } from './chacha.js';
import { xsalsa20poly1305 } from './salsa.js';
import { concatBytes, ensureBytes, utf8ToBytes } from './utils.js';
import { aes_256_gcm } from './webcrypto/aes.js';
import { randomBytes } from './webcrypto/utils.js';
export { utf8ToBytes };
/**
* Alias to xsalsa20poly1305, for compatibility with libsodium / nacl
*/
export function secretbox(key: Uint8Array, nonce: Uint8Array) {
ensureBytes(key);
ensureBytes(nonce);
const xs = xsalsa20poly1305(key, nonce);
return { seal: xs.encrypt, open: xs.decrypt };
}
export function randomKey(): Uint8Array {
return randomBytes(32);
}
/**
* Encrypt plaintext under key with random nonce, using xchacha20poly1305.
* User never touches nonce: it is prepended to ciphertext.
*/
export function encrypt(key: Uint8Array, plaintext: Uint8Array): Uint8Array {
ensureBytes(key);
const nonce = randomBytes(24);
const ciphertext = xchacha20poly1305(key, nonce).encrypt(plaintext);
return concatBytes(nonce, ciphertext);
}
/**
* Decrypt plaintext under key with random nonce, using xchacha20poly1305.
* User never touches nonce: it is prepended to ciphertext.
*/
export function decrypt(key: Uint8Array, ciphertext: Uint8Array): Uint8Array {
const nonceLength = 24;
ensureBytes(ciphertext);
if (ciphertext.length <= nonceLength) throw new Error('invalid ciphertext length');
const nonce = ciphertext.subarray(0, nonceLength);
const ciphertextWithoutNonce = ciphertext.subarray(nonceLength);
return xchacha20poly1305(key, nonce).decrypt(ciphertextWithoutNonce);
}
export async function aes_encrypt(key: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array> {
const nonceLength = 12;
ensureBytes(key);
const nonce = randomBytes(nonceLength);
const ciphertext = await aes_256_gcm(key, nonce).encrypt(plaintext);
return concatBytes(nonce, ciphertext);
}
export async function aes_decrypt(key: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array> {
const nonceLength = 12;
ensureBytes(ciphertext);
if (ciphertext.length <= nonceLength) throw new Error('invalid ciphertext length');
const nonce = ciphertext.subarray(0, nonceLength);
const ciphertextWithoutNonce = ciphertext.subarray(nonceLength);
return aes_256_gcm(key, nonce).decrypt(ciphertextWithoutNonce);
}
+195
View File
@@ -0,0 +1,195 @@
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
// prettier-ignore
export type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array |
Uint16Array | Int16Array | Uint32Array | Int32Array;
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
// Cast array to different type
export const u8 = (arr: TypedArray) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
export const u16 = (arr: TypedArray) =>
new Uint16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
export const u32 = (arr: TypedArray) =>
new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
// Cast array to view
export const createView = (arr: TypedArray) =>
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
// big-endian hardware is rare. Just in case someone still decides to run ciphers:
// early-throw an error because we don't support BE yet.
export const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
if (!isLE) throw new Error('Non little-endian hardware is not supported');
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
i.toString(16).padStart(2, '0')
);
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
export function bytesToHex(bytes: Uint8Array): string {
if (!u8a(bytes)) throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
}
/**
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
*/
export function hexToBytes(hex: string): Uint8Array {
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
const len = hex.length;
if (len % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + len);
const array = new Uint8Array(len / 2);
for (let i = 0; i < array.length; i++) {
const j = i * 2;
const hexByte = hex.slice(j, j + 2);
const byte = Number.parseInt(hexByte, 16);
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence');
array[i] = byte;
}
return array;
}
// There is no setImmediate in browser and setTimeout is slow.
// call of async fn will return Promise, which will be fullfiled only on
// next scheduler queue processing step and this is exactly what we need.
export const nextTick = async () => {};
// Returns control to thread each 'tick' ms to avoid blocking
export async function asyncLoop(iters: number, tick: number, cb: (i: number) => void) {
let ts = Date.now();
for (let i = 0; i < iters; i++) {
cb(i);
// Date.now() is not monotonic, so in case if clock goes backwards we return return control too
const diff = Date.now() - ts;
if (diff >= 0 && diff < tick) continue;
await nextTick();
ts += diff;
}
}
// Global symbols in both browsers and Node.js since v11
// See https://github.com/microsoft/TypeScript/issues/31535
declare const TextEncoder: any;
declare const TextDecoder: any;
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
export function utf8ToBytes(str: string): Uint8Array {
if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
export function bytesToUtf8(bytes: Uint8Array): string {
return new TextDecoder().decode(bytes);
}
export type Input = Uint8Array | string;
/**
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
* Warning: when Uint8Array is passed, it would NOT get copied.
* Keep in mind for future mutable operations.
*/
export function toBytes(data: Input): Uint8Array {
if (typeof data === 'string') data = utf8ToBytes(data);
if (!u8a(data)) throw new Error(`expected Uint8Array, got ${typeof data}`);
return data;
}
/**
* Copies several Uint8Arrays into one.
*/
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
let pad = 0; // walk through each item, ensure they have proper type
arrays.forEach((a) => {
if (!u8a(a)) throw new Error('Uint8Array expected');
r.set(a, pad);
pad += a.length;
});
return r;
}
// Check if object doens't have custom constructor (like Uint8Array/Array)
const isPlainObject = (obj: any) =>
Object.prototype.toString.call(obj) === '[object Object]' && obj.constructor === Object;
type EmptyObj = {};
export function checkOpts<T1 extends EmptyObj, T2 extends EmptyObj>(
defaults: T1,
opts?: T2
): T1 & T2 {
if (opts !== undefined && (typeof opts !== 'object' || !isPlainObject(opts)))
throw new Error('options must be object or undefined');
const merged = Object.assign(defaults, opts);
return merged as T1 & T2;
}
export function ensureBytes(b: any, len?: number) {
if (!(b instanceof Uint8Array)) throw new Error('Uint8Array expected');
if (typeof len === 'number')
if (b.length !== len) throw new Error(`Uint8Array length ${len} expected`);
}
// Constant-time equality
export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
// Should not happen
if (a.length !== b.length) throw new Error('equalBytes: Different size of Uint8Arrays');
let isSame = true;
for (let i = 0; i < a.length; i++) isSame &&= a[i] === b[i]; // Lets hope JIT won't optimize away.
return isSame;
}
// For runtime check if class implements interface
export abstract class Hash<T extends Hash<T>> {
abstract blockLen: number; // Bytes per block
abstract outputLen: number; // Bytes in output
abstract update(buf: Input): this;
// Writes digest into buf
abstract digestInto(buf: Uint8Array): void;
abstract digest(): Uint8Array;
/**
* Resets internal state. Makes Hash instance unusable.
* Reset is impossible for keyed hashes if key is consumed into state. If digest is not consumed
* by user, they will need to manually call `destroy()` when zeroing is necessary.
*/
abstract destroy(): void;
}
// This will allow to re-use with composable things like packed & base encoders
// Also, we probably can make tags composable
export type Cipher = {
tagLength?: number;
encrypt(plaintext: Uint8Array): Uint8Array;
decrypt(ciphertext: Uint8Array): Uint8Array;
};
export type AsyncCipher = {
tagLength?: number;
encrypt(plaintext: Uint8Array): Promise<Uint8Array>;
decrypt(ciphertext: Uint8Array): Promise<Uint8Array>;
};
// Polyfill for Safari 14
export function setBigUint64(
view: DataView,
byteOffset: number,
value: bigint,
isLE: boolean
): void {
if (typeof view.setBigUint64 === 'function') return view.setBigUint64(byteOffset, value, isLE);
const _32n = BigInt(32);
const _u32_max = BigInt(0xffffffff);
const wh = Number((value >> _32n) & _u32_max);
const wl = Number(value & _u32_max);
const h = isLE ? 4 : 0;
const l = isLE ? 0 : 4;
view.setUint32(byteOffset + h, wh, isLE);
view.setUint32(byteOffset + l, wl, isLE);
}
+48
View File
@@ -0,0 +1,48 @@
import { ensureBytes } from '../utils.js';
import { getWebcryptoSubtle } from './utils.js';
function generate(algo: string, length: number) {
const keyLength = length / 8;
const keyParams = { name: algo, length };
const cryptParams: Record<string, any> = { name: algo };
// const params: Record<string, any> = ({ e: algo, i: { name: algo, length } });
return (key: Uint8Array, nonce: Uint8Array) => {
ensureBytes(key, keyLength);
if (algo === 'AES-CTR') {
cryptParams.counter = nonce;
cryptParams.length = 64;
} else {
cryptParams.iv = nonce;
}
return {
keyLength,
async encrypt(plaintext: Uint8Array): Promise<Uint8Array> {
ensureBytes(plaintext);
const cr = getWebcryptoSubtle();
const iKey = await cr.importKey('raw', key, keyParams, true, ['encrypt']);
const cipher = await cr.encrypt(cryptParams, iKey, plaintext);
return new Uint8Array(cipher);
},
async decrypt(ciphertext: Uint8Array): Promise<Uint8Array> {
ensureBytes(ciphertext);
const cr = getWebcryptoSubtle();
const iKey = await cr.importKey('raw', key, keyParams, true, ['decrypt']);
const plaintext = await cr.decrypt(cryptParams, iKey, ciphertext);
return new Uint8Array(plaintext);
},
};
};
}
export const aes_128_ctr = generate('AES-CTR', 128);
export const aes_256_ctr = generate('AES-CTR', 256);
export const aes_128_cbc = generate('AES-CBC', 128);
export const aes_256_cbc = generate('AES-CBC', 256);
export const aes_128_gcm = generate('AES-GCM', 128);
export const aes_256_gcm = generate('AES-GCM', 256);
+5
View File
@@ -0,0 +1,5 @@
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
// See utils.ts for details.
declare const globalThis: Record<string, any> | undefined;
export const crypto =
typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
+7
View File
@@ -0,0 +1,7 @@
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
// See utils.ts for details.
// The file will throw on node.js 14 and earlier.
// @ts-ignore
import * as nc from 'node:crypto';
export const crypto =
nc && typeof nc === 'object' && 'webcrypto' in nc ? (nc.webcrypto as any) : undefined;
+169
View File
@@ -0,0 +1,169 @@
import type { AsyncCipher } from '../utils.js';
import { getWebcryptoSubtle } from './utils.js';
// Format-preserving encryption algorithm (FPE-FF1) specified in NIST Special Publication 800-38G.
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38G.pdf
// Utils
function toBytesBE(num: bigint, length?: number): Uint8Array {
let hex = num.toString(16);
hex = hex.length & 1 ? `0${hex}` : hex;
if (length) hex = hex.padStart(length * 2, '00');
const len = hex.length / 2;
const u8 = new Uint8Array(len);
for (let j = 0, i = 0; i < hex.length && i < len * 2; i += 2, j++)
u8[j] = parseInt(hex[i] + hex[i + 1], 16);
return u8;
}
function fromBytesBE(bytes: Uint8Array): bigint {
let value = 0n;
for (let i = bytes.length - 1, j = 0; i >= 0; i--, j++)
value += (BigInt(bytes[i]) & 255n) << (8n * BigInt(j));
return value;
}
// Calculates a modulo b
function mod(a: number, b: number): number;
function mod(a: bigint, b: bigint): bigint;
function mod(a: any, b: any): number | bigint {
const result = a % b;
return result >= 0 ? result : b + result;
}
// AES stuff
const BLOCK_LEN = 16;
const IV = new Uint8Array(BLOCK_LEN);
export async function encryptBlock(msg: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
if (key.length !== 16 && key.length !== 32) throw new Error('Invalid key length');
const cr = getWebcryptoSubtle();
const mode = { name: `AES-CBC`, length: key.length * 8 };
const wKey = await cr.importKey('raw', key, mode, true, ['encrypt']);
const cipher = await cr.encrypt({ name: `aes-cbc`, iv: IV, counter: IV, length: 64 }, wKey, msg);
return new Uint8Array(cipher).subarray(0, 16);
}
function NUMradix(radix: number, data: number[]): bigint {
let res = 0n;
for (let i of data) res = res * BigInt(radix) + BigInt(i);
return res;
}
async function getRound(radix: number, key: Uint8Array, tweak: Uint8Array, x: number[]) {
if (radix > 2 ** 16 - 1) throw new Error(`Invalid radix: ${radix}`);
// radix**minlen ≥ 100
const minLen = Math.ceil(Math.log(100) / Math.log(radix));
const maxLen = 2 ** 32 - 1;
// 2 ≤ minlen ≤ maxlen < 2**32
if (2 > minLen || minLen > maxLen || maxLen >= 2 ** 32)
throw new Error('Invalid radix: 2 ≤ minlen ≤ maxlen < 2**32');
if (x.length < minLen || x.length > maxLen) throw new Error('X is outside minLen..maxLen bounds');
const u = Math.floor(x.length / 2);
const v = x.length - u;
const b = Math.ceil(Math.ceil(v * Math.log2(radix)) / 8);
const d = 4 * Math.ceil(b / 4) + 4;
const padding = mod(-tweak.length - b - 1, 16);
// P = [1]1 || [2]1 || [1]1 || [radix]3 || [10]1 || [u mod 256]1 || [n]4 || [t]4.
const P = new Uint8Array([1, 2, 1, 0, 0, 0, 10, u, 0, 0, 0, 0, 0, 0, 0, 0]);
const view = new DataView(P.buffer);
view.setUint16(4, radix, false);
view.setUint32(8, x.length, false);
view.setUint32(12, tweak.length, false);
// Q = T || [0](tb1) mod 16 || [i]1 || [NUMradix(B)]b.
const PQ = new Uint8Array(P.length + tweak.length + padding + 1 + b);
PQ.set(P);
P.fill(0);
PQ.set(tweak, P.length);
const round = async (A: number[], B: number[], i: number, decrypt = false) => {
// Q = ... || [i]1 || [NUMradix(B)]b.
PQ[PQ.length - b - 1] = i;
if (b) PQ.set(toBytesBE(NUMradix(radix, B), b), PQ.length - b);
// PRF
let r = new Uint8Array(16);
for (let j = 0; j < PQ.length / BLOCK_LEN; j++) {
for (let i = 0; i < BLOCK_LEN; i++) r[i] ^= PQ[j * BLOCK_LEN + i];
r.set(await encryptBlock(r, key));
}
// Let S be the first d bytes of the following string of ⎡d/16⎤ blocks:
// R || CIPHK(R ⊕[1]16) || CIPHK(R ⊕[2]16) ...CIPHK(R ⊕[⎡d / 16⎤ 1]16).
let s = Array.from(r);
for (let j = 1; s.length < d; j++) {
const block = toBytesBE(BigInt(j), 16);
for (let k = 0; k < BLOCK_LEN; k++) block[k] ^= r[k];
s.push(...Array.from(await encryptBlock(block, key)));
}
let y = fromBytesBE(Uint8Array.from(s.slice(0, d)));
s.fill(0);
if (decrypt) y = -y;
const m = i % 2 === 0 ? u : v;
let c = mod(NUMradix(radix, A) + y, BigInt(radix) ** BigInt(m));
// STR(radix, m, c)
const C = Array(m).fill(0);
for (let i = 0; i < m; i++, c /= BigInt(radix)) C[m - 1 - i] = Number(c % BigInt(radix));
A.fill(0);
A = B;
B = C;
return [A, B];
};
const destroy = () => PQ.fill(0);
return { u, round, destroy };
}
const EMPTY_BUF = new Uint8Array([]);
export function FF1(radix: number, key: Uint8Array, tweak: Uint8Array = EMPTY_BUF) {
const PQ = getRound.bind(null, radix, key, tweak);
return {
async encrypt(x: number[]) {
const { u, round, destroy } = await PQ(x);
let [A, B] = [x.slice(0, u), x.slice(u)];
for (let i = 0; i < 10; i++) [A, B] = await round(A, B, i);
destroy();
const res = A.concat(B);
A.fill(0);
B.fill(0);
return res;
},
async decrypt(x: number[]) {
const { u, round, destroy } = await PQ(x);
// The FF1.Decrypt algorithm is similar to the FF1.Encrypt algorithm;
// the differences are in Step 6, where:
// 1) the order of the indices is reversed,
// 2) the roles of A and B are swapped
// 3) modular addition is replaced by modular subtraction, in Step 6vi.
let [B, A] = [x.slice(0, u), x.slice(u)];
for (let i = 9; i >= 0; i--) [A, B] = await round(A, B, i, true);
destroy();
const res = B.concat(A);
A.fill(0);
B.fill(0);
return res;
},
};
}
// Binary string which encodes each byte in little-endian byte order
const binLE = {
encode(bytes: Uint8Array): number[] {
const x = [];
for (let i = 0; i < bytes.length; i++) {
for (let j = 0, tmp = bytes[i]; j < 8; j++, tmp >>= 1) x.push(tmp & 1);
}
return x;
},
decode(b: number[]): Uint8Array {
if (b.length % 8) throw new Error('Invalid binary string');
const res = new Uint8Array(b.length / 8);
for (let i = 0, j = 0; i < res.length; i++) {
res[i] = b[j++] | (b[j++] << 1) | (b[j++] << 2) | (b[j++] << 3);
res[i] |= (b[j++] << 4) | (b[j++] << 5) | (b[j++] << 6) | (b[j++] << 7);
}
return res;
},
};
export function BinaryFF1(key: Uint8Array, tweak: Uint8Array = EMPTY_BUF): AsyncCipher {
const ff1 = FF1(2, key, tweak);
return {
encrypt: async (x: Uint8Array) => binLE.decode(await ff1.encrypt(binLE.encode(x))),
decrypt: async (x: Uint8Array) => binLE.decode(await ff1.decrypt(binLE.encode(x))),
};
}
+125
View File
@@ -0,0 +1,125 @@
import { AsyncCipher, createView, setBigUint64 } from '../utils.js';
import { polyval } from '../_polyval.js';
import { getWebcryptoSubtle } from './utils.js';
/**
* AES-GCM-SIV: classic AES-GCM with nonce-misuse resistance.
* RFC 8452, https://datatracker.ietf.org/doc/html/rfc8452
*/
// AES stuff (same as ff1)
const BLOCK_LEN = 16;
const IV = new Uint8Array(BLOCK_LEN);
async function encryptBlock(msg: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
if (key.length !== 16 && key.length !== 32) throw new Error('Invalid key length');
const mode = { name: `AES-CBC`, length: key.length * 8 };
const cr = getWebcryptoSubtle();
const wKey = await cr.importKey('raw', key, mode, true, ['encrypt']);
const cipher = await cr.encrypt({ name: `aes-cbc`, iv: IV, counter: IV, length: 64 }, wKey, msg);
return new Uint8Array(cipher).subarray(0, 16);
}
// Kinda constant-time equality
function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
// Should not happen
if (a.length !== b.length) throw new Error('equalBytes: Different size of Uint8Arrays');
let flag = true;
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) flag &&= false;
return flag;
}
// Wrap position so it will be in padded to blockSize
const wrapPos = (pos: number, blockSize: number) => Math.ceil(pos / blockSize) * blockSize;
const limit = (name: string, min: number, max: number) => (value: number) => {
if (!Number.isSafeInteger(value) || min > value || value > max)
throw new Error(`${name}: invalid value=${value}, must be [${min}..${max}]`);
};
// From RFC 8452: Section 6
const AAD_LIMIT = limit('AAD', 0, 2 ** 36);
const PLAIN_LIMIT = limit('Plaintext', 0, 2 ** 36);
const NONCE_LIMIT = limit('Nonce', 12, 12);
const CIPHER_LIMIT = limit('Ciphertext', 16, 2 ** 36 + 16);
// nodejs api doesn't support 32bit counters, browser does
async function ctr(key: Uint8Array, tag: Uint8Array, input: Uint8Array) {
// The initial counter block is the tag with the most significant bit of the last byte set to one.
let block = tag.slice();
block[15] |= 0x80;
let view = createView(block);
let output = new Uint8Array(input.length);
for (let pos = 0; pos < input.length; ) {
const encryptedBlock = await encryptBlock(block, key);
view.setUint32(0, view.getUint32(0, true) + 1, true);
const take = Math.min(input.length, encryptedBlock.length);
for (let j = 0; j < take; j++, pos++) output[pos] = encryptedBlock[j] ^ input[pos];
}
return new Uint8Array(output);
}
export async function deriveKeys(key: Uint8Array, nonce: Uint8Array) {
NONCE_LIMIT(nonce.length);
const len = key.length;
if (len !== 16 && len !== 32)
throw new Error(`key length must be 16 or 32 bytes, got: ${len} bytes`);
const encKey = new Uint8Array(len);
const authKey = new Uint8Array(16);
let counter = 0;
const deriveBlock = new Uint8Array(nonce.length + 4);
deriveBlock.set(nonce, 4);
const view = createView(deriveBlock);
for (const derivedKey of [authKey, encKey]) {
for (let i = 0; i < derivedKey.length; i += 8) {
view.setUint32(0, counter++, true);
const block = await encryptBlock(deriveBlock, key);
derivedKey.set(block.subarray(0, 8), i);
}
}
return { authKey, encKey };
}
export async function aes_256_gcm_siv(
key: Uint8Array,
nonce: Uint8Array,
AAD: Uint8Array
): Promise<AsyncCipher> {
const { encKey, authKey } = await deriveKeys(key, nonce);
const computeTag = async (data: Uint8Array, AAD: Uint8Array) => {
const dataPos = wrapPos(AAD.length, 16);
const lenPos = wrapPos(dataPos + data.length, 16);
const block = new Uint8Array(lenPos + 16);
const view = createView(block);
block.set(AAD);
block.set(data, dataPos);
setBigUint64(view, lenPos, BigInt(AAD.length * 8), true);
setBigUint64(view, lenPos + 8, BigInt(data.length * 8), true);
// Compute the expected tag by XORing S_s and the nonce, clearing the
// most significant bit of the last byte and encrypting with the
// message-encryption key.
const tag = polyval(authKey, block);
for (let i = 0; i < 12; i++) tag[i] ^= nonce[i];
// Clear the highest bit
tag[15] &= 0x7f;
return await encryptBlock(tag, encKey);
};
return {
// computeTag,
encrypt: async (plaintext: Uint8Array) => {
AAD_LIMIT(AAD.length);
PLAIN_LIMIT(plaintext.length);
const tag = await computeTag(plaintext, AAD);
const out = new Uint8Array(plaintext.length + 16);
out.set(tag, plaintext.length);
out.set(await ctr(encKey, tag, plaintext));
return out;
},
decrypt: async (ciphertext: Uint8Array) => {
CIPHER_LIMIT(ciphertext.length);
AAD_LIMIT(AAD.length);
const tag = ciphertext.subarray(-16);
const plaintext = await ctr(encKey, tag, ciphertext.subarray(0, -16));
const expectedTag = await computeTag(plaintext, AAD);
if (!equalBytes(tag, expectedTag)) throw new Error('invalid poly1305 tag');
return plaintext;
},
};
}
+24
View File
@@ -0,0 +1,24 @@
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
// node.js versions earlier than v19 don't declare it in global scope.
// For node.js, package.js on#exports field mapping rewrites import
// from `crypto` to `cryptoNode`, which imports native module.
// Makes the utils un-importable in browsers without a bundler.
// Once node.js 18 is deprecated, we can just drop the import.
import { crypto } from '@noble/ciphers/webcrypto/crypto';
/**
* Secure PRNG. Uses `crypto.getRandomValues`, which defers to OS.
*/
export function randomBytes(bytesLength = 32): Uint8Array {
if (crypto && typeof crypto.getRandomValues === 'function') {
return crypto.getRandomValues(new Uint8Array(bytesLength));
}
throw new Error('crypto.getRandomValues must be defined');
}
export function getWebcryptoSubtle() {
if (crypto && typeof crypto.subtle === 'object' && crypto.subtle != null) {
return crypto.subtle;
}
throw new Error('crypto.subtle must be defined');
}
+63
View File
@@ -0,0 +1,63 @@
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
export type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array | Uint16Array | Int16Array | Uint32Array | Int32Array;
export declare const u8: (arr: TypedArray) => Uint8Array;
export declare const u16: (arr: TypedArray) => Uint16Array;
export declare const u32: (arr: TypedArray) => Uint32Array;
export declare const createView: (arr: TypedArray) => DataView;
export declare const isLE: boolean;
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
export declare function bytesToHex(bytes: Uint8Array): string;
/**
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
*/
export declare function hexToBytes(hex: string): Uint8Array;
export declare const nextTick: () => Promise<void>;
export declare function asyncLoop(iters: number, tick: number, cb: (i: number) => void): Promise<void>;
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
export declare function utf8ToBytes(str: string): Uint8Array;
export declare function bytesToUtf8(bytes: Uint8Array): string;
export type Input = Uint8Array | string;
/**
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
* Warning: when Uint8Array is passed, it would NOT get copied.
* Keep in mind for future mutable operations.
*/
export declare function toBytes(data: Input): Uint8Array;
/**
* Copies several Uint8Arrays into one.
*/
export declare function concatBytes(...arrays: Uint8Array[]): Uint8Array;
type EmptyObj = {};
export declare function checkOpts<T1 extends EmptyObj, T2 extends EmptyObj>(defaults: T1, opts?: T2): T1 & T2;
export declare function ensureBytes(b: any, len?: number): void;
export declare function equalBytes(a: Uint8Array, b: Uint8Array): boolean;
export declare abstract class Hash<T extends Hash<T>> {
abstract blockLen: number;
abstract outputLen: number;
abstract update(buf: Input): this;
abstract digestInto(buf: Uint8Array): void;
abstract digest(): Uint8Array;
/**
* Resets internal state. Makes Hash instance unusable.
* Reset is impossible for keyed hashes if key is consumed into state. If digest is not consumed
* by user, they will need to manually call `destroy()` when zeroing is necessary.
*/
abstract destroy(): void;
}
export type Cipher = {
tagLength?: number;
encrypt(plaintext: Uint8Array): Uint8Array;
decrypt(ciphertext: Uint8Array): Uint8Array;
};
export type AsyncCipher = {
tagLength?: number;
encrypt(plaintext: Uint8Array): Promise<Uint8Array>;
decrypt(ciphertext: Uint8Array): Promise<Uint8Array>;
};
export declare function setBigUint64(view: DataView, byteOffset: number, value: bigint, isLE: boolean): void;
export {};
//# sourceMappingURL=utils.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["src/utils.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAGvE,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,iBAAiB,GAAG,UAAU,GACjE,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,CAAC;AAItD,eAAO,MAAM,EAAE,QAAS,UAAU,eAA+D,CAAC;AAClG,eAAO,MAAM,GAAG,QAAS,UAAU,gBAC0C,CAAC;AAC9E,eAAO,MAAM,GAAG,QAAS,UAAU,gBAC0C,CAAC;AAG9E,eAAO,MAAM,UAAU,QAAS,UAAU,aACgB,CAAC;AAI3D,eAAO,MAAM,IAAI,SAAmE,CAAC;AAMrF;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAQpD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAalD;AAKD,eAAO,MAAM,QAAQ,qBAAiB,CAAC;AAGvC,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,iBAUnF;AAOD;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAGnD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAErD;AAED,MAAM,MAAM,KAAK,GAAG,UAAU,GAAG,MAAM,CAAC;AACxC;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,UAAU,CAI/C;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAS/D;AAMD,KAAK,QAAQ,GAAG,EAAE,CAAC;AACnB,wBAAgB,SAAS,CAAC,EAAE,SAAS,QAAQ,EAAE,EAAE,SAAS,QAAQ,EAChE,QAAQ,EAAE,EAAE,EACZ,IAAI,CAAC,EAAE,EAAE,GACR,EAAE,GAAG,EAAE,CAKT;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,QAI/C;AAGD,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAMhE;AAGD,8BAAsB,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI;IAEjC,QAAQ,CAAC,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;IAC1C,QAAQ,CAAC,MAAM,IAAI,UAAU;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,IAAI,IAAI;CACzB;AAID,MAAM,MAAM,MAAM,GAAG;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IAC3C,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACpD,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACtD,CAAC;AAGF,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,EACd,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,OAAO,GACZ,IAAI,CAUN"}
+163
View File
@@ -0,0 +1,163 @@
"use strict";
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
Object.defineProperty(exports, "__esModule", { value: true });
exports.setBigUint64 = exports.Hash = exports.equalBytes = exports.ensureBytes = exports.checkOpts = exports.concatBytes = exports.toBytes = exports.bytesToUtf8 = exports.utf8ToBytes = exports.asyncLoop = exports.nextTick = exports.hexToBytes = exports.bytesToHex = exports.isLE = exports.createView = exports.u32 = exports.u16 = exports.u8 = void 0;
const u8a = (a) => a instanceof Uint8Array;
// Cast array to different type
const u8 = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
exports.u8 = u8;
const u16 = (arr) => new Uint16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
exports.u16 = u16;
const u32 = (arr) => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
exports.u32 = u32;
// Cast array to view
const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
exports.createView = createView;
// big-endian hardware is rare. Just in case someone still decides to run ciphers:
// early-throw an error because we don't support BE yet.
exports.isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
if (!exports.isLE)
throw new Error('Non little-endian hardware is not supported');
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
function bytesToHex(bytes) {
if (!u8a(bytes))
throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
}
exports.bytesToHex = bytesToHex;
/**
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
*/
function hexToBytes(hex) {
if (typeof hex !== 'string')
throw new Error('hex string expected, got ' + typeof hex);
const len = hex.length;
if (len % 2)
throw new Error('padded hex string expected, got unpadded hex of length ' + len);
const array = new Uint8Array(len / 2);
for (let i = 0; i < array.length; i++) {
const j = i * 2;
const hexByte = hex.slice(j, j + 2);
const byte = Number.parseInt(hexByte, 16);
if (Number.isNaN(byte) || byte < 0)
throw new Error('Invalid byte sequence');
array[i] = byte;
}
return array;
}
exports.hexToBytes = hexToBytes;
// There is no setImmediate in browser and setTimeout is slow.
// call of async fn will return Promise, which will be fullfiled only on
// next scheduler queue processing step and this is exactly what we need.
const nextTick = async () => { };
exports.nextTick = nextTick;
// Returns control to thread each 'tick' ms to avoid blocking
async function asyncLoop(iters, tick, cb) {
let ts = Date.now();
for (let i = 0; i < iters; i++) {
cb(i);
// Date.now() is not monotonic, so in case if clock goes backwards we return return control too
const diff = Date.now() - ts;
if (diff >= 0 && diff < tick)
continue;
await (0, exports.nextTick)();
ts += diff;
}
}
exports.asyncLoop = asyncLoop;
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
function utf8ToBytes(str) {
if (typeof str !== 'string')
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
exports.utf8ToBytes = utf8ToBytes;
function bytesToUtf8(bytes) {
return new TextDecoder().decode(bytes);
}
exports.bytesToUtf8 = bytesToUtf8;
/**
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
* Warning: when Uint8Array is passed, it would NOT get copied.
* Keep in mind for future mutable operations.
*/
function toBytes(data) {
if (typeof data === 'string')
data = utf8ToBytes(data);
if (!u8a(data))
throw new Error(`expected Uint8Array, got ${typeof data}`);
return data;
}
exports.toBytes = toBytes;
/**
* Copies several Uint8Arrays into one.
*/
function concatBytes(...arrays) {
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
let pad = 0; // walk through each item, ensure they have proper type
arrays.forEach((a) => {
if (!u8a(a))
throw new Error('Uint8Array expected');
r.set(a, pad);
pad += a.length;
});
return r;
}
exports.concatBytes = concatBytes;
// Check if object doens't have custom constructor (like Uint8Array/Array)
const isPlainObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]' && obj.constructor === Object;
function checkOpts(defaults, opts) {
if (opts !== undefined && (typeof opts !== 'object' || !isPlainObject(opts)))
throw new Error('options must be object or undefined');
const merged = Object.assign(defaults, opts);
return merged;
}
exports.checkOpts = checkOpts;
function ensureBytes(b, len) {
if (!(b instanceof Uint8Array))
throw new Error('Uint8Array expected');
if (typeof len === 'number')
if (b.length !== len)
throw new Error(`Uint8Array length ${len} expected`);
}
exports.ensureBytes = ensureBytes;
// Constant-time equality
function equalBytes(a, b) {
// Should not happen
if (a.length !== b.length)
throw new Error('equalBytes: Different size of Uint8Arrays');
let isSame = true;
for (let i = 0; i < a.length; i++)
isSame && (isSame = a[i] === b[i]); // Lets hope JIT won't optimize away.
return isSame;
}
exports.equalBytes = equalBytes;
// For runtime check if class implements interface
class Hash {
}
exports.Hash = Hash;
// Polyfill for Safari 14
function setBigUint64(view, byteOffset, value, isLE) {
if (typeof view.setBigUint64 === 'function')
return view.setBigUint64(byteOffset, value, isLE);
const _32n = BigInt(32);
const _u32_max = BigInt(0xffffffff);
const wh = Number((value >> _32n) & _u32_max);
const wl = Number(value & _u32_max);
const h = isLE ? 4 : 0;
const l = isLE ? 0 : 4;
view.setUint32(byteOffset + h, wh, isLE);
view.setUint32(byteOffset + l, wl, isLE);
}
exports.setBigUint64 = setBigUint64;
//# sourceMappingURL=utils.js.map
+1
View File
File diff suppressed because one or more lines are too long
+31
View File
@@ -0,0 +1,31 @@
export declare const aes_128_ctr: (key: Uint8Array, nonce: Uint8Array) => {
keyLength: number;
encrypt(plaintext: Uint8Array): Promise<Uint8Array>;
decrypt(ciphertext: Uint8Array): Promise<Uint8Array>;
};
export declare const aes_256_ctr: (key: Uint8Array, nonce: Uint8Array) => {
keyLength: number;
encrypt(plaintext: Uint8Array): Promise<Uint8Array>;
decrypt(ciphertext: Uint8Array): Promise<Uint8Array>;
};
export declare const aes_128_cbc: (key: Uint8Array, nonce: Uint8Array) => {
keyLength: number;
encrypt(plaintext: Uint8Array): Promise<Uint8Array>;
decrypt(ciphertext: Uint8Array): Promise<Uint8Array>;
};
export declare const aes_256_cbc: (key: Uint8Array, nonce: Uint8Array) => {
keyLength: number;
encrypt(plaintext: Uint8Array): Promise<Uint8Array>;
decrypt(ciphertext: Uint8Array): Promise<Uint8Array>;
};
export declare const aes_128_gcm: (key: Uint8Array, nonce: Uint8Array) => {
keyLength: number;
encrypt(plaintext: Uint8Array): Promise<Uint8Array>;
decrypt(ciphertext: Uint8Array): Promise<Uint8Array>;
};
export declare const aes_256_gcm: (key: Uint8Array, nonce: Uint8Array) => {
keyLength: number;
encrypt(plaintext: Uint8Array): Promise<Uint8Array>;
decrypt(ciphertext: Uint8Array): Promise<Uint8Array>;
};
//# sourceMappingURL=aes.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"aes.d.ts","sourceRoot":"","sources":["../src/webcrypto/aes.ts"],"names":[],"mappings":"AAwCA,eAAO,MAAM,WAAW,QA/BT,UAAU,SAAS,UAAU;;uBAYb,UAAU,GAAG,QAAQ,UAAU,CAAC;wBAQ/B,UAAU,GAAG,QAAQ,UAAU,CAAC;CAWb,CAAC;AACpD,eAAO,MAAM,WAAW,QAhCT,UAAU,SAAS,UAAU;;uBAYb,UAAU,GAAG,QAAQ,UAAU,CAAC;wBAQ/B,UAAU,GAAG,QAAQ,UAAU,CAAC;CAYb,CAAC;AAEpD,eAAO,MAAM,WAAW,QAlCT,UAAU,SAAS,UAAU;;uBAYb,UAAU,GAAG,QAAQ,UAAU,CAAC;wBAQ/B,UAAU,GAAG,QAAQ,UAAU,CAAC;CAcb,CAAC;AACpD,eAAO,MAAM,WAAW,QAnCT,UAAU,SAAS,UAAU;;uBAYb,UAAU,GAAG,QAAQ,UAAU,CAAC;wBAQ/B,UAAU,GAAG,QAAQ,UAAU,CAAC;CAeb,CAAC;AAEpD,eAAO,MAAM,WAAW,QArCT,UAAU,SAAS,UAAU;;uBAYb,UAAU,GAAG,QAAQ,UAAU,CAAC;wBAQ/B,UAAU,GAAG,QAAQ,UAAU,CAAC;CAiBb,CAAC;AACpD,eAAO,MAAM,WAAW,QAtCT,UAAU,SAAS,UAAU;;uBAYb,UAAU,GAAG,QAAQ,UAAU,CAAC;wBAQ/B,UAAU,GAAG,QAAQ,UAAU,CAAC;CAkBb,CAAC"}
+45
View File
@@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.aes_256_gcm = exports.aes_128_gcm = exports.aes_256_cbc = exports.aes_128_cbc = exports.aes_256_ctr = exports.aes_128_ctr = void 0;
const utils_js_1 = require("../utils.js");
const utils_js_2 = require("./utils.js");
function generate(algo, length) {
const keyLength = length / 8;
const keyParams = { name: algo, length };
const cryptParams = { name: algo };
// const params: Record<string, any> = ({ e: algo, i: { name: algo, length } });
return (key, nonce) => {
(0, utils_js_1.ensureBytes)(key, keyLength);
if (algo === 'AES-CTR') {
cryptParams.counter = nonce;
cryptParams.length = 64;
}
else {
cryptParams.iv = nonce;
}
return {
keyLength,
async encrypt(plaintext) {
(0, utils_js_1.ensureBytes)(plaintext);
const cr = (0, utils_js_2.getWebcryptoSubtle)();
const iKey = await cr.importKey('raw', key, keyParams, true, ['encrypt']);
const cipher = await cr.encrypt(cryptParams, iKey, plaintext);
return new Uint8Array(cipher);
},
async decrypt(ciphertext) {
(0, utils_js_1.ensureBytes)(ciphertext);
const cr = (0, utils_js_2.getWebcryptoSubtle)();
const iKey = await cr.importKey('raw', key, keyParams, true, ['decrypt']);
const plaintext = await cr.decrypt(cryptParams, iKey, ciphertext);
return new Uint8Array(plaintext);
},
};
};
}
exports.aes_128_ctr = generate('AES-CTR', 128);
exports.aes_256_ctr = generate('AES-CTR', 256);
exports.aes_128_cbc = generate('AES-CBC', 128);
exports.aes_256_cbc = generate('AES-CBC', 256);
exports.aes_128_gcm = generate('AES-GCM', 128);
exports.aes_256_gcm = generate('AES-GCM', 256);
//# sourceMappingURL=aes.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"aes.js","sourceRoot":"","sources":["../src/webcrypto/aes.ts"],"names":[],"mappings":";;;AAAA,0CAA0C;AAC1C,yCAAgD;AAEhD,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc;IAC5C,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,CAAC;IAC7B,MAAM,SAAS,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACzC,MAAM,WAAW,GAAwB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxD,gFAAgF;IAEhF,OAAO,CAAC,GAAe,EAAE,KAAiB,EAAE,EAAE;QAC5C,IAAA,sBAAW,EAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC5B,IAAI,IAAI,KAAK,SAAS,EAAE;YACtB,WAAW,CAAC,OAAO,GAAG,KAAK,CAAC;YAC5B,WAAW,CAAC,MAAM,GAAG,EAAE,CAAC;SACzB;aAAM;YACL,WAAW,CAAC,EAAE,GAAG,KAAK,CAAC;SACxB;QAED,OAAO;YACL,SAAS;YAET,KAAK,CAAC,OAAO,CAAC,SAAqB;gBACjC,IAAA,sBAAW,EAAC,SAAS,CAAC,CAAC;gBACvB,MAAM,EAAE,GAAG,IAAA,6BAAkB,GAAE,CAAC;gBAChC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC1E,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;gBAC9D,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;YAChC,CAAC;YAED,KAAK,CAAC,OAAO,CAAC,UAAsB;gBAClC,IAAA,sBAAW,EAAC,UAAU,CAAC,CAAC;gBACxB,MAAM,EAAE,GAAG,IAAA,6BAAkB,GAAE,CAAC;gBAChC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC1E,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;gBAClE,OAAO,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;YACnC,CAAC;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAEY,QAAA,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AACvC,QAAA,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AAEvC,QAAA,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AACvC,QAAA,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AAEvC,QAAA,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AACvC,QAAA,WAAW,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC"}
+2
View File
@@ -0,0 +1,2 @@
export declare const crypto: any;
//# sourceMappingURL=crypto.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/webcrypto/crypto.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,MAAM,KACuE,CAAC"}
+5
View File
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.crypto = void 0;
exports.crypto = typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
//# sourceMappingURL=crypto.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/webcrypto/crypto.ts"],"names":[],"mappings":";;;AAGa,QAAA,MAAM,GACjB,OAAO,UAAU,KAAK,QAAQ,IAAI,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC"}

Some files were not shown because too many files have changed in this diff Show More