├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── multihashes.rb └── multihashes │ └── version.rb ├── multihashes.gemspec └── test ├── multihashes_test.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | cache: bundler 4 | rvm: 5 | - 2.6.3 6 | before_install: gem install bundler -v 2.0.1 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.2.0 4 | 5 | - Add `multicodecs` (ruby-multicodec) which adds _all_ the current multihash 6 | codecs to this gem. 7 | - Add a test to show the breaking changes 8 | 9 | ### Breaking changes: 10 | 11 | The official codec table does not match the initial (`0.1.x`) multihash mapping. 12 | 13 | `0x14` used to be named `sha3` and is now named `sha3-512` 14 | `0x40` used to be named `blake2b` and is now not assigned 15 | `0x41` used to be named `blake2s` and is now not assigned 16 | 17 | `blake2b` consists of 64 output lengths that give different hashes 18 | | name | code | 19 | |-------------|--------| 20 | | blake2b-8 | 0xb201 | 21 | | blake2b-16 | 0xb202 | 22 | | blake2b-24 | 0xb203 | 23 | | ... | | 24 | | blake2b-504 | 0xb23f | 25 | | blake2b-512 | 0xb240 | 26 | 27 | `blake2s` consists of 32 output lengths that give different hashes 28 | | name | code | 29 | |-------------|--------| 30 | | blake2s-8 | 0xb241 | 31 | | blake2s-16 | 0xb242 | 32 | | blake2s-24 | 0xb243 | 33 | | ... | | 34 | | blake2s-248 | 0xb25f | 35 | | blake2s-256 | 0xb260 | 36 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in multihashes.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kyle Drake 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This project is no longer maintained and has been archived.** 2 | 3 | # ruby-multihash 4 | 5 | [![](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)](https://github.com/multiformats/multiformats) 6 | [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipfs) 7 | [![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 8 | [![Travis CI](https://img.shields.io/travis/multiformats/ruby-multihash.svg?style=flat-square&branch=master)](https://travis-ci.org/multiformats/ruby-multihash) 9 | [![codecov.io](https://img.shields.io/codecov/c/github/multiformats/ruby-multihash.svg?style=flat-square&branch=master)](https://codecov.io/github/multiformats/ruby-multihash?branch=master) 10 | 11 | > A simple [Multihash](https://github.com/multiformats/multihash) implementation for ruby. 12 | 13 | A multihash is a digest with an embedded hash function code (and length) (['cause you never know](https://twitter.com/matthew_d_green/status/597409850381836288)). It was developed primarily for use with [IPFS](https://github.com/ipfs/ipfs), but is not specific to it. 14 | 15 | ## Install 16 | 17 | Add this line to your application's Gemfile: 18 | 19 | ```ruby 20 | gem 'multihashes' 21 | ``` 22 | 23 | And then execute: 24 | 25 | $ bundle 26 | 27 | Or install it yourself as: 28 | 29 | $ gem install multihashes 30 | 31 | ## Usage 32 | 33 | This is a low-level library. Bring your own digest. A binary digest goes in, a binary digest goes out. To compute a sha256 multihash that would work nicely with [IPFS](https://github.com/ipfs/ipfs): 34 | 35 | ```ruby 36 | require 'multihashes' 37 | require 'digest' 38 | 39 | digest = Digest::SHA256.digest 'Dade Murphy will never figure this one out' 40 | multihash_binary_string = Multihashes.encode digest, 'sha2-256' 41 | 42 | multihash_binary_string.unpack('H*').first # hex: "1220142711d38ca7a33c521841..." 43 | 44 | out = Multihashes.decode multihash_binary_string 45 | # => {:code=>18, :hash_function=>"sha2-256", :length=>32, :digest=>"\x14'\x11\xD3\x8C\xA7\xA3 :test 11 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "multihashes" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /lib/multihashes.rb: -------------------------------------------------------------------------------- 1 | require 'multihashes/version' 2 | require 'multicodecs' 3 | 4 | module Multihashes 5 | class HashFunctionNotImplemented < StandardError; end 6 | class DigestLengthError < StandardError; end 7 | 8 | # https://github.com/multiformats/multicodec/blob/master/table.csv 9 | TABLE = Multicodecs.where(tag: 'multihash') 10 | .map { |codec| [codec.code, codec.name] } 11 | .to_h 12 | 13 | def self.encode(digest, hash_function) 14 | length = digest.bytesize 15 | key = TABLE.key hash_function 16 | raise HashFunctionNotImplemented, 'unknown hash function code' if key.nil? 17 | [TABLE.key(hash_function), length, digest].pack("CCa#{length}") 18 | end 19 | 20 | def self.decode(multihash) 21 | integer, length, digest = multihash.unpack('CCa*') 22 | 23 | if length != digest.bytesize 24 | raise DigestLengthError, 'digest did not match expected multihash length' 25 | end 26 | 27 | {code: integer, hash_function: TABLE[integer], length: length, digest: digest} 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/multihashes/version.rb: -------------------------------------------------------------------------------- 1 | module Multihashes 2 | VERSION = "0.2.0" 3 | end 4 | -------------------------------------------------------------------------------- /multihashes.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'multihashes/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "multihashes" 8 | spec.version = Multihashes::VERSION 9 | spec.authors = ["Kyle Drake"] 10 | spec.email = ["kyle@kyledrake.net"] 11 | 12 | spec.summary = %q{A simple multihash (https://github.com/jbenet/multihash) implementation for ruby.} 13 | spec.description = %q{A simple, low-level multihash (https://github.com/jbenet/multihash) implementation for ruby.} 14 | spec.homepage = "https://github.com/neocities/ruby-multihashes" 15 | spec.license = "MIT" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 18 | spec.bindir = "exe" 19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 20 | spec.require_paths = ["lib"] 21 | 22 | spec.add_development_dependency "bundler", "~> 2" 23 | spec.add_development_dependency "rake", "~> 12.3" 24 | spec.add_development_dependency "minitest" 25 | 26 | spec.add_dependency "multicodecs", ">= 0.2.0", "< 1" 27 | end 28 | -------------------------------------------------------------------------------- /test/multihashes_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'digest' 3 | 4 | class MultihashesTest < Minitest::Test 5 | def setup 6 | @sha1_digest = Digest::SHA1.digest 'god' 7 | @sha256_digest = Digest::SHA256.digest 'secret' 8 | end 9 | 10 | def test_encodes_from_digest 11 | out = Multihashes.encode @sha1_digest, 'sha1' 12 | assert_equal "\x11\x14#{@sha1_digest}".b, out 13 | end 14 | 15 | def test_encodes_from_digest_all_zero 16 | out = Multihashes.encode ("\x00".b * 20), 'sha1' 17 | assert_equal "\x11\x14#{"\x00".b * 20}".b, out 18 | end 19 | 20 | def test_decodes_from_multihash 21 | out = Multihashes.decode Multihashes.encode(@sha1_digest, 'sha1') 22 | assert_equal 0x11, out[:code] 23 | assert_equal 'sha1', out[:hash_function] 24 | assert_equal 20, out[:length] 25 | assert_equal @sha1_digest, out[:digest] 26 | end 27 | 28 | def test_decodes_from_multihash_all_zero 29 | out = Multihashes.decode "\x12\x20#{"\x00".b * 32}".b 30 | assert_equal 0x12, out[:code] 31 | assert_equal 'sha2-256', out[:hash_function] 32 | assert_equal 32, out[:length] 33 | assert_equal("\x00".b * 32, out[:digest]) 34 | end 35 | 36 | def test_sha256_encode_decode 37 | out = Multihashes.decode Multihashes.encode(@sha256_digest, 'sha2-256') 38 | assert_equal 0x12, out[:code] 39 | assert_equal 'sha2-256', out[:hash_function] 40 | assert_equal 32, out[:length] 41 | assert_equal @sha256_digest, out[:digest] 42 | end 43 | 44 | def test_missing_hash_function 45 | assert_raises(Multihashes::HashFunctionNotImplemented) { 46 | Multihashes.encode(@sha256_digest, 'derp') 47 | } 48 | end 49 | 50 | def test_incorrect_length 51 | assert_raises(Multihashes::DigestLengthError) { 52 | out = Multihashes.encode(@sha256_digest, 'sha2-256') 53 | out = out + 'a' 54 | Multihashes.decode(@sha256_digest) 55 | } 56 | end 57 | 58 | def test_breaking_table_change_from_0_1_0 59 | old_hardcoded_mapping_yay = { 60 | 0xd5 => 'md5', 61 | 0x11 => 'sha1', 62 | 0x12 => 'sha2-256', 63 | 0x13 => 'sha2-512', 64 | # 0x14 => 'sha3', # this was renamed from sha3 65 | # 0x40 => 'blake2b', 66 | # 0x41 => 'blake2s' 67 | } 68 | 69 | old_hardcoded_mapping_yay.each do |code, name| 70 | assert_equal Multihashes::TABLE[code], name 71 | end 72 | 73 | old_hardcoded_mapping_nay = { 74 | 0x14 => 'sha3', # this was renamed from sha3 75 | 0x40 => 'blake2b', 76 | 0x41 => 'blake2s' 77 | } 78 | 79 | old_hardcoded_mapping_nay.each do |code, name| 80 | refute_equal Multihashes::TABLE[code], name 81 | end 82 | 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'multihashes' 3 | 4 | require 'minitest/autorun' 5 | --------------------------------------------------------------------------------