-
Notifications
You must be signed in to change notification settings - Fork 26
feat: initial implementation #1
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
Merged
Merged
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,51 @@ | ||
'use strict' | ||
|
||
const base32Encode = require('base32-encode') | ||
const Big = require('big.js') | ||
const NanoDate = require('nano-date').default | ||
|
||
const debug = require('debug') | ||
const log = debug('jsipns') | ||
log.error = debug('jsipns:error') | ||
|
||
const ipnsEntryProto = require('./pb/ipns.proto') | ||
const { parseRFC3339 } = require('./utils') | ||
const ERRORS = require('./errors') | ||
|
||
/** | ||
* Creates a new ipns entry and signs it with the given private key. | ||
* The ipns entry validity should follow the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision. | ||
* Note: This function does not embed the public key. If you want to do that, use `EmbedPublicKey`. | ||
* | ||
* @param {Object} privateKey private key for signing the record. | ||
* @param {string} value value to be stored in the record. | ||
* @param {number} seq sequence number of the record. | ||
* @param {string} eol end of life datetime of the record. | ||
* @param {function(Error)} [callback] | ||
* @returns {Promise|void} | ||
* @param {number} seq number representing the current version of the record. | ||
* @param {string} lifetime lifetime of the record (in milliseconds). | ||
* @param {function(Error, entry)} [callback] | ||
* @returns {function(Error, entry)} callback | ||
*/ | ||
const create = (privateKey, value, seq, eol, callback) => { | ||
const validity = eol.toISOString() | ||
const create = (privateKey, value, seq, lifetime, callback) => { | ||
// Calculate eol with nanoseconds precision | ||
const bnLifetime = new Big(lifetime) | ||
const bnCurrentDate = new Big(new NanoDate()) | ||
const bnEol = bnCurrentDate.plus(bnLifetime).times('10e+6') | ||
const nanoDateEol = new NanoDate(bnEol.toString()) | ||
|
||
// Validity in ISOString with nanoseconds precision and validity type EOL | ||
const isoValidity = nanoDateEol.toISOStringFull() | ||
const validityType = ipnsEntryProto.ValidityType.EOL | ||
|
||
sign(privateKey, value, validityType, validity, (error, signature) => { | ||
sign(privateKey, value, validityType, isoValidity, (error, signature) => { | ||
if (error) { | ||
log.error(error) | ||
return callback(error) | ||
log.error('record signature creation failed') | ||
return callback(Object.assign(new Error('record signature verification failed'), { code: ERRORS.ERR_SIGNATURE_CREATION })) | ||
} | ||
|
||
const entry = { | ||
value: value, | ||
signature: signature, // TODO confirm format compliance with go-ipfs | ||
validityType: validityType, | ||
validity: validity, | ||
validity: isoValidity, | ||
sequence: seq | ||
} | ||
|
||
|
@@ -48,7 +60,7 @@ const create = (privateKey, value, seq, eol, callback) => { | |
* @param {Object} publicKey public key for validating the record. | ||
* @param {Object} entry ipns entry record. | ||
* @param {function(Error)} [callback] | ||
* @returns {Promise|void} | ||
* @returns {function(Error)} callback | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this |
||
*/ | ||
const validate = (publicKey, entry, callback) => { | ||
const { value, validityType, validity } = entry | ||
|
@@ -63,7 +75,14 @@ const validate = (publicKey, entry, callback) => { | |
|
||
// Validate according to the validity type | ||
if (validityType === ipnsEntryProto.ValidityType.EOL) { | ||
const validityDate = Date.parse(validity.toString()) | ||
let validityDate | ||
|
||
try { | ||
validityDate = parseRFC3339(validity.toString()) | ||
} catch (e) { | ||
log.error('unrecognized validity format (not an rfc3339 format)') | ||
return callback(Object.assign(new Error('unrecognized validity format (not an rfc3339 format)'), { code: ERRORS.ERR_UNRECOGNIZED_FORMAT })) | ||
} | ||
|
||
if (validityDate < Date.now()) { | ||
log.error('record has expired') | ||
|
@@ -85,10 +104,10 @@ const validate = (publicKey, entry, callback) => { | |
* @param {Object} publicKey public key for validating the record. | ||
* @param {Object} entry ipns entry record. | ||
* @param {function(Error)} [callback] | ||
* @returns {Promise|void} | ||
* @return {Void} | ||
*/ | ||
const embedPublicKey = (publicKey, entry, callback) => { | ||
return callback(new Error('not implemented yet')) | ||
callback(new Error('not implemented yet')) | ||
} | ||
|
||
/** | ||
|
@@ -97,33 +116,30 @@ const embedPublicKey = (publicKey, entry, callback) => { | |
* @param {Object} peerId peer identifier object. | ||
* @param {Object} entry ipns entry record. | ||
* @param {function(Error)} [callback] | ||
* @returns {Promise|void} | ||
* @return {Void} | ||
*/ | ||
const extractPublicKey = (peerId, entry, callback) => { | ||
return callback(new Error('not implemented yet')) | ||
callback(new Error('not implemented yet')) | ||
} | ||
|
||
// rawStdEncoding as go | ||
// TODO Remove once resolved | ||
// Created PR for allowing this inside base32-encode https://github.com/LinusU/base32-encode/issues/2 | ||
const regex = new RegExp('=', 'g') | ||
const rawStdEncoding = (key) => base32Encode(key, 'RFC4648').replace(regex, '') | ||
// rawStdEncoding with RFC4648 | ||
const rawStdEncoding = (key) => base32Encode(key, 'RFC4648', { padding: false }) | ||
|
||
/** | ||
* Get key for storing the record in the datastore. | ||
* Get key for storing the record locally. | ||
* Format: /ipns/${base32(<HASH>)} | ||
* | ||
* @param {Buffer} key peer identifier object. | ||
* @returns {string} | ||
*/ | ||
const getDatastoreKey = (key) => `/ipns/${rawStdEncoding(key)}` | ||
const getLocalKey = (key) => `/ipns/${rawStdEncoding(key)}` | ||
|
||
/** | ||
* Get key for sharing the record in the routing mechanism. | ||
* Format: ${base32(/ipns/<HASH>)}, ${base32(/pk/<HASH>)} | ||
* | ||
* @param {Buffer} key peer identifier object. | ||
* @returns {string} | ||
* @returns {Object} containgin the `nameKey` and the `ipnsKey`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo "containgin" |
||
*/ | ||
const getIdKeys = (key) => { | ||
const pkBuffer = Buffer.from('/pk/') | ||
|
@@ -165,8 +181,8 @@ module.exports = { | |
embedPublicKey, | ||
// extract public key from the record | ||
extractPublicKey, | ||
// get key for datastore | ||
getDatastoreKey, | ||
// get key for storing the entry locally | ||
getLocalKey, | ||
// get keys for routing | ||
getIdKeys, | ||
// marshal | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
'use strict' | ||
|
||
const leftPad = require('left-pad') | ||
|
||
/** | ||
* Convert a JavaScript date into an `RFC3339Nano` formatted | ||
* string. | ||
* | ||
* @param {Date} time | ||
* @returns {string} | ||
*/ | ||
module.exports.toRFC3339 = (time) => { | ||
const year = time.getUTCFullYear() | ||
const month = leftPad(time.getUTCMonth() + 1, 2, '0') | ||
const day = leftPad(time.getUTCDate(), 2, '0') | ||
const hour = leftPad(time.getUTCHours(), 2, '0') | ||
const minute = leftPad(time.getUTCMinutes(), 2, '0') | ||
const seconds = leftPad(time.getUTCSeconds(), 2, '0') | ||
const milliseconds = time.getUTCMilliseconds() | ||
const nanoseconds = milliseconds * 1000 * 1000 | ||
|
||
return `${year}-${month}-${day}T${hour}:${minute}:${seconds}.${nanoseconds}Z` | ||
} | ||
|
||
/** | ||
* Parses a date string formatted as `RFC3339Nano` into a | ||
* JavaScript Date object. | ||
* | ||
* @param {string} time | ||
* @returns {Date} | ||
*/ | ||
module.exports.parseRFC3339 = (time) => { | ||
const rfc3339Matcher = new RegExp( | ||
// 2006-01-02T | ||
'(\\d{4})-(\\d{2})-(\\d{2})T' + | ||
// 15:04:05 | ||
'(\\d{2}):(\\d{2}):(\\d{2})' + | ||
// .999999999Z | ||
'\\.(\\d+)Z' | ||
) | ||
const m = String(time).trim().match(rfc3339Matcher) | ||
|
||
if (!m) { | ||
throw new Error('Invalid format') | ||
} | ||
|
||
const year = parseInt(m[1], 10) | ||
const month = parseInt(m[2], 10) - 1 | ||
const date = parseInt(m[3], 10) | ||
const hour = parseInt(m[4], 10) | ||
const minute = parseInt(m[5], 10) | ||
const second = parseInt(m[6], 10) | ||
const millisecond = parseInt(m[7].slice(0, -6), 10) | ||
|
||
return new Date(Date.UTC(year, month, date, hour, minute, second, millisecond)) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back 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.
This needs documentation about the shape or class of the object that is returned, it's methods and properties.
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.
haha, still says
(string)
here, should it be a number now?