js-libp2p-noise/src/utils.ts
2021-01-25 11:40:21 +01:00

118 lines
3.7 KiB
TypeScript

import HKDF from 'bcrypto/lib/hkdf'
import x25519 from 'bcrypto/lib/js/x25519'
import SHA256 from 'bcrypto/lib/js/sha256'
import { Buffer } from 'buffer'
import PeerId from 'peer-id'
import { keys } from 'libp2p-crypto'
import { KeyPair } from './@types/libp2p'
import { bytes, bytes32 } from './@types/basic'
import { Hkdf, INoisePayload } from './@types/handshake'
import { pb } from './proto/payload'
import uint8ArrayEquals from 'uint8arrays/equals'
const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload
export function generateKeypair (): KeyPair {
const privateKey = x25519.privateKeyGenerate()
const publicKey = x25519.publicKeyCreate(privateKey)
return {
publicKey,
privateKey
}
}
export async function getPayload (
localPeer: PeerId,
staticPublicKey: bytes,
earlyData?: bytes
): Promise<bytes> {
const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey))
const earlyDataPayload = earlyData || Buffer.alloc(0)
return await createHandshakePayload(
localPeer.marshalPubKey(),
signedPayload,
earlyDataPayload
)
}
export function createHandshakePayload (
libp2pPublicKey: Uint8Array,
signedPayload: Uint8Array,
earlyData?: Uint8Array
): bytes {
const payloadInit = NoiseHandshakePayloadProto.create({
identityKey: Buffer.from(libp2pPublicKey),
identitySig: signedPayload,
data: earlyData || null
})
return Buffer.from(NoiseHandshakePayloadProto.encode(payloadInit).finish())
}
export async function signPayload (peerId: PeerId, payload: bytes): Promise<bytes> {
return Buffer.from(await peerId.privKey.sign(payload))
}
export async function getPeerIdFromPayload (payload: pb.INoiseHandshakePayload): Promise<PeerId> {
return await PeerId.createFromPubKey(Buffer.from(payload.identityKey as Uint8Array))
}
export function decodePayload (payload: bytes|Uint8Array): pb.INoiseHandshakePayload {
return NoiseHandshakePayloadProto.toObject(
NoiseHandshakePayloadProto.decode(Buffer.from(payload))
) as INoisePayload
}
export function getHandshakePayload (publicKey: bytes): bytes {
return Buffer.concat([Buffer.from('noise-libp2p-static-key:'), publicKey])
}
async function isValidPeerId (peerId: Uint8Array, publicKeyProtobuf: bytes) {
const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf)
return uint8ArrayEquals(generatedPeerId.id, peerId)
}
/**
* Verifies signed payload, throws on any irregularities.
*
* @param {bytes} noiseStaticKey - owner's noise static key
* @param {bytes} payload - decoded payload
* @param {PeerId} remotePeer - owner's libp2p peer ID
* @returns {Promise<PeerId>} - peer ID of payload owner
*/
export async function verifySignedPayload (
noiseStaticKey: bytes,
payload: pb.INoiseHandshakePayload,
remotePeer: PeerId
): Promise<PeerId> {
const identityKey = Buffer.from(payload.identityKey as Uint8Array)
if (!(await isValidPeerId(remotePeer.id, identityKey))) {
throw new Error("Peer ID doesn't match libp2p public key.")
}
const generatedPayload = getHandshakePayload(noiseStaticKey)
// Unmarshaling from PublicKey protobuf
const publicKey = keys.unmarshalPublicKey(identityKey)
if (!payload.identitySig || !publicKey.verify(generatedPayload, Buffer.from(payload.identitySig))) {
throw new Error("Static key doesn't match to peer that signed payload!")
}
return PeerId.createFromPubKey(identityKey)
}
export function getHkdf (ck: bytes32, ikm: bytes): Hkdf {
const info = Buffer.alloc(0)
const prk = HKDF.extract(SHA256, ikm, ck)
const okm = HKDF.expand(SHA256, prk, info, 96)
const k1 = okm.slice(0, 32)
const k2 = okm.slice(32, 64)
const k3 = okm.slice(64, 96)
return [k1, k2, k3]
}
export function isValidPublicKey (pk: bytes): boolean {
return x25519.publicKeyVerify(pk.slice(0, 32))
}