- 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
259 lines
9.2 KiB
Markdown
259 lines
9.2 KiB
Markdown
# ed25519-keygen
|
|
|
|
Generate ed25519 keys for SSH, PGP (GPG), TOR, IPNS and SLIP-0010 hdkey.
|
|
|
|
- Pure JS, no CLI tools are involved
|
|
- Can generate both deterministic and random keys
|
|
- Uses [noble-curves](https://github.com/paulmillr/noble-curves) under the hood
|
|
|
|
Includes SLIP-0010 (ed BIP32) HDKey implementation, funded by the Kin Foundation for
|
|
[Kinetic](https://github.com/kin-labs/kinetic). For the apps made with the library, check out:
|
|
[terminal7 WebRTC terminal multiplexer](https://github.com/tuzig/terminal7)
|
|
|
|
## Usage
|
|
|
|
> npm install ed25519-keygen
|
|
|
|
The package exports six modules:
|
|
|
|
- [`ed25519-keygen/ssh`](#sshseed-username) for SSH key generation
|
|
- [`ed25519-keygen/pgp`](#pgpseed-user-password) for
|
|
[RFC 4880](https://datatracker.ietf.org/doc/html/rfc4880) +
|
|
[RFC 6637](https://datatracker.ietf.org/doc/html/rfc6637)
|
|
- [`ed25519-keygen/tor`](#torseed) for TOR onion addresses
|
|
- [`ed25519-keygen/ipns`](#ipnsseed) for IPNS addresses
|
|
- [`ed25519-keygen/hdkey`](#hdkey) for
|
|
[SLIP-0010](https://github.com/satoshilabs/slips/blob/master/slip-0010.md)/[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
|
|
HDKey
|
|
- [`ed25519-keygen/utils`](#randombyteslength) for cryptographically secure random number generator
|
|
(CSPRNG)
|
|
|
|
Use it in the following way:
|
|
|
|
```ts
|
|
import ssh from 'ed25519-keygen/ssh';
|
|
import pgp from 'ed25519-keygen/pgp';
|
|
import tor from 'ed25519-keygen/tor';
|
|
import { randomBytes } from 'ed25519-keygen/utils';
|
|
```
|
|
|
|
## `ssh(seed, username)`
|
|
|
|
- `seed: Uint8Array`
|
|
- `username: string`
|
|
- Returns
|
|
`{ fingerprint: string, privateKey: string, publicKey: string, publicKeyBytes: Uint8Array }`
|
|
|
|
```js
|
|
import ssh from 'ed25519-keygen/ssh';
|
|
import { randomBytes } from 'ed25519-keygen/utils';
|
|
const sseed = randomBytes(32);
|
|
const skeys = await ssh(sseed, 'user@example.com');
|
|
console.log(skeys.fingerprint);
|
|
console.log(skeys.privateKey);
|
|
console.log(skeys.publicKey);
|
|
/*
|
|
SHA256:3M832z6j5R6mQh4TTzVG5KVs2IbvythcS6VPiEixMJg
|
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
|
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
|
QyNTUxOQAAACB7IzMcWzDbGACJFPmt8hDZGedH6W1w0SGuY1Ut+oIlxQAAAJh8wUpUfMFK
|
|
VAAAAAtzc2gtZWQyNTUxOQAAACB7IzMcWzDbGACJFPmt8hDZGedH6W1w0SGuY1Ut+oIlxQ
|
|
AAAEBPTJHsreF9Losr930Yt/8DseFi66G7vK8QF/Kd8fcRlXsjMxxbMNsYAIkU+a3yENkZ
|
|
50fpbXDRIa5jVS36giXFAAAAEHVzZXJAZXhhbXBsZS5jb20BAgMEBQ==
|
|
-----END OPENSSH PRIVATE KEY-----
|
|
|
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHsjMxxbMNsYAIkU+a3yENkZ50fpbXDRIa5jVS36giXF user@example.com
|
|
*/
|
|
```
|
|
|
|
## `pgp(seed, user, password)`
|
|
|
|
- `seed: Uint8Array`
|
|
- `user: string`
|
|
- `password: string`
|
|
- `createdAt: number` - (default: 0) timestamp corresponding to key creation time
|
|
- Returns `{ keyId: string, privateKey: string, publicKey: string, publicKeyBytes: Uint8Array }`
|
|
|
|
Creates keys compatible with GPG. GPG is a commonly known utility that supports PGP protocol.
|
|
Quirks:
|
|
|
|
1. Generated private and public keys would have different representation, however, **their
|
|
fingerprints would be the same**. This is because AES encryption is used to hide the keys, and
|
|
AES requires different IV / salt.
|
|
2. The function is slow (~725ms on Apple M1), because it uses S2K to derive keys.
|
|
3. "warning: lower 3 bits of the secret key are not cleared" happens even for keys generated with
|
|
GnuPG 2.3.6, because check looks at item as Opaque MPI, when it is just MPI: see
|
|
[bugtracker URL](https://dev.gnupg.org/rGdbfb7f809b89cfe05bdacafdb91a2d485b9fe2e0).
|
|
|
|
```js
|
|
import * as pgp from 'ed25519-keygen/pgp';
|
|
import { randomBytes } from 'ed25519-keygen/utils';
|
|
const pseed = randomBytes(32);
|
|
const pkeys = await pgp.getKeys(pseed, 'user@example.com', 'password');
|
|
console.log(pkeys.keyId);
|
|
console.log(pkeys.privateKey);
|
|
console.log(pkeys.publicKey);
|
|
/*
|
|
ca88e2a8afd9cdb8
|
|
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
|
|
|
lIYEAAAAABYJKwYBBAHaRw8BAQdA0TSxOgyxDIuJh0afj457vpf7IZJsnyVu+HG2
|
|
k/v1F0P+BwMCpkzMdFodxiHwgVurmhm72ikz5FqdF8WJEBy0VC8ovbXkNz9oCi31
|
|
grwUafRgb874q0n99Q6Kh1cDMwMNF6vjQvgusaJQtvy75Y0pkNEnKrQQdXNlckBl
|
|
eGFtcGxlLmNvbYiUBBMWCgA8FiEELCHHdWxwPnwIbwzfyojiqK/ZzbgFAgAAAAAC
|
|
GwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4HAheAAAoJEMqI4qiv2c24nyABALvn
|
|
+XR0T4AeohBNL+h88o2tgPazB1GtKo1FhMb8cpaDAQCRr8Ml3Ow5ijijFBQ0aqG4
|
|
1D43SIinNvQFD59o85YfBZyLBAAAAAASCisGAQQBl1UBBQEBB0C8acmhByJtlAZo
|
|
7T2lVQa0iCo0RBm/CgMJKO+3/NaHGgMBCAf+BwMCJsLfz4M3/KrwyBBBRu8MTvjq
|
|
pY5FjFcJGoPRhYHX+/ZATZf4cRrA0LX3zDi1nudO1f755Q4ALWjPXNXMMBkmKjHJ
|
|
p5WaAFm7xMdxEvXYaIh4BBgWCgAgFiEELCHHdWxwPnwIbwzfyojiqK/ZzbgFAgAA
|
|
AAACGwwACgkQyojiqK/ZzbikkgEAod4RMOLsVngAH/WFBWQi+Ee5hZ4nXsfasDsT
|
|
hNEnaqcBAI5h/ss8SU/gOmx/uiGJTCpp8VRAac+VbeU5XU//aLwA
|
|
=oOli
|
|
-----END PGP PRIVATE KEY BLOCK-----
|
|
|
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
|
|
mDMEAAAAABYJKwYBBAHaRw8BAQdA0TSxOgyxDIuJh0afj457vpf7IZJsnyVu+HG2
|
|
k/v1F0O0EHVzZXJAZXhhbXBsZS5jb22IlAQTFgoAPBYhBCwhx3VscD58CG8M38qI
|
|
4qiv2c24BQIAAAAAAhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeBwIXgAAKCRDK
|
|
iOKor9nNuJ8gAQC75/l0dE+AHqIQTS/ofPKNrYD2swdRrSqNRYTG/HKWgwEAka/D
|
|
JdzsOYo4oxQUNGqhuNQ+N0iIpzb0BQ+faPOWHwW4OAQAAAAAEgorBgEEAZdVAQUB
|
|
AQdAvGnJoQcibZQGaO09pVUGtIgqNEQZvwoDCSjvt/zWhxoDAQgHiHgEGBYKACAW
|
|
IQQsIcd1bHA+fAhvDN/KiOKor9nNuAUCAAAAAAIbDAAKCRDKiOKor9nNuKSSAQCh
|
|
3hEw4uxWeAAf9YUFZCL4R7mFnidex9qwOxOE0SdqpwEAjmH+yzxJT+A6bH+6IYlM
|
|
KmnxVEBpz5Vt5TldT/9ovAA=
|
|
=4hZe
|
|
-----END PGP PUBLIC KEY BLOCK-----
|
|
*/
|
|
|
|
// Also, you can explore existing keys internal structure
|
|
console.log(await pgp.pubArmor.decode(keys.publicKey));
|
|
const privDecoded = await pgp.privArmor.decode(keys.privateKey);
|
|
console.log(privDecoded);
|
|
// And receive raw private keys as bigint
|
|
console.log({
|
|
ed25519: await pgp.decodeSecretKey('password', privDecoded[0].data),
|
|
cv25519: await pgp.decodeSecretKey('password', privDecoded[3].data),
|
|
});
|
|
```
|
|
|
|
## `tor(seed)`
|
|
|
|
Generates TOR addresses.
|
|
|
|
- `seed: Uint8Array`
|
|
- Returns `{ privateKey: string, publicKey: string, publicKeyBytes: Uint8Array }`
|
|
|
|
```js
|
|
import tor from 'ed25519-keygen/tor';
|
|
import { randomBytes } from 'ed25519-keygen/utils';
|
|
const tseed = randomBytes(32);
|
|
const tkeys = await tor(tseed);
|
|
console.log(tkeys.privateKey);
|
|
console.log(tkeys.publicKey);
|
|
/*
|
|
ED25519-V3:EOl78M2gARYOyp4BDltfzxSR3dA/LLTXZLb2imgOwFuYC5ISIUxsQ42ywzHaxvc03mahmaLziuyN0+f8EhM+4w==
|
|
rx724x3oambzxr46pkbdckdqyut5x5lhsneru3uditf4nuyuf4uou6qd.onion
|
|
*/
|
|
```
|
|
|
|
## `ipns(seed)`
|
|
|
|
Generates IPNS addresses.
|
|
|
|
- `seed: Uint8Array`
|
|
- Returns
|
|
`{ privateKey: string, publicKey: string, base36: string, base32: string, base16: string, contenthash: string}`
|
|
|
|
```js
|
|
import ipns from 'ed25519-keygen/ipns';
|
|
import { randomBytes } from 'ed25519-keygen/utils';
|
|
const iseed = randomBytes(32);
|
|
const ikeys = await ipns(iseed);
|
|
console.log(ikeys.privateKey);
|
|
console.log(ikeys.publicKey);
|
|
console.log(ikeys.base16);
|
|
console.log(ikeys.base32);
|
|
console.log(ikeys.base36);
|
|
console.log(ikeys.contenthash);
|
|
/*
|
|
0x080112400681d6420abb1ba47acd5c03c8e5ee84185a2673576b262e234e50c46d86f59712c8299ec2c51dffbbcb4f9fccadcee1424cb237e9b30d3cd72d47c18103689d
|
|
0x017200240801122012c8299ec2c51dffbbcb4f9fccadcee1424cb237e9b30d3cd72d47c18103689d
|
|
ipns://f017200240801122012c8299ec2c51dffbbcb4f9fccadcee1424cb237e9b30d3cd72d47c18103689d
|
|
ipns://bafzaajaiaejcaewifgpmfri57654wt47zsw45ykcjszdp2ntbu6nolkhygaqg2e5
|
|
ipns://k51qzi5uqu5dgnfwbc46une4upw1vc9hxznymyeykmg6rev1513yrnbyrwmmql
|
|
0xe501017200240801122012c8299ec2c51dffbbcb4f9fccadcee1424cb237e9b30d3cd72d47c18103689d
|
|
*/
|
|
```
|
|
|
|
## hdkey
|
|
|
|
SLIP-0010 hierarchical deterministic (HD) wallets for implementation. Based on code from
|
|
[scure-bip32](https://github.com/paulmillr/scure-bip32). Check out
|
|
[scure-bip39](https://github.com/paulmillr/scure-bip39) if you also need mnemonic phrases.
|
|
|
|
- SLIP-0010 publicKey is 33 bytes (see
|
|
[this issue](https://github.com/satoshilabs/slips/issues/1251)), if you want 32-byte publicKey,
|
|
use `.publicKeyRaw` getter
|
|
- SLIP-0010 vectors fingerprint is actually `parentFingerprint`
|
|
- SLIP-0010 doesn't allow deriving non-hardened keys for Ed25519, however some other libraries treat
|
|
non-hardened keys (`m/0/1`) as hardened (`m/0'/1'`). If you want this behaviour, there is a flag
|
|
`forceHardened` in `derive` method
|
|
|
|
```ts
|
|
import { HDKey } from 'ed25519-keygen/hdkey';
|
|
const hdkey1 = HDKey.fromMasterSeed(seed);
|
|
|
|
// props
|
|
[hdkey1.depth, hdkey1.index, hdkey1.chainCode];
|
|
console.log(hdkey2.privateKey, hdkey2.publicKey);
|
|
console.log(hdkey3.derive("m/0/2147483647'/1'"));
|
|
const sig = hdkey3.sign(hash);
|
|
hdkey3.verify(hash, sig);
|
|
```
|
|
|
|
Note: `chainCode` property is essentially a private part of a secret "master" key, it should be
|
|
guarded from unauthorized access.
|
|
|
|
The full API is:
|
|
|
|
```ts
|
|
class HDKey {
|
|
public static HARDENED_OFFSET: number;
|
|
public static fromMasterSeed(seed: Uint8Array | string): HDKey;
|
|
|
|
readonly depth: number = 0;
|
|
readonly index: number = 0;
|
|
readonly chainCode: Uint8Array | null = null;
|
|
readonly parentFingerprint: number = 0;
|
|
public readonly privateKey: Uint8Array;
|
|
|
|
get fingerprint(): number;
|
|
get fingerprintHex(): string;
|
|
get parentFingerprintHex(): string;
|
|
get pubKeyHash(): Uint8Array;
|
|
get publicKey(): Uint8Array;
|
|
get publicKeyRaw(): Uint8Array;
|
|
|
|
derive(path: string, forceHardened = false): HDKey;
|
|
deriveChild(index: number): HDKey;
|
|
sign(hash: Uint8Array): Uint8Array;
|
|
verify(hash: Uint8Array, signature: Uint8Array): boolean;
|
|
}
|
|
```
|
|
|
|
## utils
|
|
|
|
```ts
|
|
import { randomBytes } from 'ed25519-keygen/utils';
|
|
const key = randomBytes(32);
|
|
```
|
|
|
|
CSPRNG for secure generation of random Uint8Array. Utilizes webcrypto under the hood.
|
|
|
|
## License
|
|
|
|
MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file.
|