← back

node:crypto is underused

4 min read ·

In this post, I’ll show you* the basics of node:crypto, the builtin Node.js cryptography module. I find it pretty cool, but not well known.

I’m not saying everybody needs to be a security expert. I’m def not one! But I feel that many engineers I know are needlessly scared of cryptography. With just a tiny sprinkle of it, we can do amazing things: like keeping our users’ data private with encryption or signing stuff we give out to the public to guarantee it hasn’t been tampered with. It’s been a while since I’ve written a tutorial-style blog post, so here it goes.

You can find all code snippets from this on CodeSandbox.

First things first, we need to generate a key pair.

import { function promisify<TCustom extends Function>(fn: CustomPromisify<TCustom>): TCustom (+13 overloads)
Takes a function following the common error-first callback style, i.e. taking an `(err, value) => ...` callback as the last argument, and returns a version that returns promises. ```js import { promisify } from 'node:util'; import { stat } from 'node:fs'; const promisifiedStat = promisify(stat); promisifiedStat('.').then((stats) => { // Do something with `stats` }).catch((error) => { // Handle the error. }); ``` Or, equivalently using `async function`s: ```js import { promisify } from 'node:util'; import { stat } from 'node:fs'; const promisifiedStat = promisify(stat); async function callStat() { const stats = await promisifiedStat('.'); console.log(`This directory is owned by ${stats.uid}`); } callStat(); ``` If there is an `original[util.promisify.custom]` property present, `promisify` will return its value, see [Custom promisified functions](https://nodejs.org/docs/latest-v24.x/api/util.html#custom-promisified-functions). `promisify()` assumes that `original` is a function taking a callback as its final argument in all cases. If `original` is not a function, `promisify()` will throw an error. If `original` is a function but its last argument is not an error-first callback, it will still be passed an error-first callback as its last argument. Using `promisify()` on class methods or other methods that use `this` may not work as expected unless handled specially: ```js import { promisify } from 'node:util'; class Foo { constructor() { this.a = 42; } bar(callback) { callback(null, this.a); } } const foo = new Foo(); const naiveBar = promisify(foo.bar); // TypeError: Cannot read properties of undefined (reading 'a') // naiveBar().then(a => console.log(a)); naiveBar.call(foo).then((a) => console.log(a)); // '42' const bindBar = naiveBar.bind(foo); bindBar().then((a) => console.log(a)); // '42' ```
@sincev8.0.0
promisify
} from "node:util";
import { function generateKeyPair(type: "rsa", options: RSAKeyPairOptions<"pem", "pem">, callback: (err: Error | null, publicKey: string, privateKey: string) => void): void (+39 overloads)
Generates a new asymmetric key pair of the given `type`. RSA, RSA-PSS, DSA, EC, Ed25519, Ed448, X25519, X448, and DH are currently supported. If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function behaves as if `keyObject.export()` had been called on its result. Otherwise, the respective part of the key is returned as a `KeyObject`. It is recommended to encode public keys as `'spki'` and private keys as `'pkcs8'` with encryption for long-term storage: ```js const { generateKeyPair, } = await import('node:crypto'); generateKeyPair('rsa', { modulusLength: 4096, publicKeyEncoding: { type: 'spki', format: 'pem', }, privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: 'top secret', }, }, (err, publicKey, privateKey) => { // Handle errors and use the generated key pair. }); ``` On completion, `callback` will be called with `err` set to `undefined` and `publicKey` / `privateKey` representing the generated key pair. If this method is invoked as its `util.promisify()` ed version, it returns a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
@sincev10.12.0@paramtype Must be `'rsa'`, `'rsa-pss'`, `'dsa'`, `'ec'`, `'ed25519'`, `'ed448'`, `'x25519'`, `'x448'`, or `'dh'`.
generateKeyPair
} from "node:crypto";
const { const privateKey: KeyObjectprivateKey, const publicKey: KeyObjectpublicKey } = await
promisify<{
    (type: "rsa", options: RSAKeyPairOptions<"pem", "pem">): Promise<{
        publicKey: string;
        privateKey: string;
    }>;
    (type: "rsa", options: RSAKeyPairOptions<"pem", "der">): Promise<...>;
    (type: "rsa", options: RSAKeyPairOptions<...>): Promise<...>;
    (type: "rsa", options: RSAKeyPairOptions<...>): Promise<...>;
    (type: "rsa", options: RSAKeyPairKeyObjectOptions): Promise<...>;
    (type: "rsa-pss", options: RSAPSSKeyPairOptions<...>): Promise<...>;
    (type: "rsa-pss", options: RSAPSSKeyPairOptions<...>): Promise<...>;
    (type: "rsa-pss", options: RSAPSSKeyPairOptions<...>): Promise<...>;
    (type: "rsa-pss", options: RSAPSSKeyPairOptions<...>): Promise<...>;
    (type: "rsa-pss", options: RSAPSSKeyPairKeyObjectOptions): Promise<...>;
    (type: "dsa", options: DSAKeyPairOptions<...>): Promise<...>;
    (type: "dsa", options: DSAKeyPairOptions<...>): Promise<...>;
    (type: "dsa", options: DSAKeyPairOptions<...>): Promise<...>;
    (type: "dsa", options: DSAKeyPairOptions<...>): Promise<...>;
    (type: "dsa", options: DSAKeyPairKeyObjectOptions): Promise<...>;
    (type: "ec", options: ECKeyPairOptions<...>): Promise<...>;
    (type: "ec", options: ECKeyPairOptions<...>): Promise<...>;
    (type: "ec", options: ECKeyPairOptions<...>): Promise<...>;
    (type: "ec", options: ECKeyPairOptions<...>): Promise<...>;
    (type: "ec", options: ECKeyPairKeyObjectOptions): Promise<...>;
    (type: "ed25519", options: ED25519KeyPairOptions<...>): Promise<...>;
    (type: "ed25519", options: ED25519KeyPairOptions<...>): Promise<...>;
    (type: "ed25519" ... (+13 overloads)
Takes a function following the common error-first callback style, i.e. taking an `(err, value) => ...` callback as the last argument, and returns a version that returns promises. ```js import { promisify } from 'node:util'; import { stat } from 'node:fs'; const promisifiedStat = promisify(stat); promisifiedStat('.').then((stats) => { // Do something with `stats` }).catch((error) => { // Handle the error. }); ``` Or, equivalently using `async function`s: ```js import { promisify } from 'node:util'; import { stat } from 'node:fs'; const promisifiedStat = promisify(stat); async function callStat() { const stats = await promisifiedStat('.'); console.log(`This directory is owned by ${stats.uid}`); } callStat(); ``` If there is an `original[util.promisify.custom]` property present, `promisify` will return its value, see [Custom promisified functions](https://nodejs.org/docs/latest-v24.x/api/util.html#custom-promisified-functions). `promisify()` assumes that `original` is a function taking a callback as its final argument in all cases. If `original` is not a function, `promisify()` will throw an error. If `original` is a function but its last argument is not an error-first callback, it will still be passed an error-first callback as its last argument. Using `promisify()` on class methods or other methods that use `this` may not work as expected unless handled specially: ```js import { promisify } from 'node:util'; class Foo { constructor() { this.a = 42; } bar(callback) { callback(null, this.a); } } const foo = new Foo(); const naiveBar = promisify(foo.bar); // TypeError: Cannot read properties of undefined (reading 'a') // naiveBar().then(a => console.log(a)); naiveBar.call(foo).then((a) => console.log(a)); // '42' const bindBar = naiveBar.bind(foo); bindBar().then((a) => console.log(a)); // '42' ```
@sincev8.0.0
promisify
(function generateKeyPair(type: "rsa", options: RSAKeyPairOptions<"pem", "pem">, callback: (err: Error | null, publicKey: string, privateKey: string) => void): void (+39 overloads)
Generates a new asymmetric key pair of the given `type`. RSA, RSA-PSS, DSA, EC, Ed25519, Ed448, X25519, X448, and DH are currently supported. If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function behaves as if `keyObject.export()` had been called on its result. Otherwise, the respective part of the key is returned as a `KeyObject`. It is recommended to encode public keys as `'spki'` and private keys as `'pkcs8'` with encryption for long-term storage: ```js const { generateKeyPair, } = await import('node:crypto'); generateKeyPair('rsa', { modulusLength: 4096, publicKeyEncoding: { type: 'spki', format: 'pem', }, privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: 'top secret', }, }, (err, publicKey, privateKey) => { // Handle errors and use the generated key pair. }); ``` On completion, `callback` will be called with `err` set to `undefined` and `publicKey` / `privateKey` representing the generated key pair. If this method is invoked as its `util.promisify()` ed version, it returns a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
@sincev10.12.0@paramtype Must be `'rsa'`, `'rsa-pss'`, `'dsa'`, `'ec'`, `'ed25519'`, `'ed448'`, `'x25519'`, `'x448'`, or `'dh'`.
generateKeyPair
)("rsa", {
RSAKeyPairKeyObjectOptions.modulusLength: number
Key size in bits
modulusLength
: 2048,
});

Easy, right? We’ve got ourselves an RSA public and private key.

Rivest–Shamir–Adleman public-key cryptosystem was discovered in the late 70s, just like hip-hop. You need to now pause reading this, play the “Rapper’s Delight” by The Sugarhill Gang, while reading about RSA Security’s relationship with NSA.

Now back to JavaScript.

Let’s write and encode a short message, so we have something to work with.

const const originalString: "my important message"originalString = "my important message";
const const originalUint8Array: Uint8ArrayoriginalUint8Array = new 
var Uint8Array: Uint8ArrayConstructor
new (elements: Iterable<number>) => Uint8Array (+4 overloads)
Uint8Array
(
const originalString: "my important message"originalString.String.split(separator: string | RegExp, limit?: number | undefined): string[] (+1 overload)
Split a string into substrings using the specified separator and return them as an array.
@paramseparator A string that identifies character or characters to use in separating the string. If omitted, a single-element array containing the entire string is returned.@paramlimit A value used to limit the number of elements returned in the array.
split
("").Array<string>.map<number>(callbackfn: (value: string, index: number, array: string[]) => number, thisArg?: any): number[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((ch: stringch) => ch: stringch.String.charCodeAt(index: number): number
Returns the Unicode value of the character at the specified location.
@paramindex The zero-based index of the desired character. If there is no character at the specified index, NaN is returned.
charCodeAt
(0)),
);

Now anybody can encrypt it with our public key, but only we can decrypt it.

import { function publicEncrypt(key: RsaPublicKey | RsaPrivateKey | KeyLike, buffer: NodeJS.ArrayBufferView | string): Buffer
Encrypts the content of `buffer` with `key` and returns a new `Buffer` with encrypted content. The returned data can be decrypted using the corresponding private key, for example using {@link privateDecrypt } . If `key` is not a `KeyObject`, this function behaves as if `key` had been passed to {@link createPublicKey } . If it is an object, the `padding` property can be passed. Otherwise, this function uses `RSA_PKCS1_OAEP_PADDING`. Because RSA public keys can be derived from private keys, a private key may be passed instead of a public key.
@sincev0.11.14
publicEncrypt
, function privateDecrypt(privateKey: RsaPrivateKey | KeyLike, buffer: NodeJS.ArrayBufferView | string): Buffer
Decrypts `buffer` with `privateKey`. `buffer` was previously encrypted using the corresponding public key, for example using {@link publicEncrypt } . If `privateKey` is not a `KeyObject`, this function behaves as if `privateKey` had been passed to {@link createPrivateKey } . If it is an object, the `padding` property can be passed. Otherwise, this function uses `RSA_PKCS1_OAEP_PADDING`.
@sincev0.11.14
privateDecrypt
} from "node:crypto";
const const encryptedBuffer: BufferencryptedBuffer = function publicEncrypt(key: RsaPublicKey | RsaPrivateKey | KeyLike, buffer: string | NodeJS.ArrayBufferView): Buffer
Encrypts the content of `buffer` with `key` and returns a new `Buffer` with encrypted content. The returned data can be decrypted using the corresponding private key, for example using {@link privateDecrypt } . If `key` is not a `KeyObject`, this function behaves as if `key` had been passed to {@link createPublicKey } . If it is an object, the `padding` property can be passed. Otherwise, this function uses `RSA_PKCS1_OAEP_PADDING`. Because RSA public keys can be derived from private keys, a private key may be passed instead of a public key.
@sincev0.11.14
publicEncrypt
(const publicKey: KeyObjectpublicKey, const originalUint8Array: Uint8ArrayoriginalUint8Array);
const const decryptedBuffer: BufferdecryptedBuffer = function privateDecrypt(privateKey: RsaPrivateKey | KeyLike, buffer: string | NodeJS.ArrayBufferView): Buffer
Decrypts `buffer` with `privateKey`. `buffer` was previously encrypted using the corresponding public key, for example using {@link publicEncrypt } . If `privateKey` is not a `KeyObject`, this function behaves as if `privateKey` had been passed to {@link createPrivateKey } . If it is an object, the `padding` property can be passed. Otherwise, this function uses `RSA_PKCS1_OAEP_PADDING`.
@sincev0.11.14
privateDecrypt
(const privateKey: KeyObjectprivateKey, const encryptedBuffer: BufferencryptedBuffer);
const const decryptedString: stringdecryptedString = const decryptedBuffer: BufferdecryptedBuffer.Buffer.toString(encoding?: BufferEncoding | undefined, start?: number | undefined, end?: number | undefined): string
Decodes `buf` to a string according to the specified character encoding in`encoding`. `start` and `end` may be passed to decode only a subset of `buf`. If `encoding` is `'utf8'` and a byte sequence in the input is not valid UTF-8, then each invalid byte is replaced with the replacement character `U+FFFD`. The maximum length of a string instance (in UTF-16 code units) is available as {@link constants.MAX_STRING_LENGTH } . ```js import { Buffer } from 'node:buffer'; const buf1 = Buffer.allocUnsafe(26); for (let i = 0; i < 26; i++) { // 97 is the decimal ASCII value for 'a'. buf1[i] = i + 97; } console.log(buf1.toString('utf8')); // Prints: abcdefghijklmnopqrstuvwxyz console.log(buf1.toString('utf8', 0, 5)); // Prints: abcde const buf2 = Buffer.from('tést'); console.log(buf2.toString('hex')); // Prints: 74c3a97374 console.log(buf2.toString('utf8', 0, 3)); // Prints: té console.log(buf2.toString(undefined, 0, 3)); // Prints: té ```
@sincev0.1.90@paramencoding The character encoding to use.@paramstart The byte offset to start decoding at.@paramend The byte offset to stop decoding at (not inclusive).
toString
();

The functions for signing and verifying have a bit of a peculiar API, where we can pass null or undefined as the first argument if we already have our key in a KeyObject instead of a raw string. I’m pretty used to JSON.stringify(object, null, 2), so I think I actually prefer it over repeating the algorithm name.

import { function sign(algorithm: string | null | undefined, data: NodeJS.ArrayBufferView, key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput | SignJsonWebKeyInput): Buffer (+1 overload)
Calculates and returns the signature for `data` using the given private key and algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is dependent upon the key type (especially Ed25519 and Ed448). If `key` is not a `KeyObject`, this function behaves as if `key` had been passed to {@link createPrivateKey } . If it is an object, the following additional properties can be passed: If the `callback` function is provided this function uses libuv's threadpool.
@sincev12.0.0
sign
, function verify(algorithm: string | null | undefined, data: NodeJS.ArrayBufferView, key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput, signature: NodeJS.ArrayBufferView): boolean (+1 overload)
Verifies the given signature for `data` using the given key and algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is dependent upon the key type (especially Ed25519 and Ed448). If `key` is not a `KeyObject`, this function behaves as if `key` had been passed to {@link createPublicKey } . If it is an object, the following additional properties can be passed: The `signature` argument is the previously calculated signature for the `data`. Because public keys can be derived from private keys, a private key or a public key may be passed for `key`. If the `callback` function is provided this function uses libuv's threadpool.
@sincev12.0.0
verify
} from "node:crypto";
const const signature: Buffersignature = function sign(algorithm: string | null | undefined, data: NodeJS.ArrayBufferView, key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput | SignJsonWebKeyInput): Buffer (+1 overload)
Calculates and returns the signature for `data` using the given private key and algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is dependent upon the key type (especially Ed25519 and Ed448). If `key` is not a `KeyObject`, this function behaves as if `key` had been passed to {@link createPrivateKey } . If it is an object, the following additional properties can be passed: If the `callback` function is provided this function uses libuv's threadpool.
@sincev12.0.0
sign
(null, const originalUint8Array: Uint8ArrayoriginalUint8Array, const privateKey: KeyObjectprivateKey);
const const verified: booleanverified = function verify(algorithm: string | null | undefined, data: NodeJS.ArrayBufferView, key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput, signature: NodeJS.ArrayBufferView): boolean (+1 overload)
Verifies the given signature for `data` using the given key and algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is dependent upon the key type (especially Ed25519 and Ed448). If `key` is not a `KeyObject`, this function behaves as if `key` had been passed to {@link createPublicKey } . If it is an object, the following additional properties can be passed: The `signature` argument is the previously calculated signature for the `data`. Because public keys can be derived from private keys, a private key or a public key may be passed for `key`. If the `callback` function is provided this function uses libuv's threadpool.
@sincev12.0.0
verify
(null, const originalUint8Array: Uint8ArrayoriginalUint8Array, const publicKey: KeyObjectpublicKey, const signature: Buffersignature);

If you’re just interested in signing, you should probably use Ed25519. It’s faster than RSA, twisted Edwards curves sound like a band name, and GitHub recommends it for your SSH keys.

const const signingKeys: KeyPairKeyObjectResultsigningKeys = await 
promisify<{
    (type: "rsa", options: RSAKeyPairOptions<"pem", "pem">): Promise<{
        publicKey: string;
        privateKey: string;
    }>;
    (type: "rsa", options: RSAKeyPairOptions<"pem", "der">): Promise<...>;
    (type: "rsa", options: RSAKeyPairOptions<...>): Promise<...>;
    (type: "rsa", options: RSAKeyPairOptions<...>): Promise<...>;
    (type: "rsa", options: RSAKeyPairKeyObjectOptions): Promise<...>;
    (type: "rsa-pss", options: RSAPSSKeyPairOptions<...>): Promise<...>;
    (type: "rsa-pss", options: RSAPSSKeyPairOptions<...>): Promise<...>;
    (type: "rsa-pss", options: RSAPSSKeyPairOptions<...>): Promise<...>;
    (type: "rsa-pss", options: RSAPSSKeyPairOptions<...>): Promise<...>;
    (type: "rsa-pss", options: RSAPSSKeyPairKeyObjectOptions): Promise<...>;
    (type: "dsa", options: DSAKeyPairOptions<...>): Promise<...>;
    (type: "dsa", options: DSAKeyPairOptions<...>): Promise<...>;
    (type: "dsa", options: DSAKeyPairOptions<...>): Promise<...>;
    (type: "dsa", options: DSAKeyPairOptions<...>): Promise<...>;
    (type: "dsa", options: DSAKeyPairKeyObjectOptions): Promise<...>;
    (type: "ec", options: ECKeyPairOptions<...>): Promise<...>;
    (type: "ec", options: ECKeyPairOptions<...>): Promise<...>;
    (type: "ec", options: ECKeyPairOptions<...>): Promise<...>;
    (type: "ec", options: ECKeyPairOptions<...>): Promise<...>;
    (type: "ec", options: ECKeyPairKeyObjectOptions): Promise<...>;
    (type: "ed25519", options: ED25519KeyPairOptions<...>): Promise<...>;
    (type: "ed25519", options: ED25519KeyPairOptions<...>): Promise<...>;
    (type: "ed25519" ... (+13 overloads)
Takes a function following the common error-first callback style, i.e. taking an `(err, value) => ...` callback as the last argument, and returns a version that returns promises. ```js import { promisify } from 'node:util'; import { stat } from 'node:fs'; const promisifiedStat = promisify(stat); promisifiedStat('.').then((stats) => { // Do something with `stats` }).catch((error) => { // Handle the error. }); ``` Or, equivalently using `async function`s: ```js import { promisify } from 'node:util'; import { stat } from 'node:fs'; const promisifiedStat = promisify(stat); async function callStat() { const stats = await promisifiedStat('.'); console.log(`This directory is owned by ${stats.uid}`); } callStat(); ``` If there is an `original[util.promisify.custom]` property present, `promisify` will return its value, see [Custom promisified functions](https://nodejs.org/docs/latest-v24.x/api/util.html#custom-promisified-functions). `promisify()` assumes that `original` is a function taking a callback as its final argument in all cases. If `original` is not a function, `promisify()` will throw an error. If `original` is a function but its last argument is not an error-first callback, it will still be passed an error-first callback as its last argument. Using `promisify()` on class methods or other methods that use `this` may not work as expected unless handled specially: ```js import { promisify } from 'node:util'; class Foo { constructor() { this.a = 42; } bar(callback) { callback(null, this.a); } } const foo = new Foo(); const naiveBar = promisify(foo.bar); // TypeError: Cannot read properties of undefined (reading 'a') // naiveBar().then(a => console.log(a)); naiveBar.call(foo).then((a) => console.log(a)); // '42' const bindBar = naiveBar.bind(foo); bindBar().then((a) => console.log(a)); // '42' ```
@sincev8.0.0
promisify
(function generateKeyPair(type: "rsa", options: RSAKeyPairOptions<"pem", "pem">, callback: (err: Error | null, publicKey: string, privateKey: string) => void): void (+39 overloads)
Generates a new asymmetric key pair of the given `type`. RSA, RSA-PSS, DSA, EC, Ed25519, Ed448, X25519, X448, and DH are currently supported. If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function behaves as if `keyObject.export()` had been called on its result. Otherwise, the respective part of the key is returned as a `KeyObject`. It is recommended to encode public keys as `'spki'` and private keys as `'pkcs8'` with encryption for long-term storage: ```js const { generateKeyPair, } = await import('node:crypto'); generateKeyPair('rsa', { modulusLength: 4096, publicKeyEncoding: { type: 'spki', format: 'pem', }, privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: 'top secret', }, }, (err, publicKey, privateKey) => { // Handle errors and use the generated key pair. }); ``` On completion, `callback` will be called with `err` set to `undefined` and `publicKey` / `privateKey` representing the generated key pair. If this method is invoked as its `util.promisify()` ed version, it returns a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
@sincev10.12.0@paramtype Must be `'rsa'`, `'rsa-pss'`, `'dsa'`, `'ec'`, `'ed25519'`, `'ed448'`, `'x25519'`, `'x448'`, or `'dh'`.
generateKeyPair
)("ed25519");

Both RSA and Ed25519 can be used to sign and verify messages, but Ed25519 is just a digital signature scheme, so you can’t use it for encryption.

If you change rsa to ed25519 in the sandbox, one of the tests will fail with a friendly message:

Error: error:03000096:digital envelope routines::operation not supported for
this keytype

A bit annoying that it doesn’t tell us about it on the type level, but without redesigning the API with types in mind, the type error would also be a bit nasty.

I remember thinking “Why shouldn’t I just use RSA for everything and limit the number of keys flying around?” sometime in the past. Apart from the performance, and some possible security reasons, there’s apparently some legislation stating that digital signatures are legally binding. I probably wouldn’t interpret hashing a message and then raising the hash to the power of a number that’s kept in secret as a signature, but I am not a lawyer, and I am unfortunately doomed to focus on the implementation.

Further Reading