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

NameDescription
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.init 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)));
}