Skip to content

Support specify hash algorithm in files.add #4

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,14 @@
"hapi": "^16.5.2",
"hapi-set-header": "^1.0.2",
"hoek": "^4.2.0",
"ipfs-api": "^14.3.3",
"ipfs-api": "^14.3.5",
"ipfs-bitswap": "~0.17.2",
"ipfs-block": "~0.6.0",
"ipfs-block-service": "~0.12.0",
"ipfs-multipart": "~0.1.0",
"ipfs-repo": "~0.17.0",
"ipfs-unixfs": "~0.1.13",
"ipfs-unixfs-engine": "~0.22.3",
"ipfs-unixfs-engine": "~0.22.5",
"ipld-resolver": "~0.13.2",
"is-ipfs": "^0.3.0",
"is-stream": "^1.1.0",
Expand Down
27 changes: 23 additions & 4 deletions src/cli/commands/files/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const pull = require('pull-stream')
const paramap = require('pull-paramap')
const zip = require('pull-zip')
const toPull = require('stream-to-pull-stream')
const mh = require('multihashes')
const utils = require('../../utils')
const print = require('../../utils').print

Expand Down Expand Up @@ -151,6 +152,11 @@ module.exports = {
type: 'boolean',
default: false,
describe: 'Write no output'
},
hash: {
type: 'string',
choices: [undefined].concat(Object.keys(mh.names)),
describe: 'Hash function to use. Will set Cid version to 1 if used. (experimental)'
}
},

Expand All @@ -160,8 +166,9 @@ module.exports = {
const options = {
strategy: argv.trickle ? 'trickle' : 'balanced',
shardSplitThreshold: argv.enableShardingExperiment ? argv.shardSplitThreshold : Infinity,
'cid-version': argv['cid-version'],
'raw-leaves': argv['raw-leaves']
cidVersion: argv.cidVersion,
rawLeaves: argv.rawLeaves,
hashAlg: argv.hash
}

// Temporary restriction on raw-leaves:
Expand All @@ -172,11 +179,23 @@ module.exports = {
// cid-version > 0 unless explicitly set to false.
//
// This retains feature parity without having to implement raw-leaves.
if (argv['cid-version'] > 0 && argv['raw-leaves'] !== false) {
if (argv.cidVersion > 0 && argv.rawLeaves !== false) {
throw new Error('Implied argument raw-leaves must be passed and set to false when cid-version is > 0')
}

if (argv['raw-leaves']) {
// Temporary restriction on raw-leaves:
// When hash != undefined then raw-leaves MUST be present and false.
//
// This is because raw-leaves is not yet implemented in js-ipfs,
// and go-ipfs changes the value of raw-leaves to true when
// hash != undefined unless explicitly set to false.
//
// This retains feature parity without having to implement raw-leaves.
if (argv.hash && argv.rawLeaves !== false) {
throw new Error('Implied argument raw-leaves must be passed and set to false when hash argument is specified')
}

if (argv.rawLeaves) {
throw new Error('Not implemented: raw-leaves')
}

Expand Down
25 changes: 18 additions & 7 deletions src/core/components/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ module.exports = function files (self) {
shardSplitThreshold: self._options.EXPERIMENTAL.sharding ? 1000 : Infinity
}, options)

if (opts.hashAlg && opts.cidVersion !== 1) {
opts.cidVersion = 1
}

return pull(
pull.map(normalizeContent),
pull.flatten(),
Expand Down Expand Up @@ -65,6 +69,13 @@ module.exports = function files (self) {
return callback(new Error('Invalid arguments, data must be an object, Buffer or readable stream'))
}

options = options || {}

// CID v0 is for multihashes encoded with sha2-256
if (options.hashAlg && options.cidVersion !== 1) {
options.cidVersion = 1
}

pull(
pull.values(normalizeContent(data)),
importer(self._ipldResolver, options),
Expand Down Expand Up @@ -117,15 +128,15 @@ module.exports = function files (self) {
function prepareFile (self, opts, file, callback) {
opts = opts || {}

waterfall([
(cb) => self.object.get(file.multihash, cb),
(node, cb) => {
let cid = new CID(node.multihash)
let cid = new CID(file.multihash)

if (opts['cid-version'] === 1) {
cid = cid.toV1()
}
if (opts.cidVersion === 1) {
cid = cid.toV1()
}

waterfall([
(cb) => self.object.get(cid, cb),
(node, cb) => {
const b58Hash = cid.toBaseEncodedString()

cb(null, {
Expand Down
31 changes: 10 additions & 21 deletions src/core/components/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,6 @@ const mh = require('multihashes')
const Unixfs = require('ipfs-unixfs')
const assert = require('assert')

function normalizeMultihash (multihash, enc) {
if (typeof multihash === 'string') {
if (enc === 'base58' || !enc) {
return multihash
}

return new Buffer(multihash, enc)
} else if (Buffer.isBuffer(multihash)) {
return multihash
} else {
throw new Error('unsupported multihash')
}
}

function parseBuffer (buf, encoding, callback) {
switch (encoding) {
case 'json':
Expand Down Expand Up @@ -178,20 +164,17 @@ module.exports = function object (self) {
}
}),

get: promisify((multihash, options, callback) => {
get: promisify((cid, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}

let mh

try {
mh = normalizeMultihash(multihash, options.enc)
cid = new CID(cid)
} catch (err) {
return callback(err)
}
const cid = new CID(mh)

self._ipldResolver.get(cid, (err, result) => {
if (err) {
Expand All @@ -204,13 +187,19 @@ module.exports = function object (self) {
})
}),

data: promisify((multihash, options, callback) => {
data: promisify((cid, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}

self.object.get(multihash, options, (err, node) => {
try {
cid = new CID(cid)
} catch (err) {
return callback(err)
}

self.object.get(cid, options, (err, node) => {
if (err) {
return callback(err)
}
Expand Down
19 changes: 10 additions & 9 deletions src/http-api/resources/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,19 @@ exports.add = {
query: Joi.object()
.keys({
'cid-version': Joi.number().integer().min(0).max(1),
hash: Joi.string().valid(Object.keys(mh.names)),
// Temporary restriction on raw-leaves:
// When cid-version=1 then raw-leaves MUST be present and false.
// When cid-version > 0 or hash != undefined then raw-leaves MUST be
// present and false.
//
// This is because raw-leaves is not yet implemented in js-ipfs,
// and go-ipfs changes the value of raw-leaves to true when
// cid-version > 0 unless explicitly set to false.
// cid-version > 0 or hash != undefined unless explicitly set to false.
//
// This retains feature parity without having to implement raw-leaves.
'raw-leaves': Joi.any().when('cid-version', {
is: 1,
then: Joi.boolean().valid(false).required(),
otherwise: Joi.boolean().valid(false)
})
'raw-leaves': Joi.boolean().valid(false)
.when('cid-version', { is: 1, then: Joi.required() })
.when('hash', { is: Joi.string(), then: Joi.required() })
})
// TODO: Necessary until validate "recursive", "stream-channels" etc.
.options({ allowUnknown: true })
Expand Down Expand Up @@ -208,8 +208,9 @@ exports.add = {
})

const options = {
'cid-version': request.query['cid-version'],
'raw-leaves': request.query['raw-leaves']
cidVersion: request.query['cid-version'],
rawLeaves: request.query['raw-leaves'],
hashAlg: request.query['hash']
}

pull(
Expand Down
5 changes: 2 additions & 3 deletions src/http-api/resources/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const DAGLink = dagPB.DAGLink
const DAGNode = dagPB.DAGNode
const waterfall = require('async/waterfall')
const series = require('async/series')
const CID = require('cids')
const debug = require('debug')
const log = debug('jsipfs:http-api:object')
log.error = debug('jsipfs:http-api:object:error')
Expand All @@ -20,9 +21,7 @@ exports.parseKey = (request, reply) => {
}

try {
return reply({
key: mh.fromB58String(request.query.arg)
})
return reply({ key: new CID(request.query.arg) })
} catch (err) {
log.error(err)
return reply({
Expand Down
25 changes: 25 additions & 0 deletions test/cli/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,22 @@ const fs = require('fs')
const path = require('path')
const compareDir = require('dir-compare').compareSync
const rimraf = require('rimraf').sync
const CID = require('cids')
const mh = require('multihashes')
const runOnAndOff = require('../utils/on-and-off')

// TODO: Test against all algorithms Object.keys(mh.names)
// This subset is known to work with both go-ipfs and js-ipfs as of 2017-09-05
const HASH_ALGS = [
'sha1',
'sha2-256',
'sha2-512',
'keccak-224',
'keccak-256',
'keccak-384',
'keccak-512'
]

describe('files', () => runOnAndOff((thing) => {
let ipfs
const readme = fs.readFileSync(path.join(process.cwd(), '/src/init-files/init-docs/readme'))
Expand Down Expand Up @@ -222,6 +236,17 @@ describe('files', () => runOnAndOff((thing) => {
})
})

HASH_ALGS.forEach((name) => {
it(`add with hash=${name} and raw-leaves=false`, () => {
return ipfs(`add src/init-files/init-docs/readme --hash=${name} --raw-leaves=false`)
.then((out) => {
const hash = out.split(' ')[1]
const cid = new CID(hash)
expect(mh.decode(cid.multihash).name).to.equal(name)
})
})
})

it('cat', () => {
return ipfs('files cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')
.then((out) => {
Expand Down