175 lines
4.6 KiB
JavaScript
Raw Normal View History

'use strict'
const debug = require('debug')
const log = debug('libp2p:envelope')
log.error = debug('libp2p:envelope:error')
const errCode = require('err-code')
2020-06-26 17:37:53 +02:00
const { Buffer } = require('buffer')
const crypto = require('libp2p-crypto')
const PeerId = require('peer-id')
2020-06-26 17:37:53 +02:00
const varint = require('varint')
2020-06-26 17:37:53 +02:00
const { codes } = require('../../errors')
const Protobuf = require('./envelope.proto')
/**
2020-06-26 17:37:53 +02:00
* The Envelope is responsible for keeping an arbitrary signed record
* by a libp2p peer.
*/
class Envelope {
/**
* @constructor
* @param {object} params
* @param {PeerId} params.peerId
* @param {Buffer} params.payloadType
* @param {Buffer} params.payload marshaled record
* @param {Buffer} params.signature signature of the domain string :: type hint :: payload.
*/
constructor ({ peerId, payloadType, payload, signature }) {
this.peerId = peerId
this.payloadType = payloadType
this.payload = payload
this.signature = signature
// Cache
this._marshal = undefined
}
/**
* Marshal the envelope content.
* @return {Buffer}
*/
marshal () {
if (this._marshal) {
return this._marshal
}
2020-06-26 17:37:53 +02:00
const publicKey = crypto.keys.marshalPublicKey(this.peerId.pubKey)
this._marshal = Protobuf.encode({
public_key: publicKey,
payload_type: this.payloadType,
payload: this.payload,
signature: this.signature
})
return this._marshal
}
/**
* Verifies if the other Envelope is identical to this one.
* @param {Envelope} other
* @return {boolean}
*/
2020-07-15 11:40:57 +02:00
equals (other) {
return this.peerId.pubKey.bytes.equals(other.peerId.pubKey.bytes) &&
this.payloadType.equals(other.payloadType) &&
this.payload.equals(other.payload) &&
this.signature.equals(other.signature)
}
/**
* Validate envelope data signature for the given domain.
* @param {string} domain
2020-06-26 17:37:53 +02:00
* @return {Promise<boolean>}
*/
2020-06-26 17:37:53 +02:00
validate (domain) {
2020-07-15 11:40:57 +02:00
const signData = formatSignaturePayload(domain, this.payloadType, this.payload)
2020-06-26 17:37:53 +02:00
return this.peerId.pubKey.verify(signData, this.signature)
}
}
/**
* Helper function that prepares a buffer to sign or verify a signature.
* @param {string} domain
2020-06-26 17:37:53 +02:00
* @param {Buffer} payloadType
* @param {Buffer} payload
* @return {Buffer}
*/
2020-07-15 11:40:57 +02:00
const formatSignaturePayload = (domain, payloadType, payload) => {
2020-06-26 17:37:53 +02:00
// When signing, a peer will prepare a buffer by concatenating the following:
// - The length of the domain separation string string in bytes
// - The domain separation string, encoded as UTF-8
// - The length of the payload_type field in bytes
// - The value of the payload_type field
// - The length of the payload field in bytes
// - The value of the payload field
const domainLength = varint.encode(Buffer.byteLength(domain))
const payloadTypeLength = varint.encode(payloadType.length)
const payloadLength = varint.encode(payload.length)
return Buffer.concat([
Buffer.from(domainLength),
Buffer.from(domain),
Buffer.from(payloadTypeLength),
payloadType,
Buffer.from(payloadLength),
payload
])
}
/**
* Unmarshal a serialized Envelope protobuf message.
* @param {Buffer} data
* @return {Promise<Envelope>}
*/
Envelope.createFromProtobuf = async (data) => {
const envelopeData = Protobuf.decode(data)
const peerId = await PeerId.createFromPubKey(envelopeData.public_key)
return new Envelope({
peerId,
payloadType: envelopeData.payload_type,
payload: envelopeData.payload,
signature: envelopeData.signature
})
}
2020-06-24 15:10:08 +02:00
/**
* Seal marshals the given Record, places the marshaled bytes inside an Envelope
2020-06-26 17:37:53 +02:00
* and signs it with the given peerId's private key.
2020-06-24 15:10:08 +02:00
* @async
* @param {Record} record
* @param {PeerId} peerId
* @return {Envelope}
*/
Envelope.seal = async (record, peerId) => {
const domain = record.domain
2020-06-26 17:37:53 +02:00
const payloadType = Buffer.from(record.codec)
2020-06-24 15:10:08 +02:00
const payload = record.marshal()
2020-07-15 11:40:57 +02:00
const signData = formatSignaturePayload(domain, payloadType, payload)
2020-06-24 15:10:08 +02:00
const signature = await peerId.privKey.sign(signData)
return new Envelope({
peerId,
payloadType,
payload,
signature
})
}
/**
* Open and certify a given marshalled envelope.
2020-07-15 11:40:57 +02:00
* Data is unmarshalled and the signature validated for the given domain.
2020-06-24 15:10:08 +02:00
* @param {Buffer} data
* @param {string} domain
* @return {Envelope}
*/
Envelope.openAndCertify = async (data, domain) => {
const envelope = await Envelope.createFromProtobuf(data)
2020-06-26 17:37:53 +02:00
const valid = await envelope.validate(domain)
if (!valid) {
throw errCode(new Error('envelope signature is not valid for the given domain'), codes.ERR_SIGNATURE_NOT_VALID)
}
2020-06-24 15:10:08 +02:00
return envelope
}
module.exports = Envelope