Add comprehensive installation and setup documentation

- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
This commit is contained in:
Dorian
2026-01-27 17:18:21 +00:00
parent a81f655133
commit 0d073fa89e
22658 changed files with 4494151 additions and 6 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019-2023 Weiliang Li
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.
+155
View File
@@ -0,0 +1,155 @@
# eciesjs
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/47784cde956642b1b9e8e33cb8551674)](https://app.codacy.com/app/ecies/js)
[![License](https://img.shields.io/github/license/ecies/js.svg)](https://github.com/ecies/js)
[![Npm Package](https://img.shields.io/npm/v/eciesjs.svg)](https://www.npmjs.com/package/eciesjs)
[![CI](https://img.shields.io/github/actions/workflow/status/ecies/js/ci.yml)](https://github.com/ecies/js/actions)
[![Codecov](https://img.shields.io/codecov/c/github/ecies/js.svg)](https://codecov.io/gh/ecies/js)
Elliptic Curve Integrated Encryption Scheme for secp256k1/curve25519 in TypeScript.
This is the JavaScript/TypeScript version of [eciespy](https://github.com/ecies/py) with a built-in class-like secp256k1/curve25519 [API](#privatekey), you may go there for detailed documentation and learn the mechanism under the hood.
If you want a WASM version to run directly in modern browsers or on some blockchains, check [`ecies-wasm`](https://github.com/ecies/rs-wasm).
## Install
```bash
npm install eciesjs
```
We recommend using the latest Node runtime although it's still possible to install on old versions.
## Quick Start
Run the code below with `npx ts-node`.
```typescript
> import { encrypt, decrypt, PrivateKey } from 'eciesjs'
> const sk = new PrivateKey()
> const data = Buffer.from('hello world🌍')
> decrypt(sk.secret, encrypt(sk.publicKey.toHex(), data)).toString()
'hello world🌍'
```
See [Configuration](#configuration) to control with more granularity.
## API
### `encrypt(receiverRawPK: string | Uint8Array, msg: Uint8Array): Buffer`
Parameters:
- **receiverRawPK** - Receiver's public key, hex string or buffer
- **msg** - Data to encrypt
Returns: **Buffer**
### `decrypt(receiverRawSK: string | Uint8Array, msg: Uint8Array): Buffer`
Parameters:
- **receiverRawSK** - Receiver's private key, hex string or buffer
- **msg** - Data to decrypt
Returns: **Buffer**
### `PrivateKey`
- Methods
```typescript
static fromHex(hex: string): PrivateKey;
constructor(secret?: Uint8Array);
toHex(): string;
encapsulate(pk: PublicKey): Uint8Array;
multiply(pk: PublicKey, compressed?: boolean): Uint8Array;
equals(other: PrivateKey): boolean;
```
- Properties
```typescript
get secret(): Buffer;
readonly publicKey: PublicKey;
private readonly data;
```
### `PublicKey`
- Methods
```typescript
static fromHex(hex: string): PublicKey;
constructor(data: Uint8Array);
toHex(compressed?: boolean): string;
decapsulate(sk: PrivateKey): Uint8Array;
equals(other: PublicKey): boolean;
```
- Properties
```typescript
get uncompressed(): Buffer;
get compressed(): Buffer;
private readonly data;
```
## Configuration
Following configurations are available.
- Elliptic curve: secp256k1 or curve25519 (x25519/ed25519)
- Ephemeral key format in the payload: compressed or uncompressed (only for secp256k1)
- Shared elliptic curve key format in the key derivation: compressed or uncompressed (only for secp256k1)
- Symmetric cipher algorithm: AES-256-GCM or XChaCha20-Poly1305
- Symmetric nonce length: 12 or 16 bytes (only for AES-256-GCM)
For compatibility, make sure different applications share the same configuration.
```ts
export type EllipticCurve = "secp256k1" | "x25519" | "ed25519";
export type SymmetricAlgorithm = "aes-256-gcm" | "xchacha20";
export type NonceLength = 12 | 16;
class Config {
ellipticCurve: EllipticCurve = "secp256k1";
isEphemeralKeyCompressed: boolean = false;
isHkdfKeyCompressed: boolean = false;
symmetricAlgorithm: SymmetricAlgorithm = "aes-256-gcm";
symmetricNonceLength: NonceLength = 16;
}
export const ECIES_CONFIG = new Config();
```
### Elliptic curve configuration
On `ellipticCurve = "x25519"` or `ellipticCurve = "ed25519"`, x25519 (key exchange function on curve25519) or ed25519 (signature algorithm on curve25519) will be used for key exchange instead of secp256k1.
In this case, the payload would always be: `32 Bytes + Ciphered` regardless of `isEphemeralKeyCompressed`.
> If you don't know how to choose between x25519 and ed25519, just use the dedicated key exchange function x25519 for efficiency.
### Secp256k1-specific configuration
On `isEphemeralKeyCompressed = true`, the payload would be: `33 Bytes + Ciphered` instead of `65 Bytes + Ciphered`.
On `isHkdfKeyCompressed = true`, the hkdf key would be derived from `ephemeral public key (compressed) + shared public key (compressed)` instead of `ephemeral public key (uncompressed) + shared public key (uncompressed)`.
### Symmetric cipher configuration
On `symmetricAlgorithm = "xchacha20"`, plaintext data would be encrypted with XChaCha20-Poly1305.
On `symmetricNonceLength = 12`, the nonce of AES-256-GCM would be 12 bytes. XChaCha20-Poly1305's nonce is always 24 bytes regardless of `symmetricNonceLength`.
## Security Audit
Following dependencies are audited:
- [noble-curves](https://github.com/paulmillr/noble-curves/tree/main/audit)
- [noble-hashes](https://github.com/paulmillr/noble-hashes#security)
## Changelog
See [CHANGELOG.md](./CHANGELOG.md).
+18
View File
@@ -0,0 +1,18 @@
export type EllipticCurve = "secp256k1" | "x25519" | "ed25519";
export type SymmetricAlgorithm = "aes-256-gcm" | "xchacha20";
export type NonceLength = 12 | 16;
declare class Config {
ellipticCurve: EllipticCurve;
isEphemeralKeyCompressed: boolean;
isHkdfKeyCompressed: boolean;
symmetricAlgorithm: SymmetricAlgorithm;
symmetricNonceLength: NonceLength;
}
export declare const ECIES_CONFIG: Config;
export declare const ellipticCurve: () => EllipticCurve;
export declare const isEphemeralKeyCompressed: () => boolean;
export declare const isHkdfKeyCompressed: () => boolean;
export declare const symmetricAlgorithm: () => SymmetricAlgorithm;
export declare const symmetricNonceLength: () => NonceLength;
export declare const ephemeralKeySize: () => number;
export {};
+41
View File
@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ephemeralKeySize = exports.symmetricNonceLength = exports.symmetricAlgorithm = exports.isHkdfKeyCompressed = exports.isEphemeralKeyCompressed = exports.ellipticCurve = exports.ECIES_CONFIG = void 0;
var consts_1 = require("./consts");
var Config = /** @class */ (function () {
function Config() {
this.ellipticCurve = "secp256k1";
this.isEphemeralKeyCompressed = false; // secp256k1 only
this.isHkdfKeyCompressed = false; // secp256k1 only
this.symmetricAlgorithm = "aes-256-gcm";
this.symmetricNonceLength = 16; // aes-256-gcm only
}
return Config;
}());
exports.ECIES_CONFIG = new Config();
var ellipticCurve = function () { return exports.ECIES_CONFIG.ellipticCurve; };
exports.ellipticCurve = ellipticCurve;
var isEphemeralKeyCompressed = function () { return exports.ECIES_CONFIG.isEphemeralKeyCompressed; };
exports.isEphemeralKeyCompressed = isEphemeralKeyCompressed;
var isHkdfKeyCompressed = function () { return exports.ECIES_CONFIG.isHkdfKeyCompressed; };
exports.isHkdfKeyCompressed = isHkdfKeyCompressed;
var symmetricAlgorithm = function () { return exports.ECIES_CONFIG.symmetricAlgorithm; };
exports.symmetricAlgorithm = symmetricAlgorithm;
var symmetricNonceLength = function () { return exports.ECIES_CONFIG.symmetricNonceLength; };
exports.symmetricNonceLength = symmetricNonceLength;
var ephemeralKeySize = function () {
var mapping = {
secp256k1: exports.ECIES_CONFIG.isEphemeralKeyCompressed
? consts_1.COMPRESSED_PUBLIC_KEY_SIZE
: consts_1.UNCOMPRESSED_PUBLIC_KEY_SIZE,
x25519: consts_1.CURVE25519_PUBLIC_KEY_SIZE,
ed25519: consts_1.CURVE25519_PUBLIC_KEY_SIZE,
};
if (exports.ECIES_CONFIG.ellipticCurve in mapping) {
return mapping[exports.ECIES_CONFIG.ellipticCurve];
}
else {
throw new Error("Not implemented");
}
};
exports.ephemeralKeySize = ephemeralKeySize;
+7
View File
@@ -0,0 +1,7 @@
export declare const SECRET_KEY_LENGTH = 32;
export declare const COMPRESSED_PUBLIC_KEY_SIZE = 33;
export declare const UNCOMPRESSED_PUBLIC_KEY_SIZE = 65;
export declare const ETH_PUBLIC_KEY_SIZE = 64;
export declare const CURVE25519_PUBLIC_KEY_SIZE = 32;
export declare const XCHACHA20_NONCE_LENGTH = 24;
export declare const AEAD_TAG_LENGTH = 16;
+12
View File
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AEAD_TAG_LENGTH = exports.XCHACHA20_NONCE_LENGTH = exports.CURVE25519_PUBLIC_KEY_SIZE = exports.ETH_PUBLIC_KEY_SIZE = exports.UNCOMPRESSED_PUBLIC_KEY_SIZE = exports.COMPRESSED_PUBLIC_KEY_SIZE = exports.SECRET_KEY_LENGTH = void 0;
// elliptic
exports.SECRET_KEY_LENGTH = 32;
exports.COMPRESSED_PUBLIC_KEY_SIZE = 33;
exports.UNCOMPRESSED_PUBLIC_KEY_SIZE = 65;
exports.ETH_PUBLIC_KEY_SIZE = 64;
exports.CURVE25519_PUBLIC_KEY_SIZE = 32;
// symmetric
exports.XCHACHA20_NONCE_LENGTH = 24;
exports.AEAD_TAG_LENGTH = 16;
+13
View File
@@ -0,0 +1,13 @@
/// <reference types="node" />
import { aesDecrypt, aesEncrypt, decodeHex, getValidSecret, remove0x } from "./utils";
export declare function encrypt(receiverRawPK: string | Uint8Array, msg: Uint8Array): Buffer;
export declare function decrypt(receiverRawSK: string | Uint8Array, msg: Uint8Array): Buffer;
export { ECIES_CONFIG } from "./config";
export { PrivateKey, PublicKey } from "./keys";
export declare const utils: {
aesDecrypt: typeof aesDecrypt;
aesEncrypt: typeof aesEncrypt;
decodeHex: typeof decodeHex;
getValidSecret: typeof getValidSecret;
remove0x: typeof remove0x;
};
+48
View File
@@ -0,0 +1,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.utils = exports.PublicKey = exports.PrivateKey = exports.ECIES_CONFIG = exports.decrypt = exports.encrypt = void 0;
var utils_1 = require("@noble/ciphers/utils");
var config_1 = require("./config");
var keys_1 = require("./keys");
var utils_2 = require("./utils");
function encrypt(receiverRawPK, msg) {
var ephemeralKey = new keys_1.PrivateKey();
var receiverPK = receiverRawPK instanceof Uint8Array
? new keys_1.PublicKey(receiverRawPK)
: keys_1.PublicKey.fromHex(receiverRawPK);
var symKey = ephemeralKey.encapsulate(receiverPK);
var encrypted = (0, utils_2.aesEncrypt)(symKey, msg);
var pk;
if ((0, config_1.isEphemeralKeyCompressed)()) {
pk = ephemeralKey.publicKey.compressed;
}
else {
pk = ephemeralKey.publicKey.uncompressed;
}
return Buffer.from((0, utils_1.concatBytes)(pk, encrypted));
}
exports.encrypt = encrypt;
function decrypt(receiverRawSK, msg) {
var receiverSK = receiverRawSK instanceof Uint8Array
? new keys_1.PrivateKey(receiverRawSK)
: keys_1.PrivateKey.fromHex(receiverRawSK);
var keySize = (0, config_1.ephemeralKeySize)();
var senderPK = new keys_1.PublicKey(msg.subarray(0, keySize));
var encrypted = msg.subarray(keySize);
var symKey = senderPK.decapsulate(receiverSK);
return Buffer.from((0, utils_2.aesDecrypt)(symKey, encrypted));
}
exports.decrypt = decrypt;
var config_2 = require("./config");
Object.defineProperty(exports, "ECIES_CONFIG", { enumerable: true, get: function () { return config_2.ECIES_CONFIG; } });
var keys_2 = require("./keys");
Object.defineProperty(exports, "PrivateKey", { enumerable: true, get: function () { return keys_2.PrivateKey; } });
Object.defineProperty(exports, "PublicKey", { enumerable: true, get: function () { return keys_2.PublicKey; } });
exports.utils = {
// TODO: review these before 0.5.0
aesDecrypt: utils_2.aesDecrypt,
aesEncrypt: utils_2.aesEncrypt,
decodeHex: utils_2.decodeHex,
getValidSecret: utils_2.getValidSecret,
remove0x: utils_2.remove0x,
};
+13
View File
@@ -0,0 +1,13 @@
/// <reference types="node" />
import { PublicKey } from "./PublicKey";
export declare class PrivateKey {
static fromHex(hex: string): PrivateKey;
private readonly data;
readonly publicKey: PublicKey;
get secret(): Buffer;
constructor(secret?: Uint8Array);
toHex(): string;
encapsulate(pk: PublicKey): Uint8Array;
multiply(pk: PublicKey, compressed?: boolean): Uint8Array;
equals(other: PrivateKey): boolean;
}
+53
View File
@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrivateKey = void 0;
var utils_1 = require("@noble/ciphers/utils");
var config_1 = require("../config");
var utils_2 = require("../utils");
var PublicKey_1 = require("./PublicKey");
var PrivateKey = /** @class */ (function () {
function PrivateKey(secret) {
var sk = secret === undefined ? (0, utils_2.getValidSecret)() : secret;
if (!(0, utils_2.isValidPrivateKey)(sk)) {
throw new Error("Invalid private key");
}
this.data = sk;
this.publicKey = new PublicKey_1.PublicKey((0, utils_2.getPublicKey)(sk));
}
PrivateKey.fromHex = function (hex) {
return new PrivateKey((0, utils_2.decodeHex)(hex));
};
Object.defineProperty(PrivateKey.prototype, "secret", {
get: function () {
// TODO: Uint8Array
return Buffer.from(this.data);
},
enumerable: false,
configurable: true
});
PrivateKey.prototype.toHex = function () {
return (0, utils_1.bytesToHex)(this.data);
};
PrivateKey.prototype.encapsulate = function (pk) {
var senderPoint;
var sharedPoint;
if ((0, config_1.isHkdfKeyCompressed)()) {
senderPoint = this.publicKey.compressed;
sharedPoint = this.multiply(pk, true);
}
else {
senderPoint = this.publicKey.uncompressed;
sharedPoint = this.multiply(pk, false);
}
return (0, utils_2.getSharedKey)(senderPoint, sharedPoint);
};
PrivateKey.prototype.multiply = function (pk, compressed) {
if (compressed === void 0) { compressed = false; }
return (0, utils_2.getSharedPoint)(this.data, pk.compressed, compressed);
};
PrivateKey.prototype.equals = function (other) {
return (0, utils_1.equalBytes)(this.data, other.data);
};
return PrivateKey;
}());
exports.PrivateKey = PrivateKey;
+12
View File
@@ -0,0 +1,12 @@
/// <reference types="node" />
import { PrivateKey } from "./PrivateKey";
export declare class PublicKey {
static fromHex(hex: string): PublicKey;
private readonly data;
get uncompressed(): Buffer;
get compressed(): Buffer;
constructor(data: Uint8Array);
toHex(compressed?: boolean): string;
decapsulate(sk: PrivateKey): Uint8Array;
equals(other: PublicKey): boolean;
}
+57
View File
@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PublicKey = void 0;
var utils_1 = require("@noble/ciphers/utils");
var config_1 = require("../config");
var utils_2 = require("../utils");
var PublicKey = /** @class */ (function () {
function PublicKey(data) {
this.data = (0, utils_2.convertPublicKeyFormat)(data, true);
}
PublicKey.fromHex = function (hex) {
return new PublicKey((0, utils_2.hexToPublicKey)(hex));
};
Object.defineProperty(PublicKey.prototype, "uncompressed", {
get: function () {
// TODO: Uint8Array
return Buffer.from((0, utils_2.convertPublicKeyFormat)(this.data, false));
},
enumerable: false,
configurable: true
});
Object.defineProperty(PublicKey.prototype, "compressed", {
get: function () {
// TODO: Uint8Array
return Buffer.from(this.data);
},
enumerable: false,
configurable: true
});
PublicKey.prototype.toHex = function (compressed) {
if (compressed === void 0) { compressed = true; }
if (compressed) {
return (0, utils_1.bytesToHex)(this.data);
}
else {
return (0, utils_1.bytesToHex)(this.uncompressed);
}
};
PublicKey.prototype.decapsulate = function (sk) {
var senderPoint;
var sharedPoint;
if ((0, config_1.isHkdfKeyCompressed)()) {
senderPoint = this.data;
sharedPoint = sk.multiply(this, true);
}
else {
senderPoint = this.uncompressed;
sharedPoint = sk.multiply(this, false);
}
return (0, utils_2.getSharedKey)(senderPoint, sharedPoint);
};
PublicKey.prototype.equals = function (other) {
return (0, utils_1.equalBytes)(this.data, other.data);
};
return PublicKey;
}());
exports.PublicKey = PublicKey;
+2
View File
@@ -0,0 +1,2 @@
export { PrivateKey } from "./PrivateKey";
export { PublicKey } from "./PublicKey";
+9
View File
@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PublicKey = exports.PrivateKey = void 0;
// treat Buffer as Uint8array, i.e. no call of Buffer specific functions
// finally Uint8Array only
var PrivateKey_1 = require("./PrivateKey");
Object.defineProperty(exports, "PrivateKey", { enumerable: true, get: function () { return PrivateKey_1.PrivateKey; } });
var PublicKey_1 = require("./PublicKey");
Object.defineProperty(exports, "PublicKey", { enumerable: true, get: function () { return PublicKey_1.PublicKey; } });
+2
View File
@@ -0,0 +1,2 @@
import { Cipher } from "@noble/ciphers/utils";
export declare function aes256gcm(key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array): Cipher;
+36
View File
@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.aes256gcm = void 0;
var utils_1 = require("@noble/ciphers/utils");
var crypto_1 = require("crypto");
var consts_1 = require("../consts");
// make `node:crypto`'s aes compatible with `@noble/ciphers`
function aes256gcm(key, nonce, AAD) {
var encrypt = function (plainText) {
var cipher = (0, crypto_1.createCipheriv)("aes-256-gcm", key, nonce);
if (AAD) {
cipher.setAAD(AAD);
}
var updated = cipher.update(plainText);
var finalized = cipher.final();
return (0, utils_1.concatBytes)(updated, finalized, cipher.getAuthTag());
};
var decrypt = function (cipherText) {
var encrypted = cipherText.subarray(0, cipherText.length - consts_1.AEAD_TAG_LENGTH);
var tag = cipherText.subarray(-consts_1.AEAD_TAG_LENGTH);
var decipher = (0, crypto_1.createDecipheriv)("aes-256-gcm", key, nonce);
if (AAD) {
decipher.setAAD(AAD);
}
decipher.setAuthTag(tag);
var updated = decipher.update(encrypted);
var finalized = decipher.final();
return (0, utils_1.concatBytes)(updated, finalized);
};
return {
tagLength: consts_1.AEAD_TAG_LENGTH,
encrypt: encrypt,
decrypt: decrypt,
};
}
exports.aes256gcm = aes256gcm;
+7
View File
@@ -0,0 +1,7 @@
export declare function getValidSecret(): Uint8Array;
export declare function isValidPrivateKey(secret: Uint8Array): boolean;
export declare function getPublicKey(secret: Uint8Array): Uint8Array;
export declare function getSharedKey(ephemeralPoint: Uint8Array, sharedPoint: Uint8Array): Uint8Array;
export declare function getSharedPoint(sk: Uint8Array, pk: Uint8Array, compressed?: boolean): Uint8Array;
export declare function convertPublicKeyFormat(pk: Uint8Array, compressed: boolean): Uint8Array;
export declare function hexToPublicKey(hex: string): Uint8Array;
+74
View File
@@ -0,0 +1,74 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hexToPublicKey = exports.convertPublicKeyFormat = exports.getSharedPoint = exports.getSharedKey = exports.getPublicKey = exports.isValidPrivateKey = exports.getValidSecret = void 0;
var utils_1 = require("@noble/ciphers/utils");
var utils_2 = require("@noble/ciphers/webcrypto/utils");
var ed25519_1 = require("@noble/curves/ed25519");
var secp256k1_1 = require("@noble/curves/secp256k1");
var config_1 = require("../config");
var consts_1 = require("../consts");
var hex_1 = require("./hex");
var symmetric_1 = require("./symmetric");
function getValidSecret() {
var key;
do {
key = (0, utils_2.randomBytes)(consts_1.SECRET_KEY_LENGTH);
} while (!isValidPrivateKey(key));
return key;
}
exports.getValidSecret = getValidSecret;
function isValidPrivateKey(secret) {
// on secp256k1: only key ∈ (0, group order) is valid
// on curve25519: any 32-byte key is valid
return _exec(function (curve) { return curve.utils.isValidPrivateKey(secret); }, function () { return true; }, function () { return true; });
}
exports.isValidPrivateKey = isValidPrivateKey;
function getPublicKey(secret) {
return _exec(function (curve) { return curve.getPublicKey(secret); }, function (curve) { return curve.getPublicKey(secret); }, function (curve) { return curve.getPublicKey(secret); });
}
exports.getPublicKey = getPublicKey;
function getSharedKey(ephemeralPoint, sharedPoint) {
return (0, symmetric_1.deriveKey)((0, utils_1.concatBytes)(ephemeralPoint, sharedPoint));
}
exports.getSharedKey = getSharedKey;
function getSharedPoint(sk, pk, compressed) {
return _exec(function (curve) { return curve.getSharedSecret(sk, pk, compressed); }, function (curve) { return curve.getSharedSecret(sk, pk); }, function (curve) {
// Note: scalar is hashed from sk
var scalar = curve.utils.getExtendedPublicKey(sk).scalar;
var point = curve.ExtendedPoint.fromHex(pk).multiply(scalar);
return point.toRawBytes();
});
}
exports.getSharedPoint = getSharedPoint;
function convertPublicKeyFormat(pk, compressed) {
// only for secp256k1
return _exec(function (curve) { return curve.getSharedSecret(BigInt(1), pk, compressed); }, function () { return pk; }, function () { return pk; });
}
exports.convertPublicKeyFormat = convertPublicKeyFormat;
function hexToPublicKey(hex) {
var decoded = (0, hex_1.decodeHex)(hex);
return _exec(function () {
if (decoded.length === consts_1.ETH_PUBLIC_KEY_SIZE) {
var fixed = new Uint8Array(1 + decoded.length);
fixed.set([0x04]);
fixed.set(decoded, 1);
return fixed;
}
return decoded;
}, function () { return decoded; }, function () { return decoded; });
}
exports.hexToPublicKey = hexToPublicKey;
function _exec(secp256k1Callback, x25519Callback, ed25519Callback) {
if ((0, config_1.ellipticCurve)() === "secp256k1") {
return secp256k1Callback(secp256k1_1.secp256k1);
}
else if ((0, config_1.ellipticCurve)() === "x25519") {
return x25519Callback(ed25519_1.x25519);
}
else if ((0, config_1.ellipticCurve)() === "ed25519") {
return ed25519Callback(ed25519_1.ed25519);
}
else {
throw new Error("Not implemented");
}
}
+2
View File
@@ -0,0 +1,2 @@
export declare function remove0x(hex: string): string;
export declare function decodeHex(hex: string): Uint8Array;
+15
View File
@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeHex = exports.remove0x = void 0;
var utils_1 = require("@noble/ciphers/utils");
function remove0x(hex) {
if (hex.startsWith("0x") || hex.startsWith("0X")) {
return hex.slice(2);
}
return hex;
}
exports.remove0x = remove0x;
function decodeHex(hex) {
return (0, utils_1.hexToBytes)(remove0x(hex));
}
exports.decodeHex = decodeHex;
+3
View File
@@ -0,0 +1,3 @@
export * from "./elliptic";
export * from "./hex";
export * from "./symmetric";
+20
View File
@@ -0,0 +1,20 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
// under this folder no `Buffer`
__exportStar(require("./elliptic"), exports);
__exportStar(require("./hex"), exports);
__exportStar(require("./symmetric"), exports);
+3
View File
@@ -0,0 +1,3 @@
export declare function aesEncrypt(key: Uint8Array, plainText: Uint8Array): Uint8Array;
export declare function aesDecrypt(key: Uint8Array, cipherText: Uint8Array): Uint8Array;
export declare function deriveKey(master: Uint8Array): Uint8Array;
+56
View File
@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.deriveKey = exports.aesDecrypt = exports.aesEncrypt = void 0;
var chacha_1 = require("@noble/ciphers/chacha");
var utils_1 = require("@noble/ciphers/utils");
var utils_2 = require("@noble/ciphers/webcrypto/utils");
var hkdf_1 = require("@noble/hashes/hkdf");
var sha256_1 = require("@noble/hashes/sha256");
var config_1 = require("../config");
var consts_1 = require("../consts");
var compat_1 = require("./compat");
function aesEncrypt(key, plainText) {
// TODO: Rename to symEncrypt
return _exec(true, key, plainText);
}
exports.aesEncrypt = aesEncrypt;
function aesDecrypt(key, cipherText) {
// TODO: Rename to symDecrypt
return _exec(false, key, cipherText);
}
exports.aesDecrypt = aesDecrypt;
function deriveKey(master) {
// 32 bytes shared secret for aes and xchacha20
return (0, hkdf_1.hkdf)(sha256_1.sha256, master, undefined, undefined, 32);
}
exports.deriveKey = deriveKey;
function _exec(is_encryption, key, data) {
var algorithm = (0, config_1.symmetricAlgorithm)();
var callback = is_encryption ? _encrypt : _decrypt;
if (algorithm === "aes-256-gcm") {
return callback(compat_1.aes256gcm, key, data, (0, config_1.symmetricNonceLength)());
}
else if (algorithm === "xchacha20") {
return callback(chacha_1.xchacha20poly1305, key, data, consts_1.XCHACHA20_NONCE_LENGTH);
}
else {
throw new Error("Not implemented");
}
}
function _encrypt(func, key, plainText, nonceLength) {
var nonce = (0, utils_2.randomBytes)(nonceLength);
var cipher = func(key, nonce);
var ciphered = cipher.encrypt(plainText); // TAG + encrypted
var encrypted = ciphered.subarray(0, ciphered.length - consts_1.AEAD_TAG_LENGTH);
var tag = ciphered.subarray(-consts_1.AEAD_TAG_LENGTH);
return (0, utils_1.concatBytes)(nonce, tag, encrypted);
}
function _decrypt(func, key, cipherText, nonceLength) {
var nonceTagLength = nonceLength + consts_1.AEAD_TAG_LENGTH;
var nonce = cipherText.subarray(0, nonceLength);
var tag = cipherText.subarray(nonceLength, nonceTagLength);
var encrypted = cipherText.subarray(nonceTagLength);
var decipher = func(key, Uint8Array.from(nonce)); // to reset byteOffset
var ciphered = (0, utils_1.concatBytes)(encrypted, tag);
return decipher.decrypt(ciphered);
}
@@ -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.
@@ -0,0 +1,471 @@
# 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: SIV (Nonce Misuse-Resistant encryption), simple GCM/CTR/CBC webcrypto wrapper
- 💃 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
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
// import { xchacha20poly1305 } from 'npm:@noble/ciphers@0.2.0/chacha'; // Deno
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 = xchacha20poly1305(key, nonce);
const ciphertext = stream_x.encrypt(data);
const plaintext = stream_x.decrypt(ciphertext);
```
- [Modules](#modules)
- [Salsa](#salsa)
- [ChaCha](#chacha)
- [Poly1305](#poly1305)
- [AES](#aes)
- [FF1](#ff1)
- [Guidance](#guidance)
- [How to encrypt properly](#how-to-encrypt-properly)
- [Nonces](#nonces)
- [Encryption limits](#encryption-limits)
- [AES internals and block modes](#aes-internals-and-block-modes)
- [Security](#security)
- [Speed](#speed)
- [Contributing & testing](#contributing--testing)
- [Resources](#resources)
- [Projects using ciphers](#projects-using-ciphers)
- [License](#license)
## Modules
### 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/salsa';
const box = secretbox(key, nonce);
const ciphertext = box.seal(plaintext);
const plaintext = box.open(ciphertext);
// secretbox does not manage nonces for you
// 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)) 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))
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 } from '@noble/ciphers/webcrypto/aes';
import { 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 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.
Check out [AES internals and block modes](#aes-internals-and-block-modes).
### FF1
Format-preserving encryption algorithm (FPE-FF1) specified in NIST Special Publication 800-38G.
[See more info](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38G.pdf).
## Guidance
### 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)
- 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)
- HMAC+ChaCha / HMAC+AES / chacha20poly1305 / aes-gcm is good
- chacha20 without poly1305 or hmac / aes-ctr / aes-cbc is bad
- Flipping bits or 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
### 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`
##### AES internals and block modes
`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.
As for 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!
## Security
The library has not been independently audited yet.
It is tested against property-based, cross-library and Wycheproof vectors,
and has fuzzing by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz).
### Constant-timeness
_JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to
achieve [timing attack](https://en.wikipedia.org/wiki/Timing_attack) resistance
in a scripting language. Which means _any other JS library can't have
constant-timeness_. Even statically typed Rust, a language without GC,
[makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security)
for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones.
Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.
### Supply chain security
1. **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures.
2. **Releases** are transparent and built on GitHub CI. Make sure to verify [provenance](https://docs.npmjs.com/generating-provenance-statements) logs
3. **Rare releasing** is followed.
The less often it is done, the less code dependents would need to audit
4. **Dependencies** are minimal:
- All deps are prevented from automatic updates and have locked-down version ranges. Every update is checked with `npm-diff`
- Updates themselves are rare, to ensure rogue updates are not catched accidentally
5. devDependencies are only used if you want to contribute to the repo. They are disabled for end-users:
- scure-base, micro-bmark and micro-should are developed by the same author and follow identical security practices
- prettier (linter), fast-check (property-based testing) and typescript are used for code quality, vector generation and ts compilation. The packages are big, which makes it hard to audit their source code thoroughly and fully
We consider infrastructure attacks like rogue NPM modules very important;
that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings.
If your app uses 500 dependencies, any dep could get hacked and you'll be
downloading malware with every install. Our goal is to minimize this attack vector.
If you see anything unusual: investigate and report.
### Randomness
We're deferring to built-in
[crypto.getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues)
which is considered cryptographically secure (CSPRNG).
In the past, browsers had bugs that made it weak: it may happen again.
## 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
## Resources
- [Fast-key-erasure random-number generators](https://blog.cr.yp.to/20170723-random.html)
- [The design of Chacha20](https://loup-vaillant.fr/tutorials/chacha20-design)
- [The design of Poly1305](https://loup-vaillant.fr/tutorials/poly1305-design)
- Multi-user / multi-key attacks
- [Break a dozen secret keys, get a million more for free](https://blog.cr.yp.to/20151120-batchattacks.html)
- [128 Bits of Security and 128 Bits of Security: Know the Difference](https://loup-vaillant.fr/tutorials/128-bits-of-security)
### Projects using ciphers
- [js-libp2p-noise](https://github.com/ChainSafe/js-libp2p-noise)
- See [full list of projects on GitHub](https://github.com/paulmillr/noble-curves/network/dependents).
## 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.
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
@@ -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"}
@@ -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
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
export declare function polyval(h: Uint8Array, data: Uint8Array): Uint8Array;
//# sourceMappingURL=_polyval.d.ts.map
@@ -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"}
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
@@ -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,UAuFJ,CAAC"}
@@ -0,0 +1,169 @@
"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) {
if (isAligned32(key))
k = key;
else {
// Align key to 4 bytes
k = key.slice();
toClean.push(k);
}
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}`);
// Align nonce to 4 bytes
if (!isAligned32(nonce)) {
nonce = nonce.slice();
toClean.push(nonce);
}
// 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
@@ -0,0 +1 @@
{"version":3,"file":"_salsa.js","sourceRoot":"","sources":["src/_salsa.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
import { CipherWithOutput } 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) => CipherWithOutput;
/**
* 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) => CipherWithOutput;
/**
* 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) => CipherWithOutput;
//# sourceMappingURL=chacha.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"chacha.d.ts","sourceRoot":"","sources":["src/chacha.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAMjB,MAAM,YAAY,CAAC;AAgFpB;;;;;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,gBAqCvD,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QA3CrB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,gBA2Cc,CAAC;AACzE;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,QAjDtB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,gBAiDgB,CAAC"}
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
@@ -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"}
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,165 @@
// 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) {
if (isAligned32(key))
k = key;
else {
// Align key to 4 bytes
k = key.slice();
toClean.push(k);
}
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}`);
// Align nonce to 4 bytes
if (!isAligned32(nonce)) {
nonce = nonce.slice();
toClean.push(nonce);
}
// 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
@@ -0,0 +1 @@
{"version":3,"file":"_salsa.js","sourceRoot":"","sources":["../src/_salsa.ts"],"names":[],"mappings":"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"}
@@ -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
@@ -0,0 +1,3 @@
"use strict";
throw new Error('noble-ciphers have no entry-point: consult README for usage');
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC"}
@@ -0,0 +1,9 @@
{
"type": "module",
"browser": {
"node:crypto": false
},
"node": {
"./crypto": "./esm/cryptoNode.js"
}
}
@@ -0,0 +1,216 @@
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);
},
};
};
/**
* 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 };
}
//# sourceMappingURL=salsa.js.map
File diff suppressed because one or more lines are too long
@@ -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
File diff suppressed because one or more lines are too long
@@ -0,0 +1,49 @@
import { ensureBytes } from '../utils.js';
import { getWebcryptoSubtle } from './utils.js';
function getCryptParams(algo, nonce, AAD) {
const params = { name: algo };
if (algo === 'AES-CTR') {
return { ...params, counter: nonce, length: 64 };
}
else if (algo === 'AES-GCM') {
return { ...params, iv: nonce, additionalData: AAD };
}
else if (algo === 'AES-CBC') {
return { ...params, iv: nonce };
}
else {
throw new Error('unknown aes cipher');
}
}
function generate(algo, length) {
const keyLength = length / 8;
const keyParams = { name: algo, length };
return (key, nonce, AAD) => {
ensureBytes(key, keyLength);
const cryptParams = getCryptParams(algo, nonce, AAD);
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
@@ -0,0 +1 @@
{"version":3,"file":"aes.js","sourceRoot":"","sources":["../../src/webcrypto/aes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
export const crypto = typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
//# sourceMappingURL=crypto.js.map
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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
@@ -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
@@ -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"}
@@ -0,0 +1 @@
//# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
"use strict";
throw new Error('noble-ciphers have no entry-point: consult README for usage');
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":";AAAA,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC"}
@@ -0,0 +1,131 @@
{
"name": "@noble/ciphers",
"version": "0.3.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"
},
"./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/"
}
@@ -0,0 +1,32 @@
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;
/**
* Alias to xsalsa20poly1305, for compatibility with libsodium / nacl
*/
export declare function secretbox(key: Uint8Array, nonce: Uint8Array): {
seal: (plaintext: Uint8Array) => Uint8Array;
open: (ciphertext: Uint8Array) => Uint8Array;
};
//# sourceMappingURL=salsa.d.ts.map
@@ -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,CACpB,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,GAClE,UAAU,CAoCZ;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;AAEF;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;;;EAK3D"}
@@ -0,0 +1,222 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.secretbox = 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;
/**
* 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, exports.xsalsa20poly1305)(key, nonce);
return { seal: xs.encrypt, open: xs.decrypt };
}
exports.secretbox = secretbox;
//# sourceMappingURL=salsa.js.map
File diff suppressed because one or more lines are too long
@@ -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;
@@ -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);
@@ -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));
@@ -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);
}
@@ -0,0 +1,185 @@
// 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) {
if (isAligned32(key)) k = key;
else {
// Align key to 4 bytes
k = key.slice();
toClean.push(k);
}
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}`);
// Align nonce to 4 bytes
if (!isAligned32(nonce)) {
nonce = nonce.slice();
toClean.push(nonce);
}
// 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;
};
};
@@ -0,0 +1,297 @@
import {
CipherWithOutput,
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): CipherWithOutput => {
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);
@@ -0,0 +1 @@
throw new Error('noble-ciphers have no entry-point: consult README for usage');
@@ -0,0 +1,188 @@
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);
},
};
};
/**
* 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 };
}
@@ -0,0 +1,200 @@
/*! 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>;
};
export type CipherWithOutput = Cipher & {
encrypt(plaintext: Uint8Array, output?: Uint8Array): Uint8Array;
decrypt(ciphertext: Uint8Array, output?: Uint8Array): 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);
}
@@ -0,0 +1,74 @@
import { ensureBytes } from '../utils.js';
import { getWebcryptoSubtle } from './utils.js';
/**
* AAD is only effective on AES-256-GCM or AES-128-GCM. Otherwise it'll be ignored
*/
export type Cipher = (
key: Uint8Array,
nonce: Uint8Array,
AAD?: Uint8Array
) => {
keyLength: number;
encrypt(plaintext: Uint8Array): Promise<Uint8Array>;
decrypt(ciphertext: Uint8Array): Promise<Uint8Array>;
};
type Algo = 'AES-CTR' | 'AES-GCM' | 'AES-CBC';
type BitLength = 128 | 256;
function getCryptParams(
algo: Algo,
nonce: Uint8Array,
AAD?: Uint8Array
): AesCbcParams | AesCtrParams | AesGcmParams {
const params = { name: algo };
if (algo === 'AES-CTR') {
return { ...params, counter: nonce, length: 64 } as AesCtrParams;
} else if (algo === 'AES-GCM') {
return { ...params, iv: nonce, additionalData: AAD } as AesGcmParams;
} else if (algo === 'AES-CBC') {
return { ...params, iv: nonce } as AesCbcParams;
} else {
throw new Error('unknown aes cipher');
}
}
function generate(algo: Algo, length: BitLength): Cipher {
const keyLength = length / 8;
const keyParams = { name: algo, length };
return (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => {
ensureBytes(key, keyLength);
const cryptParams = getCryptParams(algo, nonce, AAD);
return {
keyLength,
async encrypt(plaintext: 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) {
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);

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