2020-06-03 14:58:58 +02:00
|
|
|
'use strict'
|
|
|
|
|
|
|
|
const errCode = require('err-code')
|
2021-08-16 21:29:06 +02:00
|
|
|
const { concat: uint8arraysConcat } = require('uint8arrays/concat')
|
|
|
|
const { fromString: uint8arraysFromString } = require('uint8arrays/from-string')
|
2021-04-15 09:40:02 +02:00
|
|
|
// @ts-ignore libp2p-crypto does not support types
|
2020-08-24 11:58:02 +01:00
|
|
|
const cryptoKeys = require('libp2p-crypto/src/keys')
|
2020-06-03 14:58:58 +02:00
|
|
|
const PeerId = require('peer-id')
|
2020-06-26 17:37:53 +02:00
|
|
|
const varint = require('varint')
|
2021-08-16 21:29:06 +02:00
|
|
|
const { equals: uint8arraysEquals } = require('uint8arrays/equals')
|
2020-06-03 14:58:58 +02:00
|
|
|
|
2020-06-26 17:37:53 +02:00
|
|
|
const { codes } = require('../../errors')
|
2021-04-15 09:40:02 +02:00
|
|
|
const { Envelope: Protobuf } = require('./envelope')
|
2020-06-03 14:58:58 +02:00
|
|
|
|
|
|
|
/**
|
2020-12-10 14:48:14 +01:00
|
|
|
* @typedef {import('libp2p-interfaces/src/record/types').Record} Record
|
2020-06-03 14:58:58 +02:00
|
|
|
*/
|
2020-12-10 14:48:14 +01:00
|
|
|
|
2020-06-03 14:58:58 +02:00
|
|
|
class Envelope {
|
|
|
|
/**
|
2020-12-10 14:48:14 +01:00
|
|
|
* The Envelope is responsible for keeping an arbitrary signed record
|
|
|
|
* by a libp2p peer.
|
|
|
|
*
|
2020-10-06 14:59:43 +02:00
|
|
|
* @class
|
2020-06-03 14:58:58 +02:00
|
|
|
* @param {object} params
|
|
|
|
* @param {PeerId} params.peerId
|
2020-08-24 11:58:02 +01:00
|
|
|
* @param {Uint8Array} params.payloadType
|
2020-10-06 14:59:43 +02:00
|
|
|
* @param {Uint8Array} params.payload - marshaled record
|
|
|
|
* @param {Uint8Array} params.signature - signature of the domain string :: type hint :: payload.
|
2020-06-03 14:58:58 +02:00
|
|
|
*/
|
|
|
|
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.
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
|
|
|
* @returns {Uint8Array}
|
2020-06-03 14:58:58 +02:00
|
|
|
*/
|
|
|
|
marshal () {
|
|
|
|
if (this._marshal) {
|
|
|
|
return this._marshal
|
|
|
|
}
|
2020-06-26 17:37:53 +02:00
|
|
|
|
2020-08-24 11:58:02 +01:00
|
|
|
const publicKey = cryptoKeys.marshalPublicKey(this.peerId.pubKey)
|
2020-06-03 14:58:58 +02:00
|
|
|
|
2021-04-15 09:40:02 +02:00
|
|
|
this._marshal = Protobuf.encode({
|
|
|
|
publicKey: publicKey,
|
|
|
|
payloadType: this.payloadType,
|
2020-06-03 14:58:58 +02:00
|
|
|
payload: this.payload,
|
|
|
|
signature: this.signature
|
2021-04-15 09:40:02 +02:00
|
|
|
}).finish()
|
2020-06-03 14:58:58 +02:00
|
|
|
|
|
|
|
return this._marshal
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Verifies if the other Envelope is identical to this one.
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
2020-06-03 14:58:58 +02:00
|
|
|
* @param {Envelope} other
|
2020-10-06 14:59:43 +02:00
|
|
|
* @returns {boolean}
|
2020-06-03 14:58:58 +02:00
|
|
|
*/
|
2020-07-15 11:40:57 +02:00
|
|
|
equals (other) {
|
2020-08-24 11:58:02 +01:00
|
|
|
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)
|
2020-06-03 14:58:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validate envelope data signature for the given domain.
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
2020-06-03 14:58:58 +02:00
|
|
|
* @param {string} domain
|
2020-10-06 14:59:43 +02:00
|
|
|
* @returns {Promise<boolean>}
|
2020-06-03 14:58:58 +02:00
|
|
|
*/
|
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-03 14:58:58 +02:00
|
|
|
|
2020-06-26 17:37:53 +02:00
|
|
|
return this.peerId.pubKey.verify(signData, this.signature)
|
2020-06-03 14:58:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-08-24 11:58:02 +01:00
|
|
|
* Helper function that prepares a Uint8Array to sign or verify a signature.
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
2020-06-03 14:58:58 +02:00
|
|
|
* @param {string} domain
|
2020-08-24 11:58:02 +01:00
|
|
|
* @param {Uint8Array} payloadType
|
|
|
|
* @param {Uint8Array} payload
|
2020-10-06 14:59:43 +02:00
|
|
|
* @returns {Uint8Array}
|
2020-06-03 14:58:58 +02:00
|
|
|
*/
|
2020-07-15 11:40:57 +02:00
|
|
|
const formatSignaturePayload = (domain, payloadType, payload) => {
|
2020-08-24 11:58:02 +01:00
|
|
|
// 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
|
|
|
|
|
2020-12-10 14:48:14 +01:00
|
|
|
const domainUint8Array = uint8arraysFromString(domain)
|
|
|
|
const domainLength = varint.encode(domainUint8Array.byteLength)
|
2020-06-26 17:37:53 +02:00
|
|
|
const payloadTypeLength = varint.encode(payloadType.length)
|
|
|
|
const payloadLength = varint.encode(payload.length)
|
|
|
|
|
2020-08-24 11:58:02 +01:00
|
|
|
return uint8arraysConcat([
|
|
|
|
new Uint8Array(domainLength),
|
2020-12-10 14:48:14 +01:00
|
|
|
domainUint8Array,
|
2020-08-24 11:58:02 +01:00
|
|
|
new Uint8Array(payloadTypeLength),
|
2020-06-26 17:37:53 +02:00
|
|
|
payloadType,
|
2020-08-24 11:58:02 +01:00
|
|
|
new Uint8Array(payloadLength),
|
2020-06-26 17:37:53 +02:00
|
|
|
payload
|
|
|
|
])
|
2020-06-03 14:58:58 +02:00
|
|
|
}
|
|
|
|
|
2020-07-17 14:00:59 +02:00
|
|
|
/**
|
|
|
|
* Unmarshal a serialized Envelope protobuf message.
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
2020-08-24 11:58:02 +01:00
|
|
|
* @param {Uint8Array} data
|
2020-10-06 14:59:43 +02:00
|
|
|
* @returns {Promise<Envelope>}
|
2020-07-17 14:00:59 +02:00
|
|
|
*/
|
|
|
|
Envelope.createFromProtobuf = async (data) => {
|
2021-04-15 09:40:02 +02:00
|
|
|
const envelopeData = Protobuf.decode(data)
|
|
|
|
const peerId = await PeerId.createFromPubKey(envelopeData.publicKey)
|
2020-06-03 14:58:58 +02:00
|
|
|
|
|
|
|
return new Envelope({
|
|
|
|
peerId,
|
2021-04-15 09:40:02 +02:00
|
|
|
payloadType: envelopeData.payloadType,
|
2020-06-03 14:58:58 +02:00
|
|
|
payload: envelopeData.payload,
|
|
|
|
signature: envelopeData.signature
|
|
|
|
})
|
|
|
|
}
|
2020-06-24 15:10:08 +02:00
|
|
|
|
|
|
|
/**
|
2020-10-06 14:59:43 +02:00
|
|
|
* Seal marshals the given Record, places the marshaled bytes inside an Envelope
|
|
|
|
* and signs it with the given peerId's private key.
|
|
|
|
*
|
|
|
|
* @async
|
|
|
|
* @param {Record} record
|
|
|
|
* @param {PeerId} peerId
|
2020-12-10 14:48:14 +01:00
|
|
|
* @returns {Promise<Envelope>}
|
2020-10-06 14:59:43 +02:00
|
|
|
*/
|
2020-06-24 15:10:08 +02:00
|
|
|
Envelope.seal = async (record, peerId) => {
|
|
|
|
const domain = record.domain
|
2020-08-27 12:44:09 +02:00
|
|
|
const payloadType = 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-10-06 14:59:43 +02:00
|
|
|
*
|
2020-08-24 11:58:02 +01:00
|
|
|
* @param {Uint8Array} data
|
2020-06-24 15:10:08 +02:00
|
|
|
* @param {string} domain
|
2020-12-10 14:48:14 +01:00
|
|
|
* @returns {Promise<Envelope>}
|
2020-06-24 15:10:08 +02:00
|
|
|
*/
|
|
|
|
Envelope.openAndCertify = async (data, domain) => {
|
2020-07-17 14:00:59 +02:00
|
|
|
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
|