@@ -19,6 +19,8 @@ defmodule Multihash do
19
19
20
20
@ type on_decode :: { :ok , t } | error
21
21
22
+ @ type integer_default :: integer | :default
23
+
22
24
@ hash_info [
23
25
sha1: [ code: 0x11 , length: 20 ] ,
24
26
sha2_256: [ code: 0x12 , length: 32 ] ,
@@ -41,7 +43,8 @@ defmodule Multihash do
41
43
# Error strings
42
44
@ error_invalid_digest_hash "Invalid digest or hash"
43
45
@ error_invalid_multihash "Invalid multihash"
44
- @ error_invalid_length "Invalid length of provided hash function"
46
+ @ error_invalid_length "Invalid length"
47
+ @ error_invalid_trunc_length "Invalid truncation length is longer than digest"
45
48
@ error_invalid_size "Invalid size"
46
49
@ error_invalid_hash_function "Invalid hash function"
47
50
@ error_invalid_hash_code "Invalid hash code"
@@ -65,31 +68,43 @@ defmodule Multihash do
65
68
iex> Multihash.encode(0x20, :crypto.hash(:sha, "Hello"))
66
69
{:error, "Invalid hash code"}
67
70
71
+ It's possible to [truncate a digest](https://github.com/jbenet/multihash/issues/1#issuecomment-91783612)
72
+ by passing an optional `length` parameter. Passing a `length` longer than the default digest length
73
+ of the hash function will return an error.
74
+
75
+ iex> Multihash.encode(:sha1, :crypto.hash(:sha, "Hello"), 10)
76
+ {:ok, <<17, 10, 247, 255, 158, 139, 123, 178, 224, 155, 112, 147>>}
77
+
78
+ iex> Multihash.encode(:sha1, :crypto.hash(:sha, "Hello"), 30)
79
+ {:error, "Invalid truncation length is longer than digest"}
80
+
68
81
"""
69
- @ spec encode ( integer , binary ) :: on_encode
70
- def encode ( hash_code , digest ) when is_number ( hash_code ) and is_binary ( digest ) , do:
71
- Monad.Error . p ( { :ok , << hash_code >> } |> encode ( digest ) )
82
+ def encode ( hash_code , digest , length \\ :default )
72
83
73
- @ spec encode ( binary , binary ) :: on_encode
74
- def encode ( << _hash_code >> = hash_code , digest ) when is_binary ( digest ) do
84
+ @ spec encode ( integer , binary , integer_default ) :: on_encode
85
+ def encode ( hash_code , digest , length ) when is_number ( hash_code ) and is_binary ( digest ) , do:
86
+ Monad.Error . p ( { :ok , << hash_code >> } |> encode ( digest , length ) )
87
+
88
+ @ spec encode ( binary , binary , integer_default ) :: on_encode
89
+ def encode ( << _hash_code >> = hash_code , digest , length ) when is_binary ( digest ) do
75
90
Monad.Error . p do
76
91
{ :ok , hash_code }
77
92
|> get_hash_function
78
- |> encode ( digest )
93
+ |> encode ( digest , length )
79
94
end
80
95
end
81
96
82
- @ spec encode ( hash_type , binary ) :: on_encode
83
- def encode ( hash_func , digest ) when is_atom ( hash_func ) and is_binary ( digest ) do
97
+ @ spec encode ( hash_type , binary , integer_default ) :: on_encode
98
+ def encode ( hash_func , digest , length ) when is_atom ( hash_func ) and is_binary ( digest ) do
84
99
Monad.Error . p do
85
100
{ :ok , hash_func }
86
101
|> get_hash_info
87
102
|> check_digest_length ( digest )
88
- |> encode_internal ( digest )
103
+ |> encode_internal ( digest , length )
89
104
end
90
105
end
91
106
92
- def encode ( _digest , _hash_code ) , do: { :error , @ error_invalid_digest_hash }
107
+ def encode ( _digest , _hash_code , _length ) , do: { :error , @ error_invalid_digest_hash }
93
108
94
109
@ doc ~S"""
95
110
Decode the provided multi hash to %Multihash{code: , name: , length: , digest: }
@@ -99,6 +114,10 @@ defmodule Multihash do
99
114
iex> Multihash.decode(<<17, 20, 247, 255, 158, 139, 123, 178, 224, 155, 112, 147, 90, 93, 120, 94, 12, 197, 217, 208, 171, 240>>)
100
115
{:ok, %Multihash{name: "sha1", code: 17, length: 20, digest: <<247, 255, 158, 139, 123, 178, 224, 155, 112, 147, 90, 93, 120, 94, 12, 197, 217, 208, 171, 240>>}}
101
116
117
+ iex> Multihash.decode(<<17, 10, 247, 255, 158, 139, 123, 178, 224, 155, 112, 147>>)
118
+ {:ok, %Multihash{name: "sha1", code: 17, length: 10, digest: <<247, 255, 158, 139, 123, 178, 224, 155, 112, 147>>}}
119
+
120
+
102
121
Invalid multihash will result in errors
103
122
104
123
iex> Multihash.decode(<<17, 20, 247, 255, 158, 139, 123, 178, 224, 155, 112, 147, 90, 93, 120, 94, 12, 197, 217, 208, 171>>)
@@ -121,8 +140,8 @@ defmodule Multihash do
121
140
|> get_hash_function
122
141
|> get_hash_info
123
142
|> check_length ( length )
124
- |> check_digest_length ( digest )
125
- |> decode_internal ( digest )
143
+ |> check_truncated_digest_length ( digest , length )
144
+ |> decode_internal ( digest , length )
126
145
end
127
146
end
128
147
@@ -173,16 +192,20 @@ defmodule Multihash do
173
192
defp is_valid_hash_code ( { :error , _ } ) , do: false
174
193
175
194
@ doc """
176
- Encode the digest to multihash
195
+ Encode the ` digest` to multihash, truncating it to the `trunc_length` if necessary
177
196
"""
178
- defp encode_internal ( [ code: code , length: length ] , << digest :: binary >> ) do
179
- Monad.Error . return << code , length >> <> digest
197
+ defp encode_internal ( [ code: code , length: length ] , << digest :: binary >> , trunc_length ) do
198
+ case trunc_length do
199
+ :default -> Monad.Error . return << code , length >> <> digest
200
+ l when 0 < l and l <= length -> Monad.Error . return << code , l >> <> Kernel . binary_part ( digest , 0 , l )
201
+ _ -> Monad.Error . fail @ error_invalid_trunc_length
202
+ end
180
203
end
181
204
182
205
@ doc """
183
206
Decode the multihash to %Multihash{name, code, length, digest} structure
184
207
"""
185
- defp decode_internal ( [ code: code , length: length ] , << digest :: binary >> ) do
208
+ defp decode_internal ( [ code: code , length: _default_length ] , << digest :: binary >> , length ) do
186
209
{ :ok , name } = get_hash_function << code >>
187
210
Monad.Error . return % Multihash {
188
211
name: to_string ( name ) |> String . replace ( "_" , "-" ) ,
@@ -199,25 +222,35 @@ defmodule Multihash do
199
222
defp check_hash_code ( false , _ ) , do: Monad.Error . fail @ error_invalid_hash_code
200
223
201
224
@ doc """
202
- Checks that the `original_lenght` is same as the expected `length ` of the hash function
225
+ Checks if the incoming multihash has a `length` field equal or lower than the `default_length ` of the hash function
203
226
"""
204
- defp check_length ( [ code: _code , length: length ] = hash_info , original_length ) do
227
+ defp check_length ( [ code: _code , length: default_length ] = hash_info , original_length ) do
205
228
case original_length do
206
- ^ length -> Monad.Error . return hash_info
229
+ l when 0 < l and l <= default_length -> Monad.Error . return hash_info
207
230
_ -> Monad.Error . fail @ error_invalid_length
208
231
end
209
232
end
210
233
211
234
@ doc """
212
- Checks if the length of the `digest` is same as the expected `length` of the has function
235
+ Checks if the incoming multihash has a `length` field fitting the actual size of the possibly truncated `digest`
213
236
"""
214
- defp check_digest_length ( [ code: _code , length: length ] = hash_info , digest ) when is_binary ( digest ) do
237
+ defp check_truncated_digest_length ( [ code: _code , length: _default_length ] = hash_info , digest , length ) when is_binary ( digest ) do
215
238
case byte_size ( digest ) do
216
239
^ length -> Monad.Error . return hash_info
217
240
_ -> Monad.Error . fail @ error_invalid_size
218
241
end
219
242
end
220
243
244
+ @ doc """
245
+ Checks if the length of the `digest` is same as the expected `default_length` of the hash function while encoding
246
+ """
247
+ defp check_digest_length ( [ code: _code , length: default_length ] = hash_info , digest ) when is_binary ( digest ) do
248
+ case byte_size ( digest ) do
249
+ ^ default_length -> Monad.Error . return hash_info
250
+ _ -> Monad.Error . fail @ error_invalid_size
251
+ end
252
+ end
253
+
221
254
@ doc """
222
255
Get hash info from the @hash_info keyword map based on the provided `hash_func`
223
256
"""
0 commit comments