@libp2p/crypto
Version:
Crypto primitives for libp2p
133 lines (111 loc) • 3.8 kB
text/typescript
import { InvalidParametersError } from '@libp2p/interface'
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { base64urlToBuffer } from '../../util.js'
import webcrypto from '../../webcrypto/index.js'
import type { Curve } from './index.ts'
import type { ECDHKey, ECDHKeyPair, JWKEncodedPrivateKey, JWKEncodedPublicKey } from '../interface.js'
const curveLengths = {
'P-256': 32,
'P-384': 48,
'P-521': 66
}
const curveTypes = Object.keys(curveLengths)
const names = curveTypes.join(' / ')
export async function generateEphemeralKeyPair (curve: Curve): Promise<ECDHKey> {
if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') {
throw new InvalidParametersError(`Unknown curve: ${curve}. Must be ${names}`)
}
const pair = await webcrypto.get().subtle.generateKey(
{
name: 'ECDH',
namedCurve: curve
},
true,
['deriveBits']
)
// forcePrivate is used for testing only
const genSharedKey = async (theirPub: Uint8Array, forcePrivate?: ECDHKeyPair): Promise<Uint8Array> => {
let privateKey
if (forcePrivate != null) {
privateKey = await webcrypto.get().subtle.importKey(
'jwk',
unmarshalPrivateKey(curve, forcePrivate),
{
name: 'ECDH',
namedCurve: curve
},
false,
['deriveBits']
)
} else {
privateKey = pair.privateKey
}
const key = await webcrypto.get().subtle.importKey(
'jwk',
unmarshalPublicKey(curve, theirPub),
{
name: 'ECDH',
namedCurve: curve
},
false,
[]
)
const buffer = await webcrypto.get().subtle.deriveBits(
{
name: 'ECDH',
public: key
},
privateKey,
curveLengths[curve] * 8
)
return new Uint8Array(buffer, 0, buffer.byteLength)
}
const publicKey = await webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
const ecdhKey: ECDHKey = {
key: marshalPublicKey(publicKey),
genSharedKey
}
return ecdhKey
}
// Marshal converts a jwk encoded ECDH public key into the
// form specified in section 4.3.6 of ANSI X9.62. (This is the format
// go-ipfs uses)
function marshalPublicKey (jwk: JsonWebKey): Uint8Array {
if (jwk.crv == null || jwk.x == null || jwk.y == null) {
throw new InvalidParametersError('JWK was missing components')
}
if (jwk.crv !== 'P-256' && jwk.crv !== 'P-384' && jwk.crv !== 'P-521') {
throw new InvalidParametersError(`Unknown curve: ${jwk.crv}. Must be ${names}`)
}
const byteLen = curveLengths[jwk.crv]
return uint8ArrayConcat([
Uint8Array.from([4]), // uncompressed point
base64urlToBuffer(jwk.x, byteLen),
base64urlToBuffer(jwk.y, byteLen)
], 1 + byteLen * 2)
}
/**
* Unmarshal converts a point, serialized by Marshal, into an jwk encoded key
*/
function unmarshalPublicKey (curve: Curve, key: Uint8Array): JWKEncodedPublicKey {
if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') {
throw new InvalidParametersError(`Unknown curve: ${curve}. Must be ${names}`)
}
const byteLen = curveLengths[curve]
if (!uint8ArrayEquals(key.subarray(0, 1), Uint8Array.from([4]))) {
throw new InvalidParametersError('Cannot unmarshal public key - invalid key format')
}
return {
kty: 'EC',
crv: curve,
x: uint8ArrayToString(key.subarray(1, byteLen + 1), 'base64url'),
y: uint8ArrayToString(key.subarray(1 + byteLen), 'base64url'),
ext: true
}
}
const unmarshalPrivateKey = (curve: Curve, key: ECDHKeyPair): JWKEncodedPrivateKey => ({
...unmarshalPublicKey(curve, key.public),
d: uint8ArrayToString(key.private, 'base64url')
})