@@ -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: }
@@ -102,6 +117,10 @@ defmodule Multihash do
102
117
iex> Multihash.decode(<<17, 10, 247, 255, 158, 139, 123, 178, 224, 155, 112, 147, 90, 93, 120, 94, 12, 197, 217, 208, 171, 240>>)
103
118
{:ok, %Multihash{name: :sha1, code: 17, length: 10, digest: <<247, 255, 158, 139, 123, 178, 224, 155, 112, 147>>}}
104
119
120
+ iex> Multihash.decode(<<17, 10, 247, 255, 158, 139, 123, 178, 224, 155, 112, 147>>)
121
+ {:ok, %Multihash{name: "sha1", code: 17, length: 10, digest: <<247, 255, 158, 139, 123, 178, 224, 155, 112, 147>>}}
122
+
123
+
105
124
Invalid multihash will result in errors
106
125
107
126
iex> Multihash.decode(<<17, 20, 247, 255, 158, 139, 123, 178, 224, 155, 112, 147, 90, 93, 120, 94, 12, 197, 217, 208, 171>>)
@@ -124,8 +143,8 @@ defmodule Multihash do
124
143
|> get_hash_function
125
144
|> get_hash_info
126
145
|> check_length ( length )
127
- |> check_digest_length ( digest )
128
- |> decode_internal ( digest )
146
+ |> check_truncated_digest_length ( digest , length )
147
+ |> decode_internal ( digest , length )
129
148
end
130
149
end
131
150
@@ -176,16 +195,20 @@ defmodule Multihash do
176
195
defp is_valid_hash_code ( { :error , _ } ) , do: false
177
196
178
197
@ doc """
179
- Encode the digest to multihash
198
+ Encode the ` digest` to multihash, truncating it to the `trunc_length` if necessary
180
199
"""
181
- defp encode_internal ( [ code: code , length: length ] , << digest :: binary >> ) do
182
- Monad.Error . return << code , length >> <> digest
200
+ defp encode_internal ( [ code: code , length: length ] , << digest :: binary >> , trunc_length ) do
201
+ case trunc_length do
202
+ :default -> Monad.Error . return << code , length >> <> digest
203
+ l when 0 < l and l <= length -> Monad.Error . return << code , l >> <> Kernel . binary_part ( digest , 0 , l )
204
+ _ -> Monad.Error . fail @ error_invalid_trunc_length
205
+ end
183
206
end
184
207
185
208
@ doc """
186
209
Decode the multihash to %Multihash{name, code, length, digest} structure
187
210
"""
188
- defp decode_internal ( [ code: code , length: length ] , << digest :: binary >> ) do
211
+ defp decode_internal ( [ code: code , length: _default_length ] , << digest :: binary >> , length ) do
189
212
{ :ok , name } = get_hash_function << code >>
190
213
Monad.Error . return % Multihash {
191
214
name: to_string ( name ) |> String . replace ( "_" , "-" ) |> String . to_atom ,
@@ -202,25 +225,35 @@ defmodule Multihash do
202
225
defp check_hash_code ( false , _ ) , do: Monad.Error . fail @ error_invalid_hash_code
203
226
204
227
@ doc """
205
- Checks that the `original_lenght` is same as the expected `length ` of the hash function
228
+ Checks if the incoming multihash has a `length` field equal or lower than the `default_length ` of the hash function
206
229
"""
207
- defp check_length ( [ code: _code , length: length ] = hash_info , original_length ) do
230
+ defp check_length ( [ code: _code , length: default_length ] = hash_info , original_length ) do
208
231
case original_length do
209
- ^ length -> Monad.Error . return hash_info
232
+ l when 0 < l and l <= default_length -> Monad.Error . return hash_info
210
233
_ -> Monad.Error . fail @ error_invalid_length
211
234
end
212
235
end
213
236
214
237
@ doc """
215
- Checks if the length of the `digest` is same as the expected `length` of the has function
238
+ Checks if the incoming multihash has a `length` field fitting the actual size of the possibly truncated `digest`
216
239
"""
217
- defp check_digest_length ( [ code: _code , length: length ] = hash_info , digest ) when is_binary ( digest ) do
240
+ defp check_truncated_digest_length ( [ code: _code , length: _default_length ] = hash_info , digest , length ) when is_binary ( digest ) do
218
241
case byte_size ( digest ) do
219
242
^ length -> Monad.Error . return hash_info
220
243
_ -> Monad.Error . fail @ error_invalid_size
221
244
end
222
245
end
223
246
247
+ @ doc """
248
+ Checks if the length of the `digest` is same as the expected `default_length` of the hash function while encoding
249
+ """
250
+ defp check_digest_length ( [ code: _code , length: default_length ] = hash_info , digest ) when is_binary ( digest ) do
251
+ case byte_size ( digest ) do
252
+ ^ default_length -> Monad.Error . return hash_info
253
+ _ -> Monad.Error . fail @ error_invalid_size
254
+ end
255
+ end
256
+
224
257
@ doc """
225
258
Get hash info from the @hash_info keyword map based on the provided `hash_func`
226
259
"""
0 commit comments