diff --git a/.aegir.js b/.aegir.js deleted file mode 100644 index f965db3..0000000 --- a/.aegir.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' - -module.exports = { - webpack: { - node: { - // required by the cbor module - stream: true, - - // needed by the ipfs-repo-migrations module - path: true, - - // needed by the abstract-leveldown module - Buffer: true - } - } -} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 9fdbf73..6aeb9f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,16 @@ language: node_js +dist: bionic cache: npm stages: - check - test - cov +branches: + only: + - master + - /^release\/.*$/ + node_js: - 'lts/*' - 'node' @@ -16,7 +22,7 @@ os: before_install: # modules with pre-built binaries may not have deployed versions for bleeding-edge node so this lets us fall back to building from source - - npm install -g node-pre-gyp + - npm install -g @mapbox/node-pre-gyp script: npx nyc -s npm run test:node -- --bail after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov @@ -25,7 +31,7 @@ jobs: include: - stage: check script: - - npx aegir dep-check -- -i wrtc -i electron-webrtc + - npx aegir dep-check - npm run lint - stage: test @@ -33,16 +39,13 @@ jobs: addons: chrome: stable script: - - npx aegir test -t browser - - npx aegir test -t webworker + - npx aegir test -t browser -t webworker - stage: test name: firefox addons: firefox: latest - script: - - npx aegir test -t browser -- --browsers FirefoxHeadless - - npx aegir test -t webworker -- --browsers FirefoxHeadless + script: npx aegir test -t browser -t webworker -- --browser firefox notifications: email: false diff --git a/package.json b/package.json index b85b84f..db7774d 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,12 @@ "description": "ipns record definitions", "leadMaintainer": "Vasco Santos ", "main": "src/index.js", + "types": "dist/src/index.d.ts", "scripts": { - "build": "aegir build", + "prepare": "run-s prepare:*", + "prepare:proto": "pbjs -t static-module -w commonjs --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/pb/ipns.js src/pb/ipns.proto", + "prepare:proto-types": "pbts -o src/pb/ipns.d.ts src/pb/ipns.js", + "prepare:types": "aegir build --no-bundle", "lint": "aegir lint", "release": "aegir release", "release-minor": "aegir release --type minor", @@ -37,22 +41,22 @@ "err-code": "^3.0.1", "interface-datastore": "^3.0.1", "libp2p-crypto": "^0.19.0", - "multibase": "^3.0.1", - "multihashes": "^3.0.1", + "multibase": "^4.0.2", + "multihashes": "^4.0.2", "peer-id": "^0.14.2", - "protons": "^2.0.0", + "protobufjs": "^6.10.2", "timestamp-nano": "^1.0.0", "uint8arrays": "^2.0.5" }, "devDependencies": { - "aegir": "^30.2.0", + "@types/chai-string": "^1.4.2", + "@types/debug": "^4.1.5", + "aegir": "^31.0.4", "chai": "^4.2.0", "chai-bytes": "~0.1.2", "chai-string": "^1.5.0", "dirty-chai": "^2.0.1", - "ipfs": "^0.54.2", - "ipfs-http-client": "^49.0.2", - "ipfsd-ctl": "^7.0.3" + "npm-run-all": "^4.1.5" }, "contributors": [ "Vasco Santos ", diff --git a/src/index.js b/src/index.js index f4062a2..30bc67b 100644 --- a/src/index.js +++ b/src/index.js @@ -12,26 +12,24 @@ const uint8ArrayToString = require('uint8arrays/to-string') const uint8ArrayConcat = require('uint8arrays/concat') const debug = require('debug') -const log = debug('jsipns') -log.error = debug('jsipns:error') +const log = Object.assign(debug('jsipns'), { + error: debug('jsipns:error') +}) -const ipnsEntryProto = require('./pb/ipns.proto') +const { + IpnsEntry: ipnsEntryProto +} = require('./pb/ipns.js') const { parseRFC3339 } = require('./utils') const ERRORS = require('./errors') -const ID_MULTIHASH_CODE = multihash.names.id +const ID_MULTIHASH_CODE = multihash.names.identity const namespace = '/ipns/' /** - * IPNS entry - * - * @typedef {Object} IpnsEntry - * @property {string} value - value to be stored in the record - * @property {Uint8Array} signature - signature of the record - * @property {number} validityType - Type of validation being used - * @property {string} validity - expiration datetime for the record in RFC3339 format - * @property {number} sequence - number representing the version of the record + * @typedef {import('./types').IPNSEntry} IPNSEntry + * @typedef {import('libp2p-crypto').PublicKey} PublicKey + * @typedef {import('libp2p-crypto').PrivateKey} PrivateKey */ /** @@ -39,11 +37,10 @@ const namespace = '/ipns/' * 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 {PrivateKey} privateKey - private key for signing the record. * @param {string} value - value to be stored in the record. * @param {number} seq - number representing the current version of the record. * @param {number|string} lifetime - lifetime of the record (in milliseconds). - * @returns {Promise} entry */ const create = (privateKey, value, seq, lifetime) => { // Validity in ISOString with nanoseconds precision and validity type EOL @@ -56,17 +53,23 @@ const create = (privateKey, value, seq, lifetime) => { * Same as create(), but instead of generating a new Date, it receives the intended expiration time * WARNING: nano precision is not standard, make sure the value in seconds is 9 orders of magnitude lesser than the one provided. * - * @param {Object} privateKey - private key for signing the record. + * @param {PrivateKey} privateKey - private key for signing the record. * @param {string} value - value to be stored in the record. * @param {number} seq - number representing the current version of the record. * @param {string} expiration - expiration datetime for record in the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision. - * @returns {Promise} entry */ const createWithExpiration = (privateKey, value, seq, expiration) => { const validityType = ipnsEntryProto.ValidityType.EOL return _create(privateKey, value, seq, expiration, validityType) } +/** + * @param {PrivateKey} privateKey + * @param {string} value + * @param {number} seq + * @param {string} isoValidity + * @param {number} validityType + */ const _create = async (privateKey, value, seq, isoValidity, validityType) => { const signature = await sign(privateKey, value, validityType, isoValidity) @@ -85,9 +88,8 @@ const _create = async (privateKey, value, seq, isoValidity, validityType) => { /** * Validates the given ipns entry against the given public key. * - * @param {Object} publicKey - public key for validating the record. - * @param {IpnsEntry} entry - ipns entry record. - * @returns {Promise} + * @param {PublicKey} publicKey - public key for validating the record. + * @param {IPNSEntry} entry - ipns entry record. */ const validate = async (publicKey, entry) => { const { value, validityType, validity } = entry @@ -116,7 +118,7 @@ const validate = async (publicKey, entry) => { throw errCode(new Error('unrecognized validity format (not an rfc3339 format)'), ERRORS.ERR_UNRECOGNIZED_FORMAT) } - if (validityDate < Date.now()) { + if (validityDate.getTime() < Date.now()) { log.error('record has expired') throw errCode(new Error('record has expired'), ERRORS.ERR_IPNS_EXPIRED_RECORD) } @@ -137,9 +139,8 @@ const validate = async (publicKey, entry) => { * send this as part of the record itself. For newer ed25519 keys, the public key * can be embedded in the peerId. * - * @param {Object} publicKey - public key to embed. - * @param {Object} entry - ipns entry record. - * @returns {IpnsEntry} entry with public key embedded + * @param {PublicKey} publicKey - public key to embed. + * @param {IPNSEntry} entry - ipns entry record. */ const embedPublicKey = async (publicKey, entry) => { if (!publicKey || !publicKey.bytes || !entry) { @@ -182,9 +183,8 @@ const embedPublicKey = async (publicKey, entry) => { /** * Extracts a public key matching `pid` from the ipns record. * - * @param {Object} peerId - peer identifier object. - * @param {IpnsEntry} entry - ipns entry record. - * @returns {Object} the public key + * @param {PeerId} peerId - peer identifier object. + * @param {IPNSEntry} entry - ipns entry record. */ const extractPublicKey = (peerId, entry) => { if (!entry || !peerId) { @@ -211,7 +211,11 @@ const extractPublicKey = (peerId, entry) => { throw Object.assign(new Error('no public key is available'), { code: ERRORS.ERR_UNDEFINED_PARAMETER }) } -// rawStdEncoding with RFC4648 +/** + * rawStdEncoding with RFC4648 + * + * @param {Uint8Array} key + */ const rawStdEncoding = (key) => multibase.encode('base32', key).toString().slice(1).toUpperCase() /** @@ -219,7 +223,6 @@ const rawStdEncoding = (key) => multibase.encode('base32', key).toString().slice * Format: /ipns/${base32()} * * @param {Uint8Array} key - peer identifier object. - * @returns {string} */ const getLocalKey = (key) => new Key(`/ipns/${rawStdEncoding(key)}`) @@ -228,7 +231,6 @@ const getLocalKey = (key) => new Key(`/ipns/${rawStdEncoding(key)}`) * Format: ${base32(/ipns/)}, ${base32(/pk/)} * * @param {Uint8Array} pid - peer identifier represented by the multihash of the public key as Uint8Array. - * @returns {Object} containing the `nameKey` and the `ipnsKey`. */ const getIdKeys = (pid) => { const pkBuffer = uint8ArrayFromString('/pk/') @@ -242,7 +244,14 @@ const getIdKeys = (pid) => { } } -// Sign ipns record data +/** + * Sign ipns record data + * + * @param {PrivateKey} privateKey + * @param {string} value + * @param {number} validityType + * @param {Uint8Array | string} validity + */ const sign = (privateKey, value, validityType, validity) => { try { const dataForSignature = ipnsEntryDataForSig(value, validityType, validity) @@ -254,7 +263,11 @@ const sign = (privateKey, value, validityType, validity) => { } } -// Utility for getting the validity type code name of a validity +/** + * Utility for getting the validity type code name of a validity + * + * @param {number} validityType + */ const getValidityType = (validityType) => { if (validityType.toString() === '0') { return 'EOL' @@ -265,7 +278,13 @@ const getValidityType = (validityType) => { throw errCode(error, ERRORS.ERR_UNRECOGNIZED_VALIDITY) } -// Utility for creating the record data for being signed +/** + * Utility for creating the record data for being signed + * + * @param {string | Uint8Array} value + * @param {number} validityType + * @param {string | Uint8Array} validity + */ const ipnsEntryDataForSig = (value, validityType, validity) => { if (!(value instanceof Uint8Array)) { value = uint8ArrayFromString(value) @@ -280,7 +299,11 @@ const ipnsEntryDataForSig = (value, validityType, validity) => { return uint8ArrayConcat([value, validity, validityTypeBuffer]) } -// Utility for extracting the public key from a peer-id +/** + * Utility for extracting the public key from a peer-id + * + * @param {PeerId} peerId + */ const extractPublicKeyFromId = (peerId) => { const decodedId = multihash.decode(peerId.id) @@ -291,11 +314,34 @@ const extractPublicKeyFromId = (peerId) => { return crypto.keys.unmarshalPublicKey(decodedId.digest) } -const marshal = ipnsEntryProto.encode +/** + * @param {IPNSEntry} obj + */ +const marshal = (obj) => { + return ipnsEntryProto.encode(obj).finish() +} -const unmarshal = ipnsEntryProto.decode +/** + * @param {Uint8Array} buf + * @returns {IPNSEntry} + */ +const unmarshal = (buf) => { + const message = ipnsEntryProto.decode(buf) + + // @ts-ignore + return ipnsEntryProto.toObject(message, { + defaults: false, + arrays: true, + longs: Number, + objects: false + }) +} const validator = { + /** + * @param {Uint8Array} marshalledData + * @param {Uint8Array} key + */ validate: async (marshalledData, key) => { const receivedEntry = unmarshal(marshalledData) const bufferId = key.slice('/ipns/'.length) @@ -308,6 +354,10 @@ const validator = { await validate(pubKey, receivedEntry) return true }, + /** + * @param {Uint8Array} dataA + * @param {Uint8Array} dataB + */ select: (dataA, dataB) => { const entryA = unmarshal(dataA) const entryB = unmarshal(dataB) diff --git a/src/pb/ipns.d.ts b/src/pb/ipns.d.ts new file mode 100644 index 0000000..c5ccb7a --- /dev/null +++ b/src/pb/ipns.d.ts @@ -0,0 +1,103 @@ +import * as $protobuf from "protobufjs"; +/** Properties of an IpnsEntry. */ +export interface IIpnsEntry { + + /** IpnsEntry value */ + value: Uint8Array; + + /** IpnsEntry signature */ + signature: Uint8Array; + + /** IpnsEntry validityType */ + validityType?: (IpnsEntry.ValidityType|null); + + /** IpnsEntry validity */ + validity?: (Uint8Array|null); + + /** IpnsEntry sequence */ + sequence?: (number|null); + + /** IpnsEntry ttl */ + ttl?: (number|null); + + /** IpnsEntry pubKey */ + pubKey?: (Uint8Array|null); +} + +/** Represents an IpnsEntry. */ +export class IpnsEntry implements IIpnsEntry { + + /** + * Constructs a new IpnsEntry. + * @param [p] Properties to set + */ + constructor(p?: IIpnsEntry); + + /** IpnsEntry value. */ + public value: Uint8Array; + + /** IpnsEntry signature. */ + public signature: Uint8Array; + + /** IpnsEntry validityType. */ + public validityType: IpnsEntry.ValidityType; + + /** IpnsEntry validity. */ + public validity: Uint8Array; + + /** IpnsEntry sequence. */ + public sequence: number; + + /** IpnsEntry ttl. */ + public ttl: number; + + /** IpnsEntry pubKey. */ + public pubKey: Uint8Array; + + /** + * Encodes the specified IpnsEntry message. Does not implicitly {@link IpnsEntry.verify|verify} messages. + * @param m IpnsEntry message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IIpnsEntry, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an IpnsEntry message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns IpnsEntry + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): IpnsEntry; + + /** + * Creates an IpnsEntry message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns IpnsEntry + */ + public static fromObject(d: { [k: string]: any }): IpnsEntry; + + /** + * Creates a plain object from an IpnsEntry message. Also converts values to other types if specified. + * @param m IpnsEntry + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: IpnsEntry, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this IpnsEntry to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +export namespace IpnsEntry { + + /** ValidityType enum. */ + enum ValidityType { + EOL = 0 + } +} diff --git a/src/pb/ipns.js b/src/pb/ipns.js new file mode 100644 index 0000000..9092af9 --- /dev/null +++ b/src/pb/ipns.js @@ -0,0 +1,351 @@ +/*eslint-disable*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + +$root.IpnsEntry = (function() { + + /** + * Properties of an IpnsEntry. + * @exports IIpnsEntry + * @interface IIpnsEntry + * @property {Uint8Array} value IpnsEntry value + * @property {Uint8Array} signature IpnsEntry signature + * @property {IpnsEntry.ValidityType|null} [validityType] IpnsEntry validityType + * @property {Uint8Array|null} [validity] IpnsEntry validity + * @property {number|null} [sequence] IpnsEntry sequence + * @property {number|null} [ttl] IpnsEntry ttl + * @property {Uint8Array|null} [pubKey] IpnsEntry pubKey + */ + + /** + * Constructs a new IpnsEntry. + * @exports IpnsEntry + * @classdesc Represents an IpnsEntry. + * @implements IIpnsEntry + * @constructor + * @param {IIpnsEntry=} [p] Properties to set + */ + function IpnsEntry(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * IpnsEntry value. + * @member {Uint8Array} value + * @memberof IpnsEntry + * @instance + */ + IpnsEntry.prototype.value = $util.newBuffer([]); + + /** + * IpnsEntry signature. + * @member {Uint8Array} signature + * @memberof IpnsEntry + * @instance + */ + IpnsEntry.prototype.signature = $util.newBuffer([]); + + /** + * IpnsEntry validityType. + * @member {IpnsEntry.ValidityType} validityType + * @memberof IpnsEntry + * @instance + */ + IpnsEntry.prototype.validityType = 0; + + /** + * IpnsEntry validity. + * @member {Uint8Array} validity + * @memberof IpnsEntry + * @instance + */ + IpnsEntry.prototype.validity = $util.newBuffer([]); + + /** + * IpnsEntry sequence. + * @member {number} sequence + * @memberof IpnsEntry + * @instance + */ + IpnsEntry.prototype.sequence = $util.Long ? $util.Long.fromBits(0,0,true) : 0; + + /** + * IpnsEntry ttl. + * @member {number} ttl + * @memberof IpnsEntry + * @instance + */ + IpnsEntry.prototype.ttl = $util.Long ? $util.Long.fromBits(0,0,true) : 0; + + /** + * IpnsEntry pubKey. + * @member {Uint8Array} pubKey + * @memberof IpnsEntry + * @instance + */ + IpnsEntry.prototype.pubKey = $util.newBuffer([]); + + /** + * Encodes the specified IpnsEntry message. Does not implicitly {@link IpnsEntry.verify|verify} messages. + * @function encode + * @memberof IpnsEntry + * @static + * @param {IIpnsEntry} m IpnsEntry message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + IpnsEntry.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + w.uint32(10).bytes(m.value); + w.uint32(18).bytes(m.signature); + if (m.validityType != null && Object.hasOwnProperty.call(m, "validityType")) + w.uint32(24).int32(m.validityType); + if (m.validity != null && Object.hasOwnProperty.call(m, "validity")) + w.uint32(34).bytes(m.validity); + if (m.sequence != null && Object.hasOwnProperty.call(m, "sequence")) + w.uint32(40).uint64(m.sequence); + if (m.ttl != null && Object.hasOwnProperty.call(m, "ttl")) + w.uint32(48).uint64(m.ttl); + if (m.pubKey != null && Object.hasOwnProperty.call(m, "pubKey")) + w.uint32(58).bytes(m.pubKey); + return w; + }; + + /** + * Decodes an IpnsEntry message from the specified reader or buffer. + * @function decode + * @memberof IpnsEntry + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {IpnsEntry} IpnsEntry + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + IpnsEntry.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.IpnsEntry(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.value = r.bytes(); + break; + case 2: + m.signature = r.bytes(); + break; + case 3: + m.validityType = r.int32(); + break; + case 4: + m.validity = r.bytes(); + break; + case 5: + m.sequence = r.uint64(); + break; + case 6: + m.ttl = r.uint64(); + break; + case 7: + m.pubKey = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + if (!m.hasOwnProperty("value")) + throw $util.ProtocolError("missing required 'value'", { instance: m }); + if (!m.hasOwnProperty("signature")) + throw $util.ProtocolError("missing required 'signature'", { instance: m }); + return m; + }; + + /** + * Creates an IpnsEntry message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof IpnsEntry + * @static + * @param {Object.} d Plain object + * @returns {IpnsEntry} IpnsEntry + */ + IpnsEntry.fromObject = function fromObject(d) { + if (d instanceof $root.IpnsEntry) + return d; + var m = new $root.IpnsEntry(); + if (d.value != null) { + if (typeof d.value === "string") + $util.base64.decode(d.value, m.value = $util.newBuffer($util.base64.length(d.value)), 0); + else if (d.value.length) + m.value = d.value; + } + if (d.signature != null) { + if (typeof d.signature === "string") + $util.base64.decode(d.signature, m.signature = $util.newBuffer($util.base64.length(d.signature)), 0); + else if (d.signature.length) + m.signature = d.signature; + } + switch (d.validityType) { + case "EOL": + case 0: + m.validityType = 0; + break; + } + if (d.validity != null) { + if (typeof d.validity === "string") + $util.base64.decode(d.validity, m.validity = $util.newBuffer($util.base64.length(d.validity)), 0); + else if (d.validity.length) + m.validity = d.validity; + } + if (d.sequence != null) { + if ($util.Long) + (m.sequence = $util.Long.fromValue(d.sequence)).unsigned = true; + else if (typeof d.sequence === "string") + m.sequence = parseInt(d.sequence, 10); + else if (typeof d.sequence === "number") + m.sequence = d.sequence; + else if (typeof d.sequence === "object") + m.sequence = new $util.LongBits(d.sequence.low >>> 0, d.sequence.high >>> 0).toNumber(true); + } + if (d.ttl != null) { + if ($util.Long) + (m.ttl = $util.Long.fromValue(d.ttl)).unsigned = true; + else if (typeof d.ttl === "string") + m.ttl = parseInt(d.ttl, 10); + else if (typeof d.ttl === "number") + m.ttl = d.ttl; + else if (typeof d.ttl === "object") + m.ttl = new $util.LongBits(d.ttl.low >>> 0, d.ttl.high >>> 0).toNumber(true); + } + if (d.pubKey != null) { + if (typeof d.pubKey === "string") + $util.base64.decode(d.pubKey, m.pubKey = $util.newBuffer($util.base64.length(d.pubKey)), 0); + else if (d.pubKey.length) + m.pubKey = d.pubKey; + } + return m; + }; + + /** + * Creates a plain object from an IpnsEntry message. Also converts values to other types if specified. + * @function toObject + * @memberof IpnsEntry + * @static + * @param {IpnsEntry} m IpnsEntry + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + IpnsEntry.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + if (o.bytes === String) + d.value = ""; + else { + d.value = []; + if (o.bytes !== Array) + d.value = $util.newBuffer(d.value); + } + if (o.bytes === String) + d.signature = ""; + else { + d.signature = []; + if (o.bytes !== Array) + d.signature = $util.newBuffer(d.signature); + } + d.validityType = o.enums === String ? "EOL" : 0; + if (o.bytes === String) + d.validity = ""; + else { + d.validity = []; + if (o.bytes !== Array) + d.validity = $util.newBuffer(d.validity); + } + if ($util.Long) { + var n = new $util.Long(0, 0, true); + d.sequence = o.longs === String ? n.toString() : o.longs === Number ? n.toNumber() : n; + } else + d.sequence = o.longs === String ? "0" : 0; + if ($util.Long) { + var n = new $util.Long(0, 0, true); + d.ttl = o.longs === String ? n.toString() : o.longs === Number ? n.toNumber() : n; + } else + d.ttl = o.longs === String ? "0" : 0; + if (o.bytes === String) + d.pubKey = ""; + else { + d.pubKey = []; + if (o.bytes !== Array) + d.pubKey = $util.newBuffer(d.pubKey); + } + } + if (m.value != null && m.hasOwnProperty("value")) { + d.value = o.bytes === String ? $util.base64.encode(m.value, 0, m.value.length) : o.bytes === Array ? Array.prototype.slice.call(m.value) : m.value; + } + if (m.signature != null && m.hasOwnProperty("signature")) { + d.signature = o.bytes === String ? $util.base64.encode(m.signature, 0, m.signature.length) : o.bytes === Array ? Array.prototype.slice.call(m.signature) : m.signature; + } + if (m.validityType != null && m.hasOwnProperty("validityType")) { + d.validityType = o.enums === String ? $root.IpnsEntry.ValidityType[m.validityType] : m.validityType; + } + if (m.validity != null && m.hasOwnProperty("validity")) { + d.validity = o.bytes === String ? $util.base64.encode(m.validity, 0, m.validity.length) : o.bytes === Array ? Array.prototype.slice.call(m.validity) : m.validity; + } + if (m.sequence != null && m.hasOwnProperty("sequence")) { + if (typeof m.sequence === "number") + d.sequence = o.longs === String ? String(m.sequence) : m.sequence; + else + d.sequence = o.longs === String ? $util.Long.prototype.toString.call(m.sequence) : o.longs === Number ? new $util.LongBits(m.sequence.low >>> 0, m.sequence.high >>> 0).toNumber(true) : m.sequence; + } + if (m.ttl != null && m.hasOwnProperty("ttl")) { + if (typeof m.ttl === "number") + d.ttl = o.longs === String ? String(m.ttl) : m.ttl; + else + d.ttl = o.longs === String ? $util.Long.prototype.toString.call(m.ttl) : o.longs === Number ? new $util.LongBits(m.ttl.low >>> 0, m.ttl.high >>> 0).toNumber(true) : m.ttl; + } + if (m.pubKey != null && m.hasOwnProperty("pubKey")) { + d.pubKey = o.bytes === String ? $util.base64.encode(m.pubKey, 0, m.pubKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.pubKey) : m.pubKey; + } + return d; + }; + + /** + * Converts this IpnsEntry to JSON. + * @function toJSON + * @memberof IpnsEntry + * @instance + * @returns {Object.} JSON object + */ + IpnsEntry.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * ValidityType enum. + * @name IpnsEntry.ValidityType + * @enum {number} + * @property {number} EOL=0 EOL value + */ + IpnsEntry.ValidityType = (function() { + var valuesById = {}, values = Object.create(valuesById); + values[valuesById[0] = "EOL"] = 0; + return values; + })(); + + return IpnsEntry; +})(); + +module.exports = $root; diff --git a/src/pb/ipns.proto.js b/src/pb/ipns.proto similarity index 81% rename from src/pb/ipns.proto.js rename to src/pb/ipns.proto index 4d12b43..c192920 100644 --- a/src/pb/ipns.proto.js +++ b/src/pb/ipns.proto @@ -1,9 +1,3 @@ -'use strict' - -const protons = require('protons') - -/* eslint-disable no-tabs */ -const message = ` message IpnsEntry { enum ValidityType { EOL = 0; // setting an EOL says "this record is valid until..." @@ -25,6 +19,3 @@ message IpnsEntry { // peerID, making this field unnecessary. optional bytes pubKey = 7; } -` - -module.exports = protons(message).IpnsEntry diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..2cdebf1 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,11 @@ + +import ValidityType from './pb/ipns' + +export interface IPNSEntry { + value: Uint8Array, // value to be stored in the record + signature: Uint8Array, // signature of the record + validityType: ValidityType, // Type of validation being used + validity: Uint8Array, // expiration datetime for the record in RFC3339 format + sequence: number // number representing the version of the record + pubKey?: Uint8Array +} diff --git a/src/utils.js b/src/utils.js index 73490fc..b9bd032 100644 --- a/src/utils.js +++ b/src/utils.js @@ -5,7 +5,6 @@ * string. * * @param {Date} time - * @returns {string} */ module.exports.toRFC3339 = (time) => { const year = time.getUTCFullYear() @@ -25,7 +24,6 @@ module.exports.toRFC3339 = (time) => { * JavaScript Date object. * * @param {string} time - * @returns {Date} */ module.exports.parseRFC3339 = (time) => { const rfc3339Matcher = new RegExp( diff --git a/test/index.spec.js b/test/index.spec.js index 100a798..8edff37 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -11,42 +11,30 @@ chai.use(chaiBytes) chai.use(chaiString) const { toB58String } = require('multihashes') const uint8ArrayFromString = require('uint8arrays/from-string') +const PeerId = require('peer-id') -const ipfs = require('ipfs') -const ipfsHttpClient = require('ipfs-http-client') -const { createFactory } = require('ipfsd-ctl') const crypto = require('libp2p-crypto') const { fromB58String } = require('multihashes') const ipns = require('../src') const ERRORS = require('../src/errors') -const ctl = createFactory({ - type: 'proc', - ipfsHttpModule: ipfsHttpClient, - ipfsModule: ipfs -}) - describe('ipns', function () { this.timeout(20 * 1000) const cid = 'QmWEekX7EZLUd9VXRNMRXW3LXe4F6x7mB8oPxY5XLptrBq' - let ipfs = null - let ipfsd = null - let ipfsId = null - let rsa = null + /** @type {{ id: string, publicKey: string }} */ + let ipfsId + /** @type {import('libp2p-crypto').keys.supportedKeys.rsa.RsaPrivateKey} */ + let rsa before(async () => { rsa = await crypto.keys.generateKeyPair('RSA', 2048) - ipfsd = await ctl.spawn() - ipfs = ipfsd.api - ipfsId = await ipfs.id() - }) - after(async () => { - if (ipfsd) { - await ipfsd.stop() + ipfsId = { + id: 'QmQ73f8hbM4hKwRYBqeUsPtiwfE2x6WPv9WnzaYt4nYcXf', + publicKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUOR0AJ2/yO0S/JIkKmYV/QdHzQXi1nrTCCXtEbUDVW5mXZfNf9bKeNDfW3UIIOwVzV6/sRhJqq/8sQAhmzURj1q2onCKgSLzjdePSLtykolQeQGSD+JO7rcxOLx+sTdIyJiclP/tkK2gfo2nrI6pjFTKNzR8VSoJx7gfiqY1N9LBgDsD4WjaOM2pBgzgVUlXpk27Aqvcd+htSWi6JuIZaBhPY/IzEvXwntGH9k7F8VkT6nUBilhqFFSWnz8cNKToCHjyhoozKfqN89S7EGMiNvG4cX4Dc/nVXlZRTAi4PNNewutimujROy2/tNEquC2uAlcAzhRAcLL/ujhEjJYP1AgMBAAE=' } }) @@ -59,9 +47,9 @@ describe('ipns', function () { value: uint8ArrayFromString(cid), sequence: sequence }) - expect(entry).to.have.a.property('validity') - expect(entry).to.have.a.property('signature') - expect(entry).to.have.a.property('validityType') + expect(entry).to.have.property('validity') + expect(entry).to.have.property('signature') + expect(entry).to.have.property('validityType') }) it('should be able to create a record with a fixed expiration', async () => { @@ -72,7 +60,7 @@ describe('ipns', function () { const entry = await ipns.createWithExpiration(rsa, cid, sequence, expiration) await ipns.validate(rsa.public, entry) - expect(entry).to.have.a.property('validity') + expect(entry).to.have.property('validity') expect(entry.validity).to.equalBytes(uint8ArrayFromString('2033-05-18T03:33:20.000000000Z')) }) @@ -91,7 +79,7 @@ describe('ipns', function () { const entry = await ipns.create(rsa, cid, sequence, validity) // corrupt the record by changing the value to random bytes - entry.value = crypto.randomBytes(46).toString() + entry.value = crypto.randomBytes(46) try { await ipns.validate(rsa.public, entry) @@ -148,10 +136,10 @@ describe('ipns', function () { const idKeys = ipns.getIdKeys(fromB58String(ipfsId.id)) expect(idKeys).to.exist() - expect(idKeys).to.have.a.property('routingPubKey') - expect(idKeys).to.have.a.property('pkKey') - expect(idKeys).to.have.a.property('ipnsKey') - expect(idKeys).to.have.a.property('routingKey') + expect(idKeys).to.have.property('routingPubKey') + expect(idKeys).to.have.property('pkKey') + expect(idKeys).to.have.property('ipnsKey') + expect(idKeys).to.have.property('routingKey') expect(idKeys.routingPubKey).to.not.startsWith('/pk/') expect(idKeys.pkKey).to.not.startsWith('/pk/') expect(idKeys.ipnsKey).to.not.startsWith('/ipns/') @@ -223,7 +211,7 @@ describe('ipns', function () { const entry = await ipns.create(rsa, cid, sequence, validity) await ipns.embedPublicKey(rsa.public, entry) - const publicKey = ipns.extractPublicKey(ipfsId, entry) + const publicKey = ipns.extractPublicKey(PeerId.createFromB58String(ipfsId.id), entry) expect(publicKey.bytes).to.equalBytes(rsa.public.bytes) }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f2ef147 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "./node_modules/aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "test", + "src" + ], + "exclude": [ + "src/pb/ipns.js" + ] +}