176 lines
4.9 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')
const uint8arraysConcat = require('uint8arrays/concat')
const uint8arraysFromString = require('uint8arrays/from-string')
const cryptoKeys = require('libp2p-crypto/src/keys')
const PeerId = require('peer-id')
2020-06-26 17:37:53 +02:00
const varint = require('varint')
const uint8arraysEquals = require('uint8arrays/equals')
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 {Uint8Array} params.payloadType
* @param {Uint8Array} params.payload marshaled record
* @param {Uint8Array} 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 {Uint8Array}
*/
marshal () {
if (this._marshal) {
return this._marshal
}
2020-06-26 17:37:53 +02:00
const publicKey = cryptoKeys.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 uint8arraysEquals(this.peerId.pubKey.bytes, other.peerId.pubKey.bytes) &&
uint8arraysEquals(this.payloadType, other.payloadType) &&
uint8arraysEquals(this.payload, other.payload) &&
uint8arraysEquals(this.signature, 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 Uint8Array to sign or verify a signature.
* @param {string} domain
* @param {Uint8Array} payloadType
* @param {Uint8Array} payload
* @return {Uint8Array}
*/
2020-07-15 11:40:57 +02:00
const formatSignaturePayload = (domain, payloadType, payload) => {
// When signing, a peer will prepare a Uint8Array by concatenating the following:
2020-06-26 17:37:53 +02:00
// - 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
domain = uint8arraysFromString(domain)
const domainLength = varint.encode(domain.byteLength)
2020-06-26 17:37:53 +02:00
const payloadTypeLength = varint.encode(payloadType.length)
const payloadLength = varint.encode(payload.length)
return uint8arraysConcat([
new Uint8Array(domainLength),
domain,
new Uint8Array(payloadTypeLength),
2020-06-26 17:37:53 +02:00
payloadType,
new Uint8Array(payloadLength),
2020-06-26 17:37:53 +02:00
payload
])
}
/**
* Unmarshal a serialized Envelope protobuf message.
* @param {Uint8Array} 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
const payloadType = uint8arraysFromString(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.
* @param {Uint8Array} data
2020-06-24 15:10:08 +02:00
* @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