/
Hashcash.cs
89 lines (85 loc) · 4 KB
/
Hashcash.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
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
namespace Libplanet
{
/// <summary>
/// This contains a set of functions that implements
/// <a href="https://en.wikipedia.org/wiki/Hashcash">Hashcash</a>,
/// a <a href="https://en.wikipedia.org/wiki/Proof-of-work_system"
/// >proof-of-work system</a>.
/// </summary>
public static class Hashcash
{
/// <summary>
/// A delegate to determine a consistent <see cref="byte"/>s
/// representation derived from a given <paramref name="nonce"/>.
/// <para>Since it is called multiple times with different
/// <paramref name="nonce"/>s for
/// <a href="https://en.wikipedia.org/wiki/Proof-of-work_system"
/// >proof-of-work system</a>, the total time an implementation elapses
/// should not vary for different <paramref name="nonce"/>s.</para>
/// </summary>
/// <param name="nonce">An arbitrary nonce for an attempt, provided by
/// <see cref="Hashcash.Answer(Stamp, HashAlgorithmType, long, int, CancellationToken)"/>
/// method.
/// </param>
/// <returns>Chunked <see cref="byte"/>s determined from the given <paramref name="nonce"/>.
/// It should return consistently equivalent bytes for equivalent <paramref name="nonce"/>
/// values. The way how bytes are split into chunks can be flexible; regardless of the way,
/// they are concatenated into a single byte array.</returns>
/// <seealso cref="Hashcash.Answer(Stamp, HashAlgorithmType, long, int, CancellationToken)"
/// />
/// <seealso cref="Nonce"/>
public delegate IEnumerable<byte[]> Stamp(Nonce nonce);
/// <summary>
/// Finds a <see cref="Nonce"/> that satisfies the given
/// <paramref name="difficulty"/>. This process is so-called
/// “<a
/// href="https://en.wikipedia.org/wiki/Cryptocurrency#Mining"
/// >mining</a>”.
/// </summary>
/// <param name="stamp">A callback to get a “stamp”
/// which is a <see cref="byte"/> array determined from a given
/// <see cref="Nonce"/> value.</param>
/// <param name="hashAlgorithmType">The hash algorithm to use.</param>
/// <param name="difficulty">A number to calculate the target number
/// for which the returned answer should be less than.</param>
/// <param name="seed">The seed number for random generator.</param>
/// <param name="cancellationToken">
/// A cancellation token used to propagate notification that this
/// operation should be canceled.
/// </param>
/// <returns>A pair of <see cref="Nonce"/> value which satisfies the
/// given <paramref name="difficulty"/>, and the succeeded hash
/// digest.</returns>
/// <exception cref="OperationCanceledException">Thrown when the specified
/// <paramref name="cancellationToken"/> received a cancellation request.</exception>
/// <seealso cref="Stamp"/>
public static (Nonce Nonce, ImmutableArray<byte> Digest) Answer(
Stamp stamp,
HashAlgorithmType hashAlgorithmType,
long difficulty,
int seed,
CancellationToken cancellationToken = default
)
{
var nonceBytes = new byte[10];
var random = new Random(seed);
while (!cancellationToken.IsCancellationRequested)
{
random.NextBytes(nonceBytes);
var nonce = new Nonce(nonceBytes);
IEnumerable<byte[]> chunks = stamp(nonce);
byte[] digest = hashAlgorithmType.Digest(chunks);
if (ByteUtil.Satisfies(digest, difficulty))
{
return (nonce, ImmutableArray.Create(digest));
}
}
throw new OperationCanceledException(cancellationToken);
}
}
}