Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add VRF, and use VRF for proposer sortition, and random on AEV #3895

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
df1c498
feat: Add ConsensusCryptoBackend
OnedgeLee Mar 19, 2024
9f092a2
feat: Introduce Proof
OnedgeLee Mar 20, 2024
499d0e6
feat: Introduce Lot
OnedgeLee Mar 20, 2024
3885f08
chore: Clean with linting
OnedgeLee Mar 20, 2024
98861ee
doc: Update changelog
OnedgeLee Mar 22, 2024
a02f0cb
feat: Update LotMetadata.LastProof to be nullable
OnedgeLee Mar 22, 2024
717375b
feat: Force to use compressed ECPoint form on Proof hashing, Add Proo…
OnedgeLee Mar 22, 2024
5e40ddb
fix: Fix Proof.Equals()
OnedgeLee Mar 22, 2024
6f53c34
feat: Throw explicit InvalidProofException for invalid proof
OnedgeLee Mar 22, 2024
520c043
test: Add Proof test
OnedgeLee Mar 22, 2024
3b72b6e
test: Add Key pair test
OnedgeLee Mar 22, 2024
36ea133
feat: Move lazy calculation of Proof.Hash to constructor
OnedgeLee Mar 22, 2024
7b7d40e
feat: Remove Lot.Verify()
OnedgeLee Mar 22, 2024
8c559db
fix: Fix LotMetadata null key Bencodex constructor
OnedgeLee Mar 22, 2024
b170752
test: Add Lot tests, Fix Proof tests
OnedgeLee Mar 22, 2024
5eaef46
feat: Add Proof on the Block
OnedgeLee Mar 21, 2024
ea4a8bb
test: Fix test build
OnedgeLee Mar 22, 2024
f6abfb5
feat: Require proof on the block after VRF protocol version
OnedgeLee Mar 24, 2024
61e3110
Replace random seed generator with VRF
OnedgeLee Mar 24, 2024
ccfa8a4
test: Update tests
OnedgeLee Mar 24, 2024
3566ea6
doc: Update chagelog
OnedgeLee Mar 24, 2024
448d6f7
fix: Fix bug where BigInteger byte array conversion used right padding
OnedgeLee Mar 25, 2024
d7fa11c
test: Update Libplanet.Net.Tests
OnedgeLee Mar 25, 2024
b1487e7
test: Update ActionEvaluatorTest.Idempotent()
OnedgeLee Mar 25, 2024
9645f54
test: Update and fix tests
OnedgeLee Mar 29, 2024
8843f73
feat: Add PreEvaluationBlockMarshaler
OnedgeLee Apr 4, 2024
595723e
chore: Update PublicKey.VerifyProof to receive IReadOnlyList<byte>
OnedgeLee Apr 8, 2024
76c8956
feat: Add ConsensusInformation
OnedgeLee Jun 28, 2024
4e367ff
feat: Add BlockMarshaler.UnmarshalProof()
OnedgeLee Apr 8, 2024
4b9b6e9
feat: Add PreProposal, PreProposalMetadata
OnedgeLee Apr 8, 2024
c23fbb7
feat: Add ConsensusPreProposalMsg
OnedgeLee Apr 8, 2024
a6c212c
feat: Add PreProposalSet
OnedgeLee Apr 8, 2024
b1d8e14
doc: Update chagelog
OnedgeLee Apr 8, 2024
c590083
feat: Update consensus
OnedgeLee Jun 28, 2024
bbc25b6
fix: Update VRF BPV
OnedgeLee Jul 4, 2024
5ee566c
feat: Prevent proposer from proof manipulation
OnedgeLee Jul 4, 2024
9a77b1c
fix: Linting
OnedgeLee Jul 4, 2024
b0314b3
fix: Build fix
OnedgeLee Jul 4, 2024
15057af
feat: Add Proof on GenesisBlock
OnedgeLee Jul 5, 2024
9ce8cce
test: Fix Libplanet tests
OnedgeLee Jul 5, 2024
b1bd3ba
chore: Clean lot implementations
OnedgeLee Jul 6, 2024
fd808a0
test: Add lot tests
OnedgeLee Jul 6, 2024
291b135
feat: Add comparer operator on Proof
OnedgeLee Jul 11, 2024
1eda971
fix: Fix sortition consensus
OnedgeLee Jul 11, 2024
8586108
test: Update Libplanet.Net.Tests
OnedgeLee Jul 11, 2024
35549a3
fix: Fix and document for consensus
OnedgeLee Jul 11, 2024
db829f8
test: Fix tests
OnedgeLee Jul 11, 2024
2cb42b9
document: Update changelog
OnedgeLee Jul 11, 2024
df21651
chore: Fix typo
OnedgeLee Jul 11, 2024
2e61f33
chore: Minor fixes
OnedgeLee Jul 12, 2024
2a2a683
test: Add LotSetTest
OnedgeLee Jul 12, 2024
e67ac0f
feat: Allow -1 round to ConsensusInformation
OnedgeLee Jul 12, 2024
ef525a2
test: Add sortition tests
OnedgeLee Jul 12, 2024
a9fa924
Build fix after rebasing
OnedgeLee Jul 16, 2024
9f86ce3
test: Fix tests from rebasing
OnedgeLee Jul 17, 2024
0a78dce
refactor: Refactor structs into classes
OnedgeLee Jul 17, 2024
8ea4ed7
test: Fix tests
OnedgeLee Jul 17, 2024
9ff3b75
test: Fix tests about proof on genesis
OnedgeLee Jul 17, 2024
7eb55a2
test: Test fix from rebasing
OnedgeLee Jul 23, 2024
5f5ba47
document: Update changelog
OnedgeLee Jul 23, 2024
93b311e
Lint code
OnedgeLee Jul 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,62 @@ To be released.

### Deprecated APIs

- `ValidatorSet.GetProposer()` has been removed. [[#3895]]

### Backward-incompatible API changes

- `BlockMetadata.CurrentProtocolVersion` has been changed from 9 to 10.
[[#3895]]

### Backward-incompatible network protocol changes

### Backward-incompatible storage format changes

### Added APIs

- Added `IConsensusCryptoBackend` interface, which contains VRF
functionalities which is used on the consensus as a pseudo-random function.
[[#3895]]
- Added `DefaultConsensusCryptoBackend` class as a default implementation of
`IConsensusCryptoBackend`. [[#3895]]
- Added `CryptoConfig.ConsensusCryptoBackend` property as a VRF backend used
on the consensus. [[#3895]]
- Added `PrivateKey.Prove()` method as a proof generation. [[#3895]]
- Added `PublicKey.VerifyProof()` method as a proof verification. [[#3895]]
- Added `Proof` struct as a wrapper structure of proof(pi-bytes) generated by
ECVRF. [[#3895]]
- Added `ConsensusInformation` struct as a base payload to be proved
with private key. [[#3895]]
- Added `IBlockMetadata.Proof` property. [[#3895]]
- Added `Lot` class as a content of message that submits a `Proof`
to be a proposer candidate during `ConsensusStep.Sortition`. [[#3895]]
- Added `DominantLot` class as a content of message that submits a vote for
dominant `Lot` during `ConsensusStep.Sortition`. [[#3895]]`
- Added `ConsensusStep.Sortition`. [[#3895]]
- Added `LotGatherSecond`, `SortitionSecondBase`, `SortitionMultiplier`
to `ContestTimeoutOption`. [[#3895]]
- Added `ConsensusLotMsg` class as a `ConsensusMsg` broadcasted during
`ConsensusStep.Sortition`. [[#3895]]
- Added `ConsensusDominantLotMsg` class as a `ConsensusMsg` broadcasted during
`ConsensusStep.Sortition`. after lot gathering delay. [[#3895]]
- Added `LotSet` class as a `Lot` and `DominantLot` selector. [[#3895]]

### Behavioral changes

- `ActionEvaluator.EvaluateActions()` use `Proof.Seed` as a initial
random seed instead of `PreEvaluationHash`, `signature` combined seed.
[[#3895]]
- Proposer selection is now based on the VRF result. [[#3895]]
- Consensus now starts with `ConsensusStep.Sortition`. [[#3895]]`

### Bug fixes

### Dependencies

### CLI tools

[#3895]: https://github.com/planetarium/libplanet/pull/3895


Version 5.2.0
-------------
Expand Down
8 changes: 5 additions & 3 deletions src/Libplanet.Action/ActionEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private delegate (ITrie, int) StateCommitter(
public IActionLoader ActionLoader => _actionLoader;

/// <summary>
/// Creates a random seed.
/// Creates a legacy random seed.
/// </summary>
/// <param name="preEvaluationHashBytes">The pre-evaluation hash as bytes.
/// </param>
Expand All @@ -72,7 +72,7 @@ private delegate (ITrie, int) StateCommitter(
/// <exception cref="ArgumentException">Thrown when
/// <paramref name="preEvaluationHashBytes"/> is empty.</exception>
[Pure]
public static int GenerateRandomSeed(
public static int GenerateLegacyRandomSeed(
byte[] preEvaluationHashBytes,
byte[] signature,
int actionOffset)
Expand Down Expand Up @@ -228,7 +228,9 @@ IActionContext CreateActionContext(

byte[] preEvaluationHashBytes = block.PreEvaluationHash.ToByteArray();
byte[] signature = tx?.Signature ?? Array.Empty<byte>();
int seed = GenerateRandomSeed(preEvaluationHashBytes, signature, 0);
int seed = block.Proof is { } proof
? proof.Seed
: GenerateLegacyRandomSeed(preEvaluationHashBytes, signature, 0);

IWorld state = previousState;
foreach (IAction action in actions)
Expand Down
10 changes: 10 additions & 0 deletions src/Libplanet.Crypto/CryptoConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Libplanet.Crypto
public static class CryptoConfig
{
private static ICryptoBackend<SHA256>? _cryptoBackend;
private static IConsensusCryptoBackend? _consensusCryptoBackend;

/// <summary>
/// Global cryptography backend to sign and verify messages.
Expand All @@ -17,5 +18,14 @@ public static ICryptoBackend<SHA256> CryptoBackend
get => _cryptoBackend ??= new DefaultCryptoBackend<SHA256>();
set => _cryptoBackend = value;
}

/// <summary>
/// Global consensus cryptography backend to prove and verify messages.
/// </summary>
public static IConsensusCryptoBackend ConsensusCryptoBackend
{
get => _consensusCryptoBackend ??= new DefaultConsensusCryptoBackend();
set => _consensusCryptoBackend = value;
}
}
}
271 changes: 271 additions & 0 deletions src/Libplanet.Crypto/DefaultConsensusCryptoBackend.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Cryptography;
using Libplanet.Common;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using ECPoint = Org.BouncyCastle.Math.EC.ECPoint;

namespace Libplanet.Crypto
{
/// <summary>
/// Default consensus cryptography backend.
/// It implements ECVRF(Elliptic Curve Verifiable Random Function),
/// using RFC9381 as a reference.
/// </summary>
public class DefaultConsensusCryptoBackend : IConsensusCryptoBackend
{
private ECDomainParameters _eCParams;
private int _eCFieldBytesSize;

public DefaultConsensusCryptoBackend()
{
X9ECParameters ps = ECNamedCurveTable.GetByName("secp256k1");
_eCParams = new ECDomainParameters(ps.Curve, ps.G, ps.N, ps.H);
_eCFieldBytesSize = (_eCParams.Curve.FieldSize + 7) >> 3;
SuiteBytes = new byte[] { 0 }.ToImmutableArray();
}

/// <summary>
/// Suite bytes that specifying the ECVRF ciphersuite.
/// Since RFC9381 does not include ECVRF under secp256k1,
/// it cannot be between 0x01 ~ 0x04.
/// To express this implementation does not belong to any of those on
/// RFC9381, left it as 0x00.
/// </summary>
public ImmutableArray<byte> SuiteBytes { get; }

/// <inheritdoc cref="IConsensusCryptoBackend.Prove(byte[], PrivateKey)"/>
public byte[] Prove(byte[] alphaBytes, PrivateKey privateKey)
{
// n : modulus
BigInteger n = _eCParams.N;

// d : private key
BigInteger d = privateKey.KeyParam.D;
var digest = new Sha256Digest();

// k(nonce) generator is deterministic
var kCalculator = new HMacDsaKCalculator(digest);
kCalculator.Init(n, d, alphaBytes);

// k : nonce
BigInteger k = kCalculator.NextK();

// H : message hash point
ECPoint pointH = HashToCurveTai(alphaBytes, privateKey.PublicKey);

// dH : Gamma
ECPoint dPointH = pointH.Multiply(d);

// kG : r
ECPoint kPointG = _eCParams.G.Multiply(k);

// kH
ECPoint kPointH = pointH.Multiply(k);

ECPoint[] points = new ECPoint[] { pointH, dPointH, kPointG, kPointH };

// c = checksum(payload)
BigInteger c = HashPoints(points);

// s = (k + c * d) mod N
BigInteger s = k.Add(c.Multiply(d)).Mod(n);

byte[] gammaBytes = dPointH.GetEncoded(true);
byte[] cBytes = c.ToByteArrayUnsigned();
byte[] sBytes = s.ToByteArrayUnsigned();

byte[] leftPadCBytes = new byte[_eCFieldBytesSize];
byte[] leftPadSBytes = new byte[_eCFieldBytesSize];

Array.Copy(cBytes, 0, leftPadCBytes, _eCFieldBytesSize - cBytes.Length, cBytes.Length);
Array.Copy(sBytes, 0, leftPadSBytes, _eCFieldBytesSize - sBytes.Length, sBytes.Length);

byte[] piBytes = gammaBytes.Concat(leftPadCBytes).Concat(leftPadSBytes).ToArray();

return piBytes;
}

/// <inheritdoc cref="IConsensusCryptoBackend.VerifyProof(byte[], byte[], PublicKey)"/>
public bool VerifyProof(
byte[] alphaBytes, byte[] piBytes, PublicKey publicKey)
{
ECPublicKeyParameters pubKeyParam = publicKey.KeyParam;
ECDomainParameters eCParam = pubKeyParam.Parameters;
ECPoint dPointH;
BigInteger c;
BigInteger s;

try
{
(dPointH, c, s) = DecodeProof(piBytes);
}
catch (Exception)
{
return false;
}

// sG - cdG = (s-cd)G = kG
ECPoint sPointG = eCParam.G.Multiply(s);
ECPoint cdPointG = publicKey.KeyParam.Q.Multiply(c);
ECPoint scdPointG = sPointG.Subtract(cdPointG);

// sH - cdH = (s-cd)H = kH
ECPoint pointH = HashToCurveTai(alphaBytes, publicKey);
ECPoint sPointH = pointH.Multiply(s);
ECPoint cdPointH = dPointH.Multiply(c);
ECPoint scdPointH = sPointH.Subtract(cdPointH);

ECPoint[] points = new ECPoint[] { pointH, dPointH, scdPointG, scdPointH };

// check if checksum of payload is same
if (!c.Equals(HashPoints(points)))
{
return false;
}

return true;
}

/// <inheritdoc cref="IConsensusCryptoBackend.ProofToHash(byte[])"/>
public byte[] ProofToHash(byte[] piBytes)
{
(ECPoint gamma, _, _) = DecodeProof(piBytes);

ECPoint gammaMul = _eCParams.H.Equals(BigInteger.One)
? gamma
: gamma.Multiply(_eCParams.H);

byte[] payload
= SuiteBytes
.Concat(new byte[] { 3 })
.Concat(gammaMul.GetEncoded(true))
.Concat(new byte[] { 0 }).ToArray();

HashDigest<SHA512> betaHash = HashDigest<SHA512>.DeriveFrom(payload);
byte[] betaBytes = betaHash.ToByteArray();

return betaBytes;
}

/// <summary>
/// Maps a <paramref name="alphaBytes"/> to Elliptic curve point
/// with try-and-increment method.
/// </summary>
/// <param name="alphaBytes">A message bytearray.</param>
/// <param name="publicKey"><see cref="PublicKey"/> used for salt.</param>
/// <returns>
/// <see cref="ECPoint"/> generated from <paramref name="alphaBytes"/>.
/// </returns>
private ECPoint HashToCurveTai(byte[] alphaBytes, PublicKey publicKey)
{
int ctr = 0;
while (true)
{
byte[] ctrBytes
= BitConverter.IsLittleEndian
? BitConverter.GetBytes(ctr).Reverse().ToArray()
: BitConverter.GetBytes(ctr);
ctr += 1;
byte[] payload
= SuiteBytes
.Concat(new byte[] { 1 })
.Concat(publicKey.KeyParam.Q.GetEncoded(true))
.Concat(alphaBytes)
.Concat(ctrBytes)
.Concat(new byte[] { 0 }).ToArray();
HashDigest<SHA512> hashed = HashDigest<SHA512>.DeriveFrom(payload);
byte[] encoded = new byte[] { 2 }.Concat(hashed.ToByteArray()
.Take((_eCParams.Curve.FieldSize + 7) >> 3)).ToArray();
try
{
return _eCParams.Curve.DecodePoint(encoded);
}
catch (ArgumentException)
{
}
}

throw new ArgumentException();
}

/// <summary>
/// Maps <paramref name="points"/> to <see cref="BigInteger"/>
/// with SHA-512 hashing.
/// </summary>
/// <param name="points">A message bytearray.</param>
/// <returns>
/// <see cref="BigInteger"/> generated from <paramref name="points"/>.
/// </returns>
private BigInteger HashPoints(ECPoint[] points)
{
byte[] payload = new byte[] { 2 };
foreach (ECPoint point in points)
{
payload = payload.Concat(point.GetEncoded(true)).ToArray();
}

payload = payload.Concat(new byte[] { 0 }).ToArray();
HashDigest<SHA512> cHash = HashDigest<SHA512>.DeriveFrom(payload);
byte[] truncatedCBytes = cHash.ToByteArray()
.Take((_eCParams.Curve.FieldSize + 7) >> 3).ToArray();
BigInteger c = new BigInteger(1, truncatedCBytes);

return c;
}

/// <summary>
/// Decomposes a <paramref name="piBytes"/> to original compositions.
/// </summary>
/// <param name="piBytes"> Proof to verify <see cref="PrivateKey"/>
/// and <c>alphaBytes</c>.
/// </param>
/// <returns>
/// A tuple of <c>gamma</c>, <c>c</c> and <c>s</c>.
/// which is used to verify <paramref name="piBytes"/>.
/// </returns>
private (ECPoint, BigInteger, BigInteger) DecodeProof(byte[] piBytes)
{
int gammaBytesLen = _eCFieldBytesSize + 1;
int cBytesLen = _eCFieldBytesSize;
int sBytesLen = _eCFieldBytesSize;
if (piBytes.Length != gammaBytesLen + cBytesLen + sBytesLen)
{
throw new ArgumentException(
$"Length of piBytes are expected to be " +
$"{gammaBytesLen + cBytesLen + sBytesLen}, " +
$"but found {piBytes.Length}");
}

byte[] gammaBytes = piBytes.Take(gammaBytesLen).ToArray();
byte[] cBytes = piBytes.Skip(gammaBytesLen).Take(cBytesLen).ToArray();
byte[] sBytes = piBytes.Skip(gammaBytesLen + cBytesLen).Take(sBytesLen).ToArray();

BigInteger c = new BigInteger(1, cBytes);
BigInteger s = new BigInteger(1, sBytes);

if (s.CompareTo(_eCParams.N) == 1)
{
throw new ArgumentException("s cannot be bigger than EC parameter N");
}

// dH : Gamma
ECPoint gamma;
try
{
gamma = _eCParams.Curve.DecodePoint(gammaBytes);
}
catch (FormatException)
{
throw new ArgumentException("Given piBytes does not contain valid gammaBytes");
}

return (gamma, c, s);
}
}
}
Loading
Loading