mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 03:59:22 +00:00
Test the blockchain
This commit is contained in:
parent
ff5c240fcc
commit
7c7f17aac6
8 changed files with 301 additions and 36 deletions
|
@ -29,8 +29,8 @@ use crate::{
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct BlockHeader {
|
pub struct BlockHeader {
|
||||||
parent: [u8; 32],
|
pub parent: [u8; 32],
|
||||||
transactions: [u8; 32],
|
pub transactions: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReadWrite for BlockHeader {
|
impl ReadWrite for BlockHeader {
|
||||||
|
@ -55,8 +55,8 @@ impl BlockHeader {
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Block<T: Transaction> {
|
pub struct Block<T: Transaction> {
|
||||||
header: BlockHeader,
|
pub header: BlockHeader,
|
||||||
transactions: Vec<T>,
|
pub transactions: Vec<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Transaction> ReadWrite for Block<T> {
|
impl<T: Transaction> ReadWrite for Block<T> {
|
||||||
|
@ -89,7 +89,7 @@ impl<T: Transaction> Block<T> {
|
||||||
/// Create a new block.
|
/// Create a new block.
|
||||||
///
|
///
|
||||||
/// mempool is expected to only have valid, non-conflicting transactions.
|
/// mempool is expected to only have valid, non-conflicting transactions.
|
||||||
pub fn new(
|
pub(crate) fn new(
|
||||||
parent: [u8; 32],
|
parent: [u8; 32],
|
||||||
provided: &ProvidedTransactions<T>,
|
provided: &ProvidedTransactions<T>,
|
||||||
mempool: HashMap<[u8; 32], T>,
|
mempool: HashMap<[u8; 32], T>,
|
||||||
|
@ -106,7 +106,7 @@ impl<T: Transaction> Block<T> {
|
||||||
// Sort txs by nonces.
|
// Sort txs by nonces.
|
||||||
let nonce = |tx: &T| {
|
let nonce = |tx: &T| {
|
||||||
if let TransactionKind::Signed(Signed { nonce, .. }) = tx.kind() {
|
if let TransactionKind::Signed(Signed { nonce, .. }) = tx.kind() {
|
||||||
nonce
|
*nonce
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
@ -135,8 +135,8 @@ impl<T: Transaction> Block<T> {
|
||||||
&self,
|
&self,
|
||||||
genesis: [u8; 32],
|
genesis: [u8; 32],
|
||||||
last_block: [u8; 32],
|
last_block: [u8; 32],
|
||||||
locally_provided: &mut HashSet<[u8; 32]>,
|
mut locally_provided: HashSet<[u8; 32]>,
|
||||||
next_nonces: &mut HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
mut next_nonces: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
||||||
) -> Result<(), BlockError> {
|
) -> Result<(), BlockError> {
|
||||||
if self.header.parent != last_block {
|
if self.header.parent != last_block {
|
||||||
Err(BlockError::InvalidParent)?;
|
Err(BlockError::InvalidParent)?;
|
||||||
|
@ -144,7 +144,7 @@ impl<T: Transaction> Block<T> {
|
||||||
|
|
||||||
let mut txs = Vec::with_capacity(self.transactions.len());
|
let mut txs = Vec::with_capacity(self.transactions.len());
|
||||||
for tx in &self.transactions {
|
for tx in &self.transactions {
|
||||||
match verify_transaction(tx, genesis, locally_provided, next_nonces) {
|
match verify_transaction(tx, genesis, &mut locally_provided, &mut next_nonces) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(e) => Err(BlockError::TransactionError(e))?,
|
Err(e) => Err(BlockError::TransactionError(e))?,
|
||||||
}
|
}
|
||||||
|
|
73
coordinator/tributary/src/blockchain.rs
Normal file
73
coordinator/tributary/src/blockchain.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use std::collections::{HashSet, HashMap};
|
||||||
|
|
||||||
|
use ciphersuite::{Ciphersuite, Ristretto};
|
||||||
|
|
||||||
|
use crate::{Signed, TransactionKind, Transaction, ProvidedTransactions, BlockError, Block};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Blockchain<T: Transaction> {
|
||||||
|
genesis: [u8; 32],
|
||||||
|
// TODO: db
|
||||||
|
tip: [u8; 32],
|
||||||
|
provided: ProvidedTransactions<T>,
|
||||||
|
// TODO: Mempool
|
||||||
|
nonces: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Transaction> Blockchain<T> {
|
||||||
|
pub fn new(genesis: [u8; 32]) -> Self {
|
||||||
|
// TODO: Reload provided/nonces
|
||||||
|
Self { genesis, tip: genesis, provided: ProvidedTransactions::new(), nonces: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tip(&self) -> [u8; 32] {
|
||||||
|
self.tip
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn provide_transaction(&mut self, tx: T) {
|
||||||
|
self.provided.provide(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_nonce(&self, key: <Ristretto as Ciphersuite>::G) -> u32 {
|
||||||
|
self.nonces.get(&key).cloned().unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Embed mempool
|
||||||
|
pub fn build_block(&self, txs: HashMap<[u8; 32], T>) -> Block<T> {
|
||||||
|
let block = Block::new(self.tip, &self.provided, txs);
|
||||||
|
// build_block should not return invalid blocks
|
||||||
|
self.verify_block(&block).unwrap();
|
||||||
|
block
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_block(&self, block: &Block<T>) -> Result<(), BlockError> {
|
||||||
|
let mut locally_provided = HashSet::new();
|
||||||
|
for provided in self.provided.transactions.keys() {
|
||||||
|
locally_provided.insert(*provided);
|
||||||
|
}
|
||||||
|
block.verify(self.genesis, self.tip, locally_provided, self.nonces.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a block, assuming it's valid.
|
||||||
|
///
|
||||||
|
/// Do not call this without either verifying the block or having it confirmed under consensus.
|
||||||
|
/// Doing so will cause a panic or action an invalid transaction.
|
||||||
|
pub fn add_block(&mut self, block: &Block<T>) {
|
||||||
|
self.tip = block.hash();
|
||||||
|
for tx in &block.transactions {
|
||||||
|
match tx.kind() {
|
||||||
|
TransactionKind::Provided => {
|
||||||
|
self.provided.withdraw(tx.hash());
|
||||||
|
}
|
||||||
|
TransactionKind::Unsigned => {}
|
||||||
|
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
||||||
|
if let Some(prev) = self.nonces.insert(*signer, nonce + 1) {
|
||||||
|
if prev != *nonce {
|
||||||
|
panic!("block had an invalid nonce");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,9 @@ pub use provided::*;
|
||||||
mod block;
|
mod block;
|
||||||
pub use block::*;
|
pub use block::*;
|
||||||
|
|
||||||
|
mod blockchain;
|
||||||
|
pub use blockchain::*;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "tests"))]
|
#[cfg(any(test, feature = "tests"))]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ fn empty_block() {
|
||||||
const GENESIS: [u8; 32] = [0xff; 32];
|
const GENESIS: [u8; 32] = [0xff; 32];
|
||||||
const LAST: [u8; 32] = [0x01; 32];
|
const LAST: [u8; 32] = [0x01; 32];
|
||||||
Block::new(LAST, &ProvidedTransactions::<NonceTransaction>::new(), HashMap::new())
|
Block::new(LAST, &ProvidedTransactions::<NonceTransaction>::new(), HashMap::new())
|
||||||
.verify(GENESIS, LAST, &mut HashSet::new(), &mut HashMap::new())
|
.verify(GENESIS, LAST, HashSet::new(), HashMap::new())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,19 +89,18 @@ fn duplicate_nonces() {
|
||||||
for i in [1, 0] {
|
for i in [1, 0] {
|
||||||
let mut mempool = HashMap::new();
|
let mut mempool = HashMap::new();
|
||||||
let mut insert = |tx: NonceTransaction| mempool.insert(tx.hash(), tx);
|
let mut insert = |tx: NonceTransaction| mempool.insert(tx.hash(), tx);
|
||||||
insert(NonceTransaction(0, 0));
|
insert(NonceTransaction::new(0, 0));
|
||||||
insert(NonceTransaction(i, 1));
|
insert(NonceTransaction::new(i, 1));
|
||||||
|
|
||||||
let mut nonces = HashMap::new();
|
let nonces = HashMap::new();
|
||||||
let res = Block::new(LAST, &ProvidedTransactions::new(), mempool).verify(
|
let res = Block::new(LAST, &ProvidedTransactions::new(), mempool).verify(
|
||||||
GENESIS,
|
GENESIS,
|
||||||
LAST,
|
LAST,
|
||||||
&mut HashSet::new(),
|
HashSet::new(),
|
||||||
&mut nonces,
|
nonces,
|
||||||
);
|
);
|
||||||
if i == 1 {
|
if i == 1 {
|
||||||
res.unwrap();
|
res.unwrap();
|
||||||
assert_eq!(nonces[&<Ristretto as Ciphersuite>::G::identity()], 2);
|
|
||||||
} else {
|
} else {
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
@ -119,18 +118,20 @@ fn unsorted_nonces() {
|
||||||
let nonce = nonces.swap_remove(
|
let nonce = nonces.swap_remove(
|
||||||
usize::try_from(OsRng.next_u64() % u64::try_from(nonces.len()).unwrap()).unwrap(),
|
usize::try_from(OsRng.next_u64() % u64::try_from(nonces.len()).unwrap()).unwrap(),
|
||||||
);
|
);
|
||||||
let tx = NonceTransaction(nonce, 0);
|
let tx = NonceTransaction::new(nonce, 0);
|
||||||
mempool.insert(tx.hash(), tx);
|
mempool.insert(tx.hash(), tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and verify the block
|
// Create and verify the block
|
||||||
const GENESIS: [u8; 32] = [0xff; 32];
|
const GENESIS: [u8; 32] = [0xff; 32];
|
||||||
const LAST: [u8; 32] = [0x01; 32];
|
const LAST: [u8; 32] = [0x01; 32];
|
||||||
let mut nonces = HashMap::new();
|
Block::new(LAST, &ProvidedTransactions::new(), mempool.clone())
|
||||||
Block::new(LAST, &ProvidedTransactions::new(), mempool)
|
.verify(GENESIS, LAST, HashSet::new(), HashMap::new())
|
||||||
.verify(GENESIS, LAST, &mut HashSet::new(), &mut nonces)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Make sure the nonce was properly set
|
let skip = NonceTransaction::new(65, 0);
|
||||||
assert_eq!(nonces[&<Ristretto as Ciphersuite>::G::identity()], 64);
|
mempool.insert(skip.hash(), skip);
|
||||||
|
assert!(Block::new(LAST, &ProvidedTransactions::new(), mempool)
|
||||||
|
.verify(GENESIS, LAST, HashSet::new(), HashMap::new())
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
169
coordinator/tributary/src/tests/blockchain.rs
Normal file
169
coordinator/tributary/src/tests/blockchain.rs
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
use std::collections::{HashSet, HashMap};
|
||||||
|
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
|
use blake2::{Digest, Blake2s256};
|
||||||
|
|
||||||
|
use ciphersuite::{group::ff::Field, Ciphersuite, Ristretto};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
merkle, Transaction, ProvidedTransactions, Block, Blockchain,
|
||||||
|
tests::{ProvidedTransaction, SignedTransaction, random_provided_transaction},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn new_blockchain<T: Transaction>() -> ([u8; 32], Blockchain<T>) {
|
||||||
|
let mut genesis = [0; 32];
|
||||||
|
OsRng.fill_bytes(&mut genesis);
|
||||||
|
|
||||||
|
let blockchain = Blockchain::new(genesis);
|
||||||
|
assert_eq!(blockchain.tip(), genesis);
|
||||||
|
|
||||||
|
(genesis, blockchain)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_addition() {
|
||||||
|
let (genesis, mut blockchain) = new_blockchain::<SignedTransaction>();
|
||||||
|
let block = blockchain.build_block(HashMap::new());
|
||||||
|
assert_eq!(block.header.parent, genesis);
|
||||||
|
assert_eq!(block.header.transactions, [0; 32]);
|
||||||
|
blockchain.verify_block(&block).unwrap();
|
||||||
|
blockchain.add_block(&block);
|
||||||
|
assert_eq!(blockchain.tip(), block.hash());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_block() {
|
||||||
|
let (genesis, blockchain) = new_blockchain::<SignedTransaction>();
|
||||||
|
|
||||||
|
let block = blockchain.build_block(HashMap::new());
|
||||||
|
|
||||||
|
// Mutate parent
|
||||||
|
{
|
||||||
|
#[allow(clippy::redundant_clone)] // False positive
|
||||||
|
let mut block = block.clone();
|
||||||
|
block.header.parent = Blake2s256::digest(block.header.parent).into();
|
||||||
|
assert!(blockchain.verify_block(&block).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutate tranactions merkle
|
||||||
|
{
|
||||||
|
let mut block = block;
|
||||||
|
block.header.transactions = Blake2s256::digest(block.header.transactions).into();
|
||||||
|
assert!(blockchain.verify_block(&block).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||||
|
|
||||||
|
{
|
||||||
|
// Add a valid transaction
|
||||||
|
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0);
|
||||||
|
let mut block = blockchain.build_block(HashMap::from([(tx.hash(), tx.clone())]));
|
||||||
|
assert_eq!(block.header.transactions, merkle(&[tx.hash()]));
|
||||||
|
blockchain.verify_block(&block).unwrap();
|
||||||
|
|
||||||
|
// And verify mutating the transactions merkle now causes a failure
|
||||||
|
block.header.transactions = merkle(&[]);
|
||||||
|
assert!(blockchain.verify_block(&block).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Invalid nonce
|
||||||
|
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 5);
|
||||||
|
// Manually create the block to bypass build_block's checks
|
||||||
|
let block =
|
||||||
|
Block::new(blockchain.tip(), &ProvidedTransactions::new(), HashMap::from([(tx.hash(), tx)]));
|
||||||
|
assert!(blockchain.verify_block(&block).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Invalid signature
|
||||||
|
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0);
|
||||||
|
let mut block = blockchain.build_block(HashMap::from([(tx.hash(), tx)]));
|
||||||
|
blockchain.verify_block(&block).unwrap();
|
||||||
|
block.transactions[0].1.signature.s += <Ristretto as Ciphersuite>::F::ONE;
|
||||||
|
assert!(blockchain.verify_block(&block).is_err());
|
||||||
|
|
||||||
|
// Make sure this isn't because the merkle changed due to the transaction hash including the
|
||||||
|
// signature (which it explicitly isn't allowed to anyways)
|
||||||
|
assert_eq!(block.header.transactions, merkle(&[block.transactions[0].hash()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signed_transaction() {
|
||||||
|
let (genesis, mut blockchain) = new_blockchain::<SignedTransaction>();
|
||||||
|
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||||
|
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0);
|
||||||
|
let signer = tx.1.signer;
|
||||||
|
assert_eq!(blockchain.next_nonce(signer), 0);
|
||||||
|
|
||||||
|
let test = |blockchain: &mut Blockchain<SignedTransaction>, mempool: HashMap<_, _>| {
|
||||||
|
let mut hashes = mempool.keys().cloned().collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
let tip = blockchain.tip();
|
||||||
|
let block = blockchain.build_block(mempool);
|
||||||
|
assert_eq!(blockchain.tip(), tip);
|
||||||
|
assert_eq!(block.header.parent, tip);
|
||||||
|
|
||||||
|
// Make sure all transactions were included
|
||||||
|
let mut ordered_hashes = vec![];
|
||||||
|
assert_eq!(hashes.len(), block.transactions.len());
|
||||||
|
for transaction in &block.transactions {
|
||||||
|
let hash = transaction.hash();
|
||||||
|
assert!(hashes.remove(&hash));
|
||||||
|
ordered_hashes.push(hash);
|
||||||
|
}
|
||||||
|
// Make sure the merkle was correct
|
||||||
|
assert_eq!(block.header.transactions, merkle(&ordered_hashes));
|
||||||
|
|
||||||
|
// Verify and add the block
|
||||||
|
blockchain.verify_block(&block).unwrap();
|
||||||
|
blockchain.add_block(&block);
|
||||||
|
assert_eq!(blockchain.tip(), block.hash());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test with a single nonce
|
||||||
|
test(&mut blockchain, HashMap::from([(tx.hash(), tx)]));
|
||||||
|
assert_eq!(blockchain.next_nonce(signer), 1);
|
||||||
|
|
||||||
|
// Test with a flood of nonces
|
||||||
|
let mut mempool = HashMap::new();
|
||||||
|
let mut nonces = (1 .. 64).collect::<Vec<_>>();
|
||||||
|
// Randomize insertion order into HashMap, even though it should already have unordered iteration
|
||||||
|
while !nonces.is_empty() {
|
||||||
|
let nonce = nonces.swap_remove(
|
||||||
|
usize::try_from(OsRng.next_u64() % u64::try_from(nonces.len()).unwrap()).unwrap(),
|
||||||
|
);
|
||||||
|
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, nonce);
|
||||||
|
mempool.insert(tx.hash(), tx);
|
||||||
|
}
|
||||||
|
test(&mut blockchain, mempool);
|
||||||
|
assert_eq!(blockchain.next_nonce(signer), 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn provided_transaction() {
|
||||||
|
let (_, mut blockchain) = new_blockchain::<ProvidedTransaction>();
|
||||||
|
|
||||||
|
let tx = random_provided_transaction(&mut OsRng);
|
||||||
|
let mut txs = ProvidedTransactions::new();
|
||||||
|
txs.provide(tx.clone());
|
||||||
|
// Non-provided transactions should fail verification
|
||||||
|
let block = Block::new(blockchain.tip(), &txs, HashMap::new());
|
||||||
|
assert!(blockchain.verify_block(&block).is_err());
|
||||||
|
|
||||||
|
// Provided transactions should pass verification
|
||||||
|
blockchain.provide_transaction(tx);
|
||||||
|
blockchain.verify_block(&block).unwrap();
|
||||||
|
|
||||||
|
// add_block should work for verified blocks
|
||||||
|
blockchain.add_block(&block);
|
||||||
|
|
||||||
|
let block = Block::new(blockchain.tip(), &txs, HashMap::new());
|
||||||
|
// The provided transaction should no longer considered provided, causing this error
|
||||||
|
assert!(blockchain.verify_block(&block).is_err());
|
||||||
|
// add_block should also work for unverified provided transactions if told to add them
|
||||||
|
blockchain.add_block(&block);
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
mod transaction;
|
mod transaction;
|
||||||
pub use transaction::*;
|
pub use transaction::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod merkle;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod block;
|
mod block;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use block::*;
|
mod blockchain;
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::{
|
||||||
collections::{HashSet, HashMap},
|
collections::{HashSet, HashMap},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use zeroize::Zeroizing;
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use blake2::{Digest, Blake2s256};
|
use blake2::{Digest, Blake2s256};
|
||||||
|
@ -105,33 +106,42 @@ impl Transaction for SignedTransaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random_signed_transaction<R: RngCore + CryptoRng>(
|
pub fn signed_transaction<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
) -> ([u8; 32], SignedTransaction) {
|
genesis: [u8; 32],
|
||||||
use zeroize::Zeroizing;
|
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
|
||||||
|
nonce: u32,
|
||||||
|
) -> SignedTransaction {
|
||||||
let mut data = vec![0; 512];
|
let mut data = vec![0; 512];
|
||||||
rng.fill_bytes(&mut data);
|
rng.fill_bytes(&mut data);
|
||||||
|
|
||||||
let key = <Ristretto as Ciphersuite>::F::random(&mut *rng);
|
let signer = <Ristretto as Ciphersuite>::generator() * **key;
|
||||||
let signer = <Ristretto as Ciphersuite>::generator() * key;
|
|
||||||
// Shift over an additional bit to ensure it won't overflow when incremented
|
|
||||||
let nonce = u32::try_from(rng.next_u64() >> 32 >> 1).unwrap();
|
|
||||||
|
|
||||||
let mut tx =
|
let mut tx =
|
||||||
SignedTransaction(data, Signed { signer, nonce, signature: random_signed(rng).signature });
|
SignedTransaction(data, Signed { signer, nonce, signature: random_signed(rng).signature });
|
||||||
|
|
||||||
let mut genesis = [0; 32];
|
|
||||||
rng.fill_bytes(&mut genesis);
|
|
||||||
tx.1.signature = SchnorrSignature::sign(
|
tx.1.signature = SchnorrSignature::sign(
|
||||||
&Zeroizing::new(key),
|
key,
|
||||||
Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng)),
|
Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng)),
|
||||||
tx.sig_hash(genesis),
|
tx.sig_hash(genesis),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut nonces = HashMap::from([(tx.1.signer, tx.1.nonce)]);
|
let mut nonces = HashMap::from([(signer, nonce)]);
|
||||||
verify_transaction(&tx, genesis, &mut HashSet::new(), &mut nonces).unwrap();
|
verify_transaction(&tx, genesis, &mut HashSet::new(), &mut nonces).unwrap();
|
||||||
assert_eq!(nonces, HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]));
|
assert_eq!(nonces, HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]));
|
||||||
|
|
||||||
(genesis, tx)
|
tx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_signed_transaction<R: RngCore + CryptoRng>(
|
||||||
|
rng: &mut R,
|
||||||
|
) -> ([u8; 32], SignedTransaction) {
|
||||||
|
let mut genesis = [0; 32];
|
||||||
|
rng.fill_bytes(&mut genesis);
|
||||||
|
|
||||||
|
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut *rng));
|
||||||
|
// Shift over an additional bit to ensure it won't overflow when incremented
|
||||||
|
let nonce = u32::try_from(rng.next_u64() >> 32 >> 1).unwrap();
|
||||||
|
|
||||||
|
(genesis, signed_transaction(rng, genesis, &key, nonce))
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,14 +67,20 @@ pub enum TransactionKind<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Transaction: Send + Sync + Clone + Eq + Debug + ReadWrite {
|
pub trait Transaction: Send + Sync + Clone + Eq + Debug + ReadWrite {
|
||||||
|
/// Return what type of transaction this is.
|
||||||
fn kind(&self) -> TransactionKind<'_>;
|
fn kind(&self) -> TransactionKind<'_>;
|
||||||
|
|
||||||
/// Return the hash of this transaction.
|
/// Return the hash of this transaction.
|
||||||
///
|
///
|
||||||
/// The hash must NOT commit to the signature.
|
/// The hash must NOT commit to the signature.
|
||||||
fn hash(&self) -> [u8; 32];
|
fn hash(&self) -> [u8; 32];
|
||||||
|
|
||||||
|
/// Perform transaction-specific verification.
|
||||||
fn verify(&self) -> Result<(), TransactionError>;
|
fn verify(&self) -> Result<(), TransactionError>;
|
||||||
|
|
||||||
|
/// Obtain the challenge for this transaction's signature.
|
||||||
|
///
|
||||||
|
/// Do not override this unless you know what you're doing.
|
||||||
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
|
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
|
||||||
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(
|
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(
|
||||||
&Blake2b512::digest([genesis, self.hash()].concat()).into(),
|
&Blake2b512::digest([genesis, self.hash()].concat()).into(),
|
||||||
|
|
Loading…
Reference in a new issue