@libp2p/crypto
Version:
Crypto primitives for libp2p
108 lines • 4.3 kB
JavaScript
import { ed25519 as ed } from '@noble/curves/ed25519';
import { toString as uint8arrayToString } from 'uint8arrays/to-string';
import crypto from '../../webcrypto/index.js';
const PUBLIC_KEY_BYTE_LENGTH = 32;
const PRIVATE_KEY_BYTE_LENGTH = 64; // private key is actually 32 bytes but for historical reasons we concat private and public keys
const KEYS_BYTE_LENGTH = 32;
export { PUBLIC_KEY_BYTE_LENGTH as publicKeyLength };
export { PRIVATE_KEY_BYTE_LENGTH as privateKeyLength };
// memoize support result to skip additional awaits every time we use an ed key
let ed25519Supported;
const webCryptoEd25519SupportedPromise = (async () => {
try {
await crypto.get().subtle.generateKey({ name: 'Ed25519' }, true, ['sign', 'verify']);
return true;
}
catch {
return false;
}
})();
export function generateKey() {
// the actual private key (32 bytes)
const privateKeyRaw = ed.utils.randomPrivateKey();
const publicKey = ed.getPublicKey(privateKeyRaw);
// concatenated the public key to the private key
const privateKey = concatKeys(privateKeyRaw, publicKey);
return {
privateKey,
publicKey
};
}
export function generateKeyFromSeed(seed) {
if (seed.length !== KEYS_BYTE_LENGTH) {
throw new TypeError('"seed" must be 32 bytes in length.');
}
else if (!(seed instanceof Uint8Array)) {
throw new TypeError('"seed" must be a node.js Buffer, or Uint8Array.');
}
// based on node forges algorithm, the seed is used directly as private key
const privateKeyRaw = seed;
const publicKey = ed.getPublicKey(privateKeyRaw);
const privateKey = concatKeys(privateKeyRaw, publicKey);
return {
privateKey,
publicKey
};
}
async function hashAndSignWebCrypto(privateKey, msg) {
let privateKeyRaw;
if (privateKey.length === PRIVATE_KEY_BYTE_LENGTH) {
privateKeyRaw = privateKey.subarray(0, 32);
}
else {
privateKeyRaw = privateKey;
}
const jwk = {
crv: 'Ed25519',
kty: 'OKP',
x: uint8arrayToString(privateKey.subarray(32), 'base64url'),
d: uint8arrayToString(privateKeyRaw, 'base64url'),
ext: true,
key_ops: ['sign']
};
const key = await crypto.get().subtle.importKey('jwk', jwk, { name: 'Ed25519' }, true, ['sign']);
const sig = await crypto.get().subtle.sign({ name: 'Ed25519' }, key, msg instanceof Uint8Array ? msg : msg.subarray());
return new Uint8Array(sig, 0, sig.byteLength);
}
function hashAndSignNoble(privateKey, msg) {
const privateKeyRaw = privateKey.subarray(0, KEYS_BYTE_LENGTH);
return ed.sign(msg instanceof Uint8Array ? msg : msg.subarray(), privateKeyRaw);
}
export async function hashAndSign(privateKey, msg) {
if (ed25519Supported == null) {
ed25519Supported = await webCryptoEd25519SupportedPromise;
}
if (ed25519Supported) {
return hashAndSignWebCrypto(privateKey, msg);
}
return hashAndSignNoble(privateKey, msg);
}
async function hashAndVerifyWebCrypto(publicKey, sig, msg) {
if (publicKey.buffer instanceof ArrayBuffer) {
const key = await crypto.get().subtle.importKey('raw', publicKey.buffer, { name: 'Ed25519' }, false, ['verify']);
const isValid = await crypto.get().subtle.verify({ name: 'Ed25519' }, key, sig, msg instanceof Uint8Array ? msg : msg.subarray());
return isValid;
}
throw new TypeError('WebCrypto does not support SharedArrayBuffer for Ed25519 keys');
}
function hashAndVerifyNoble(publicKey, sig, msg) {
return ed.verify(sig, msg instanceof Uint8Array ? msg : msg.subarray(), publicKey);
}
export async function hashAndVerify(publicKey, sig, msg) {
if (ed25519Supported == null) {
ed25519Supported = await webCryptoEd25519SupportedPromise;
}
if (ed25519Supported) {
return hashAndVerifyWebCrypto(publicKey, sig, msg);
}
return hashAndVerifyNoble(publicKey, sig, msg);
}
function concatKeys(privateKeyRaw, publicKey) {
const privateKey = new Uint8Array(PRIVATE_KEY_BYTE_LENGTH);
for (let i = 0; i < KEYS_BYTE_LENGTH; i++) {
privateKey[i] = privateKeyRaw[i];
privateKey[KEYS_BYTE_LENGTH + i] = publicKey[i];
}
return privateKey;
}
//# sourceMappingURL=index.browser.js.map