mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-04-25 02:22:14 +00:00
* fix: replace node buffers with uint8arrays Upgrades all deps and replaces all use of node Buffers with Uint8Arrays BREAKING CHANGES: - All deps used by this module now use Uint8Arrays in place of node Buffers * chore: browser fixes * chore: remove .only * chore: stringify uint8array before parsing * chore: update interop suite * chore: remove ts from build command * chore: update deps * fix: update records to use uint8array * chore: fix lint * chore: update deps Co-authored-by: Jacob Heun <jacobheun@gmail.com>
176 lines
4.9 KiB
JavaScript
176 lines
4.9 KiB
JavaScript
'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')
|
|
const varint = require('varint')
|
|
const uint8arraysEquals = require('uint8arrays/equals')
|
|
|
|
const { codes } = require('../../errors')
|
|
const Protobuf = require('./envelope.proto')
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
|
|
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}
|
|
*/
|
|
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
|
|
* @return {Promise<boolean>}
|
|
*/
|
|
validate (domain) {
|
|
const signData = formatSignaturePayload(domain, this.payloadType, this.payload)
|
|
|
|
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}
|
|
*/
|
|
const formatSignaturePayload = (domain, payloadType, payload) => {
|
|
// When signing, a peer will prepare a Uint8Array 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
|
|
|
|
domain = uint8arraysFromString(domain)
|
|
const domainLength = varint.encode(domain.byteLength)
|
|
const payloadTypeLength = varint.encode(payloadType.length)
|
|
const payloadLength = varint.encode(payload.length)
|
|
|
|
return uint8arraysConcat([
|
|
new Uint8Array(domainLength),
|
|
domain,
|
|
new Uint8Array(payloadTypeLength),
|
|
payloadType,
|
|
new Uint8Array(payloadLength),
|
|
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
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @return {Envelope}
|
|
*/
|
|
Envelope.seal = async (record, peerId) => {
|
|
const domain = record.domain
|
|
const payloadType = uint8arraysFromString(record.codec)
|
|
const payload = record.marshal()
|
|
|
|
const signData = formatSignaturePayload(domain, payloadType, payload)
|
|
const signature = await peerId.privKey.sign(signData)
|
|
|
|
return new Envelope({
|
|
peerId,
|
|
payloadType,
|
|
payload,
|
|
signature
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Open and certify a given marshalled envelope.
|
|
* Data is unmarshalled and the signature validated for the given domain.
|
|
* @param {Uint8Array} data
|
|
* @param {string} domain
|
|
* @return {Envelope}
|
|
*/
|
|
Envelope.openAndCertify = async (data, domain) => {
|
|
const envelope = await Envelope.createFromProtobuf(data)
|
|
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)
|
|
}
|
|
|
|
return envelope
|
|
}
|
|
|
|
module.exports = Envelope
|