-
Notifications
You must be signed in to change notification settings - Fork 517
feat: signed peer records data types #681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
aa423ff
e7356a0
19c9c24
5a7b8de
b4fa13a
08b086e
55d63bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -47,7 +47,7 @@ All libp2p nodes keep a `PeerStore`, that among other information stores a set o | |||||
|
|
||||||
| Libp2p peer records were created to enable the distribution of verifiable address records, which we can prove originated from the addressed peer itself. With such guarantees, libp2p can prioritize addresses based on their authenticity, with the most strict strategy being to only dial certified addresses. | ||||||
|
|
||||||
| A peer record contains the peers' publicly reachable listen addresses, and may be extended in the future to contain additional metadata relevant to routing. It also contains a `seq` field, so that we can order peer records by time and identify if a received record is more recent than the stored one. | ||||||
| A peer record contains the peers' publicly reachable listen addresses, and may be extended in the future to contain additional metadata relevant to routing. It also contains a `seqNumber` field, so that we can order peer records by time and identify if a received record is more recent than the stored one. | ||||||
|
||||||
| A peer record contains the peers' publicly reachable listen addresses, and may be extended in the future to contain additional metadata relevant to routing. It also contains a `seqNumber` field, so that we can order peer records by time and identify if a received record is more recent than the stored one. | |
| A peer record contains the peers' publicly reachable listen addresses, and may be extended in the future to contain additional metadata relevant to routing. It also contains a `seqNumber` field, a timestamp per the spec, so that we can verify the most recent record. |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Once a record is received and its signature properly validated, its envelope should be stored in the AddressBook on its byte representations. However, the `seqNumber` number of the record must be compared with potentially stored records, so that we do not override correct data. | |
| Once a record is received and its signature properly validated, its envelope is stored in the AddressBook in its byte representation. The `seqNumber` remains unmarshalled so that we can quickly compare it against incoming records to determine the most recent record. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,14 +5,18 @@ const log = debug('libp2p:envelope') | |
| log.error = debug('libp2p:envelope:error') | ||
| const errCode = require('err-code') | ||
|
|
||
| const { Buffer } = require('buffer') | ||
|
|
||
| const crypto = require('libp2p-crypto') | ||
| const multicodec = require('multicodec') | ||
| const PeerId = require('peer-id') | ||
| const varint = require('varint') | ||
|
|
||
| const { codes } = require('../../errors') | ||
| const Protobuf = require('./envelope.proto') | ||
|
|
||
| /** | ||
| * The Envelope is responsible for keeping arbitrary signed by a libp2p peer. | ||
| * The Envelope is responsible for keeping an arbitrary signed record | ||
| * by a libp2p peer. | ||
| */ | ||
| class Envelope { | ||
| /** | ||
|
|
@@ -41,7 +45,7 @@ class Envelope { | |
| if (this._marshal) { | ||
| return this._marshal | ||
| } | ||
| // TODO: type for marshal (default: RSA) | ||
|
|
||
| const publicKey = crypto.keys.marshalPublicKey(this.peerId.pubKey) | ||
|
|
||
| this._marshal = Protobuf.encode({ | ||
|
|
@@ -69,34 +73,43 @@ class Envelope { | |
| /** | ||
| * Validate envelope data signature for the given domain. | ||
| * @param {string} domain | ||
| * @return {Promise} | ||
| * @return {Promise<boolean>} | ||
| */ | ||
| async validate (domain) { | ||
| validate (domain) { | ||
| const signData = createSignData(domain, this.payloadType, this.payload) | ||
|
|
||
| try { | ||
| await this.peerId.pubKey.verify(signData, this.signature) | ||
| } catch (_) { | ||
| log.error('record signature verification failed') | ||
| // TODO | ||
| throw errCode(new Error('record signature verification failed'), 'ERRORS.ERR_SIGNATURE_VERIFICATION') | ||
| } | ||
| return this.peerId.pubKey.verify(signData, this.signature) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Helper function that prepares a buffer to sign or verify a signature. | ||
| * @param {string} domain | ||
| * @param {number} payloadType | ||
| * @param {Buffer} payloadType | ||
| * @param {Buffer} payload | ||
| * @return {Buffer} | ||
| */ | ||
| const createSignData = (domain, payloadType, payload) => { | ||
|
||
| // TODO: this should be compliant with the spec! | ||
| const domainBuffer = Buffer.from(domain) | ||
| const payloadTypeBuffer = Buffer.from(payloadType.toString()) | ||
|
|
||
| return Buffer.concat([domainBuffer, payloadTypeBuffer, payload]) | ||
| // 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 | ||
| ]) | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -118,15 +131,15 @@ const unmarshalEnvelope = async (data) => { | |
|
|
||
| /** | ||
| * Seal marshals the given Record, places the marshaled bytes inside an Envelope | ||
| * and signs with the given private key. | ||
| * 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 = Buffer.from(`${multicodec.print[record.codec]}${domain}`) | ||
| const payloadType = Buffer.from(record.codec) | ||
| const payload = record.marshal() | ||
|
|
||
| const signData = createSignData(domain, payloadType, payload) | ||
|
|
@@ -149,7 +162,11 @@ Envelope.seal = async (record, peerId) => { | |
| */ | ||
| Envelope.openAndCertify = async (data, domain) => { | ||
| const envelope = await unmarshalEnvelope(data) | ||
| await envelope.validate(domain) | ||
| 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 | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are strategies specified? Will this come later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we still do not support any. I added: "(no strategies implemented at the time of writing)"