Function makeNewBlock
Create a new block, referencing the provided previous block.
Block makeNewBlock(Transactions)
(
const ref Block prev_block,
Transactions txs,
Hash[] preimages,
Enrollment[] enrollments = null
) nothrow @safe;
Parameters
Name | Description |
---|---|
prev_block | the previous block |
txs | the transactions that will be contained in the new block |
preimages | Pre-images that have been revealed in this block
Non-revealed pre-images must be passed as Hash
in their respective positions. |
enrollments | the enrollments that will be contained in the new block |
Example
import agora .consensus .data .genesis .Test;
auto new_block = makeNewTestBlock(GenesisBlock, [Transaction .init]);
auto rng_block = makeNewTestBlock(GenesisBlock, [Transaction .init] .take(1));
assert(new_block .header .prev_block == hashFull(GenesisBlock .header));
assert(new_block == rng_block);
Enrollment enr_1 =
{
utxo_key : Hash(
"0x412ce227771d98240ffb0015ae49349670eded40267865c18f655db662d4e698f" ~
"7caa4fcffdc5c068a07532637cf5042ae39b7af418847385480e620e1395986")
};
Enrollment enr_2 =
{
utxo_key : Hash(
"0x412ce227771d98240ffb0015ae49349670eded40267865c18f655db662d4e698f" ~
"7caa4fcffdc5c068a07532637cf5042ae39b7af418847385480e620e1395987")
};
Hash[] preimages =
WK .PreImages .at(GenesisBlock .header .height + 1, genesis_validator_keys);
auto block = makeNewBlock(GenesisBlock, [Transaction .init],
preimages, [enr_1, enr_2]);
assert(block .header .enrollments == [enr_1, enr_2]); // ascending
block = makeNewBlock(GenesisBlock, [Transaction .init],
preimages, [enr_2, enr_1]);
assert(block .header .enrollments == [enr_1, enr_2]); // ditto
Example
import agora .consensus .data .genesis .Test;
assert(GenesisBlock .header .hashFull() == GenesisBlock .hashFull());
Example
Test of Merkle Path and Merkle Proof
Transaction[] txs;
Hash[] merkle_path;
KeyPair[] key_pairs = [
KeyPair .random(),
KeyPair .random(),
KeyPair .random(),
KeyPair .random(),
KeyPair .random(),
KeyPair .random(),
KeyPair .random(),
KeyPair .random(),
KeyPair .random()
];
// Create transactions.
Hash last_hash = Hash .init;
for (int idx = 0; idx < 8; idx++)
{
auto tx = Transaction([Input(last_hash, 0)],[Output(Amount(100_000), key_pairs[idx+1] .address)]);
tx .inputs[0] .unlock = genKeyUnlock(
key_pairs[idx] .sign(tx .getChallenge()));
txs ~= tx;
}
Block block;
block .header .prev_block = Hash .init;
block .header .height = Height(0);
block .txs ~= txs;
block .header .merkle_root = block .buildMerkleTree();
Hash[] hashes;
hashes .reserve(txs .length);
foreach (ref e; txs)
hashes ~= hashFull(e);
// transactions are ordered lexicographically by hash in the Merkle tree
hashes .sort!("a < b");
foreach (idx, hash; hashes)
assert(block .findHashIndex(hash) == idx);
const Hash ha = hashes[0];
const Hash hb = hashes[1];
const Hash hc = hashes[2];
const Hash hd = hashes[3];
const Hash he = hashes[4];
const Hash hf = hashes[5];
const Hash hg = hashes[6];
const Hash hh = hashes[7];
const Hash hab = hashMulti(ha, hb);
const Hash hcd = hashMulti(hc, hd);
const Hash hef = hashMulti(he, hf);
const Hash hgh = hashMulti(hg, hh);
const Hash habcd = hashMulti(hab, hcd);
const Hash hefgh = hashMulti(hef, hgh);
const Hash habcdefgh = hashMulti(habcd, hefgh);
assert(block .header .merkle_root == habcdefgh);
// Merkle Proof
merkle_path = block .getMerklePath(2);
assert(merkle_path .length == 3);
assert(merkle_path[0] == hd);
assert(merkle_path[1] == hab);
assert(merkle_path[2] == hefgh);
assert(block .header .merkle_root == Block .checkMerklePath(hc, merkle_path, 2));
merkle_path = block .getMerklePath(4);
assert(merkle_path .length == 3);
assert(merkle_path[0] == hf);
assert(merkle_path[1] == hgh);
assert(merkle_path[2] == habcd);
assert(block .header .merkle_root == Block .checkMerklePath(he, merkle_path, 4));
Example
demonstrate signing two blocks at height 1 to reveal private node key
import agora .consensus .data .genesis .Test: GenesisBlock;
import agora .crypto .ECC: Scalar, Point;
import agora .utils .Test;
import std .format;
const TimeOffset = 1;
auto preimages =
WK .PreImages .at(GenesisBlock .header .height + 1, genesis_validator_keys);
// Generate two blocks at height 1
auto block1 = GenesisBlock .makeNewBlock(
genesisSpendable() .take(1) .map!(txb => txb .refund(WK .Keys .A .address) .sign()),
preimages);
auto block2 = GenesisBlock .makeNewBlock(
genesisSpendable() .take(1) .map!(txb => txb .refund(WK .Keys .Z .address) .sign()),
preimages);
// Two messages
auto c1 = block1 .hashFull();
auto c2 = block2 .hashFull();
assert(c1 != c2);
// Sign with same s twice
auto key = genesis_validator_keys[0] .secret;
Signature sig1 = block1 .header .sign(key, preimages[0]);
Signature sig2 = block2 .header .sign(key, preimages[0]);
// Verify signatures
assert(block1 .header .verify(genesis_validator_keys[0] .address, block1 .header .preimages[0], sig1 .R));
assert(block2 .header .verify(genesis_validator_keys[0] .address, block1 .header .preimages[0], sig2 .R));
// Calculate the private key by subtraction
// `s = (c * r) + v`
// Reusing the same `s` (pre-image) means we end up with the following system:
// s = (c1 * r1) + v
// s = (c2 * r2) + v
// We know `s`, `c1` and `c2`.
// Note: Since the scheme was changed, `r` is not reused, and this might
// not be possible anymore, and could require an on-chain mechanism for slashing.
version (none)
{
Scalar s = (sig1 .s - sig2 .s);
Scalar c = (c1 - c2);
Scalar secret = s * c .invert();
assert(secret == v,
format!"Key %s is not matching key %s"
(secret .toString(PrintMode .Clear), v .toString(PrintMode .Clear)));
}