/
HashingAlgorithm.cs
270 lines (250 loc) · 11.7 KB
/
HashingAlgorithm.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
using System;
using System.Collections.Generic;
using System.Text;
using Ipfs.Cryptography;
using System.Security.Cryptography;
using BC = Org.BouncyCastle.Crypto.Digests;
namespace Ipfs.Registry
{
/// <summary>
/// Metadata and implemetations of a IPFS hashing algorithms.
/// </summary>
/// <remarks>
/// IPFS assigns a unique <see cref="Name"/> and <see cref="Code"/> to a hashing algorithm.
/// See <see href="https://github.com/multiformats/multicodec/blob/master/table.csv">hashtable.csv</see>
/// for the currently defined hashing algorithms.
/// <para>
/// These algorithms are implemented:
/// <list type="bullet">
/// <item><description>blake2b-160, blake2b-256 blake2b-384 and blake2b-512</description></item>
/// <item><description>blake2s-128, blake2s-160, blake2s-224 a nd blake2s-256</description></item>
/// <item><description>keccak-224, keccak-256, keccak-384 and keccak-512</description></item>
/// <item><description>md4 and md5</description></item>
/// <item><description>sha1</description></item>
/// <item><description>sha2-256, sha2-512 and dbl-sha2-256</description></item>
/// <item><description>sha3-224, sha3-256, sha3-384 and sha3-512</description></item>
/// <item><description>shake-128 and shake-256</description></item>
/// </list>
/// </para>
/// <para>
/// The <c>identity</c> hash is also implemented; which just returns the input bytes.
/// This is used to inline a small amount of data into a <see cref="Cid"/>.
/// </para>
/// <para>
/// Use <see cref="Register(string, int, int, Func{HashAlgorithm})"/> to add a new
/// hashing algorithm.
/// </para>
/// </remarks>
public class HashingAlgorithm
{
internal static Dictionary<string, HashingAlgorithm> Names = new Dictionary<string, HashingAlgorithm>();
internal static Dictionary<int, HashingAlgorithm> Codes = new Dictionary<int, HashingAlgorithm>();
/// <summary>
/// Register the standard hash algorithms for IPFS.
/// </summary>
/// <seealso href="https://github.com/multiformats/multicodec/blob/master/table.csv"/>
static HashingAlgorithm()
{
Register("sha1", 0x11, 20, () => SHA1.Create());
Register("sha2-256", 0x12, 32, () => SHA256.Create());
Register("sha2-512", 0x13, 64, () => SHA512.Create());
Register("dbl-sha2-256", 0x56, 32, () => new DoubleSha256());
Register("keccak-224", 0x1A, 224 / 8, () => new KeccakManaged(224));
Register("keccak-256", 0x1B, 256 / 8, () => new KeccakManaged(256));
Register("keccak-384", 0x1C, 384 / 8, () => new KeccakManaged(384));
Register("keccak-512", 0x1D, 512 / 8, () => new KeccakManaged(512));
Register("sha3-224", 0x17, 224 / 8, () => new BouncyDigest(new BC.Sha3Digest(224)));
Register("sha3-256", 0x16, 256 / 8, () => new BouncyDigest(new BC.Sha3Digest(256)));
Register("sha3-384", 0x15, 384 / 8, () => new BouncyDigest(new BC.Sha3Digest(384)));
Register("sha3-512", 0x14, 512 / 8, () => new BouncyDigest(new BC.Sha3Digest(512)));
Register("shake-128", 0x18, 128 / 8, () => new BouncyDigest(new BC.ShakeDigest(128)));
Register("shake-256", 0x19, 256 / 8, () => new BouncyDigest(new BC.ShakeDigest(256)));
Register("blake2b-160", 0xb214, 160 / 8, () => new BouncyDigest(new BC.Blake2bDigest(160)));
Register("blake2b-256", 0xb220, 256 / 8, () => new BouncyDigest(new BC.Blake2bDigest(256)));
Register("blake2b-384", 0xb230, 384 / 8, () => new BouncyDigest(new BC.Blake2bDigest(384)));
Register("blake2b-512", 0xb240, 512 / 8, () => new BouncyDigest(new BC.Blake2bDigest(512)));
Register("blake2s-128", 0xb250, 128 / 8, () => new BouncyDigest(new BC.Blake2sDigest(128)));
Register("blake2s-160", 0xb254, 160 / 8, () => new BouncyDigest(new BC.Blake2sDigest(160)));
Register("blake2s-224", 0xb25c, 224 / 8, () => new BouncyDigest(new BC.Blake2sDigest(224)));
Register("blake2s-256", 0xb260, 256 / 8, () => new BouncyDigest(new BC.Blake2sDigest(256)));
Register("md4", 0xd4, 128 / 8, () => new BouncyDigest(new BC.MD4Digest()));
Register("md5", 0xd5, 128 / 8, () => MD5.Create());
Register("identity", 0, 0, () => new IdentityHash());
RegisterAlias("id", "identity");
}
/// <summary>
/// The name of the algorithm.
/// </summary>
/// <value>
/// A unique name.
/// </value>
public string Name { get; private set; }
/// <summary>
/// The IPFS number assigned to the hashing algorithm.
/// </summary>
/// <value>
/// Valid hash codes at <see href="https://github.com/multiformats/multicodec/blob/master/table.csv">hashtable.csv</see>.
/// </value>
public int Code { get; private set; }
/// <summary>
/// The size, in bytes, of the digest value.
/// </summary>
/// <value>
/// The digest value size in bytes. Zero indicates that the digest
/// is non fixed.
/// </value>
public int DigestSize { get; private set; }
/// <summary>
/// Returns a cryptographic hash algorithm that can compute
/// a hash (digest).
/// </summary>
public Func<HashAlgorithm> Hasher { get; private set; }
/// <summary>
/// Use <see cref="Register"/> to create a new instance of a <see cref="HashingAlgorithm"/>.
/// </summary>
HashingAlgorithm()
{
}
/// <summary>
/// The <see cref="Name"/> of the hashing algorithm.
/// </summary>
public override string ToString()
{
return Name;
}
/// <summary>
/// Register a new IPFS hashing algorithm.
/// </summary>
/// <param name="name">
/// The name of the algorithm.
/// </param>
/// <param name="code">
/// The IPFS number assigned to the hashing algorithm.
/// </param>
/// <param name="digestSize">
/// The size, in bytes, of the digest value.
/// </param>
/// <param name="hasher">
/// A <c>Func</c> that returns a <see cref="HashAlgorithm"/>. If not specified, then a <c>Func</c> is created to
/// throw a <see cref="NotImplementedException"/>.
/// </param>
/// <returns>
/// A new <see cref="HashingAlgorithm"/>.
/// </returns>
public static HashingAlgorithm Register(string name, int code, int digestSize, Func<HashAlgorithm> hasher = null)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException("name");
if (Names.ContainsKey(name))
throw new ArgumentException(string.Format("The IPFS hashing algorithm '{0}' is already defined.", name));
if (Codes.ContainsKey(code))
throw new ArgumentException(string.Format("The IPFS hashing algorithm code 0x{0:x2} is already defined.", code));
if (hasher == null)
hasher = () => { throw new NotImplementedException(string.Format("The IPFS hashing algorithm '{0}' is not implemented.", name)); };
var a = new HashingAlgorithm
{
Name = name,
Code = code,
DigestSize = digestSize,
Hasher = hasher
};
Names[name] = a;
Codes[code] = a;
return a;
}
/// <summary>
/// Register an alias for an IPFS hashing algorithm.
/// </summary>
/// <param name="alias">
/// The alias name.
/// </param>
/// <param name="name">
/// The name of the existing algorithm.
/// </param>
/// <returns>
/// A new <see cref="HashingAlgorithm"/>.
/// </returns>
public static HashingAlgorithm RegisterAlias(string alias, string name)
{
if (string.IsNullOrWhiteSpace(alias))
throw new ArgumentNullException("alias");
if (Names.ContainsKey(alias))
throw new ArgumentException(string.Format("The IPFS hashing algorithm '{0}' is already defined and cannot be used as an alias.", alias));
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException("name");
if (!Names.TryGetValue(name, out HashingAlgorithm existing))
throw new ArgumentException(string.Format("The IPFS hashing algorithm '{0}' is not defined.", name));
var a = new HashingAlgorithm
{
Name = name,
Code = existing.Code,
DigestSize = existing.DigestSize,
Hasher = existing.Hasher
};
Names[alias] = a;
return a;
}
/// <summary>
/// Remove an IPFS hashing algorithm from the registry.
/// </summary>
/// <param name="algorithm">
/// The <see cref="HashingAlgorithm"/> to remove.
/// </param>
public static void Deregister(HashingAlgorithm algorithm)
{
Names.Remove(algorithm.Name);
Codes.Remove(algorithm.Code);
}
/// <summary>
/// A sequence consisting of all <see cref="HashingAlgorithm">hashing algorithms</see>.
/// </summary>
/// <value>
/// The currently registered hashing algorithms.
/// </value>
public static IEnumerable<HashingAlgorithm> All
{
get { return Names.Values; }
}
/// <summary>
/// Gets the <see cref="HashAlgorithm"/> with the specified IPFS multi-hash name.
/// </summary>
/// <param name="name">
/// The name of a hashing algorithm, see <see href="https://github.com/multiformats/multicodec/blob/master/table.csv"/>
/// for IPFS defined names.
/// </param>
/// <returns>
/// The hashing implementation associated with the <paramref name="name"/>.
/// </returns>
/// <exception cref="KeyNotFoundException">
/// When <paramref name="name"/> is not registered.
/// </exception>
public static HashAlgorithm GetAlgorithm(string name)
{
return GetAlgorithmMetadata(name).Hasher();
}
/// <summary>
/// Gets the metadata with the specified IPFS multi-hash name.
/// </summary>
/// <param name="name">
/// The name of a hashing algorithm, see <see href="https://github.com/multiformats/multicodec/blob/master/table.csv"/>
/// for IPFS defined names.
/// </param>
/// <returns>
/// The metadata associated with the hashing <paramref name="name"/>.
/// </returns>
/// <exception cref="KeyNotFoundException">
/// When <paramref name="name"/> is not registered.
/// </exception>
public static HashingAlgorithm GetAlgorithmMetadata(string name)
{
try
{
return HashingAlgorithm.Names[name];
}
catch (KeyNotFoundException)
{
throw new KeyNotFoundException($"Hash algorithm '{name}' is not registered.");
}
}
}
}