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 Header.checkPow #801

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions ergo-chain-generation/src/chain_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

use std::convert::TryFrom;

use ergo_chain_types::autolykos_pow_scheme::{decode_compact_bits, order_bigint};
use ergo_lib::ergotree_ir::chain::context_extension::ContextExtension;
use ergo_lib::{
chain::{
ergo_box::box_builder::ErgoBoxCandidateBuilder,
transaction::{prover_result::ProverResult, Input, Transaction, TxIoVec},
},
ergo_chain_types::{BlockId, Digest32},
ergotree_ir::sigma_protocol::dlog_group::order_bigint,
};
use ergo_lib::{
ergo_chain_types::ADDigest,
Expand Down Expand Up @@ -197,7 +197,7 @@ fn prove_block(
.to_vec();
// Order of the secp256k1 elliptic curve
let order = order_bigint();
let target_b = order.clone() / ergo_nipopow::decode_compact_bits(header.n_bits);
let target_b = order.clone() / decode_compact_bits(header.n_bits);

let x = DlogProverInput::random();
let x_bigint = BigInt::from_bytes_be(Sign::Plus, &x.to_bytes());
Expand Down
3 changes: 1 addition & 2 deletions ergo-chain-generation/src/fake_pow_scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
#[cfg(test)]
mod tests {
use ergo_lib::ergo_chain_types::{blake2b256_hash, ADDigest, BlockId, Digest32};
use ergo_lib::ergotree_ir::sigma_protocol::dlog_group::order_bigint;
use ergo_nipopow::{NipopowAlgos, NipopowProof};

use ergo_chain_types::{AutolykosSolution, Header, Votes};
use ergo_chain_types::{autolykos_pow_scheme::order_bigint, AutolykosSolution, Header, Votes};
use ergo_lib::ergotree_interpreter::sigma_protocol::private_input::DlogProverInput;
use ergo_lib::ergotree_ir::serialization::sigma_byte_writer::SigmaByteWriter;
use ergo_nipopow::PoPowHeader;
Expand Down
1 change: 1 addition & 0 deletions ergo-chain-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ exclude = ["proptest-regressions/*"]

[dependencies]
thiserror = { workspace = true }
bounded-integer = { workspace = true }
derive_more = { workspace = true }
sigma-ser = { workspace = true }
sigma-util = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,95 @@
//! Autolykos Pow puzzle scheme implementation
//!
//! See for reference implmentation - <https://github.com/ergoplatform/ergo/blob/f7b91c0be00531c6d042c10a8855149ca6924373/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala>
//!
//! Based on k-sum problem, so general idea is to find k numbers in a table of size N, such that
//! sum of numbers (or a hash of the sum) is less than target value.
//!
//! See <https://docs.ergoplatform.com/ErgoPow.pdf> for details
//!
//! CPU Mining process is implemented in inefficient way and should not be used in real environment.
//!
//! See <https://github.com/ergoplatform/ergo/papers/yellow/pow/ErgoPow.tex> for full description
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use bounded_integer::{BoundedI32, BoundedU64};
use derive_more::From;
use ergo_chain_types::Header;
use k256::{elliptic_curve::PrimeField, Scalar};
use num_bigint::{BigInt, Sign};
use num_traits::Num;
use sigma_ser::ScorexSerializationError;
use sigma_util::hash::blake2b256_hash;
use thiserror::Error;

/// Autolykos PoW puzzle scheme implementation.
///
/// See for reference implmentation - <https://github.com/ergoplatform/ergo/blob/f7b91c0be00531c6d042c10a8855149ca6924373/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala>
///
/// Based on k-sum problem, so general idea is to find k numbers in a table of size N, such that
/// sum of numbers (or a hash of the sum) is less than target value.
use crate::Header;

/// The "compact" format is an encoding of a whole number `N` using an unsigned 32 bit number.
/// This number encodes a base-256 scientific notation representation of `N` (similar to a floating
/// point format):
/// - The most significant 8 bits represent the number of bytes necessary to represent `N` in
/// two's-complement form; denote it by `exponent`.
/// - The lower 23 bits are the mantissa(significand).
/// - Bit number 24 (0x800000) represents the sign of N.
///
/// See <https://docs.ergoplatform.com/ErgoPow.pdf> for details
/// There are 2 cases to consider:
/// - If `exponent >= 3` then `N` is represented by
/// `(-1^sign) * mantissa * 256^(exponent-3)`
/// E.g. suppose the compact form is given in hex-format by `0x04123456`. Mantissa is `0x123456`
/// and `exponent == 4`. So `N == 0x123456 * 265^1`. Now note that we need exactly 4 bytes to
/// represent `N`; 3 bytes for the mantissa and 1 byte for the rest. In base-256:
/// `N == B(0x12)B(0x34)B(0x56)0`
/// where `B(y)` denotes the base-256 representation of a hex-number `y` (note how each base-256
/// digit is represented by a single-byte).
/// - If `exponent < 3` then `N` is represented by the `exponent`-most-significant-bytes of the
/// mantissa. E.g. suppose the compact form is given in hex-format by `0x01003456`. Noting that
/// each hex-digit is represented by 4-bits, our `exponent == 0x01` which is `1` base-10. The
/// mantissa is represented by `0x003456` and it's most signficant byte is `0x00`. Therefore
/// `N == 0`.
///
/// CPU Mining process is implemented in inefficient way and should not be used in real environment.
/// Satoshi's original implementation used BN_bn2mpi() and BN_mpi2bn(). MPI uses the most
/// significant bit of the first byte as sign. Thus 0x1234560000 is compact 0x05123456 and
/// 0xc0de000000 is compact 0x0600c0de. Compact 0x05c0de00 would be -0x40de000000.
///
/// See <https://github.com/ergoplatform/ergo/papers/yellow/pow/ErgoPow.tex> for full description
/// Bitcoin only uses this "compact" format for encoding difficulty targets, which are unsigned
/// 256bit quantities. Thus, all the complexities of the sign bit and using base 256 are probably
/// an implementation accident.
pub fn decode_compact_bits(n_bits: u64) -> BigInt {
let compact = n_bits as i64;
let size = ((compact >> 24) as i32) & 0xFF;
if size == 0 {
return BigInt::from(0);
}
let mut buf: Vec<i8> = vec![0; size as usize];
if size >= 1 {
// Store the first byte of the mantissa
buf[0] = (((compact >> 16) as i32) & 0xFF) as i8;
}
if size >= 2 {
buf[1] = (((compact >> 8) as i32) & 0xFF) as i8;
}
if size >= 3 {
buf[2] = ((compact as i32) & 0xFF) as i8;
}

let is_negative = (buf[0] as i32) & 0x80 == 0x80;
if is_negative {
buf[0] &= 0x7f;
let buf: Vec<_> = buf.into_iter().map(|x| x as u8).collect();
-BigInt::from_signed_bytes_be(&buf)
} else {
let buf: Vec<_> = buf.into_iter().map(|x| x as u8).collect();
BigInt::from_signed_bytes_be(&buf)
}
}

/// Order of the secp256k1 elliptic curve as BigInt
pub fn order_bigint() -> BigInt {
#[allow(clippy::unwrap_used)]
BigInt::from_str_radix(Scalar::MODULUS, 16).unwrap()
}

/// Autolykos PoW puzzle scheme implementation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AutolykosPowScheme {
/// Represents the number of elements in one solution. **Important assumption**: `k <= 32`.
Expand All @@ -37,13 +110,10 @@ impl AutolykosPowScheme {
.cloned()
.ok_or(AutolykosPowSchemeError::MissingPowDistanceParameter)
} else {
use byteorder::{BigEndian, WriteBytesExt};
// hit for version 2
let msg = blake2b256_hash(&header.serialize_without_pow()?).to_vec();
let nonce = header.autolykos_solution.nonce.clone();
let mut height_bytes = Vec::with_capacity(4);
#[allow(clippy::unwrap_used)]
height_bytes.write_u32::<BigEndian>(header.height).unwrap();
let height_bytes = header.height.to_be_bytes();

let mut concat = msg.clone();
concat.extend(&nonce);
Expand All @@ -56,8 +126,7 @@ impl AutolykosPowScheme {
let f2 = indexes.into_iter().fold(BigInt::from(0u32), |acc, idx| {
// This is specific to autolykos v2.
let mut concat = vec![];
#[allow(clippy::unwrap_used)]
concat.write_u32::<BigEndian>(idx).unwrap();
concat.extend_from_slice(&idx.to_be_bytes());
concat.extend(&height_bytes);
concat.extend(&self.calc_big_m());
acc + BigInt::from_bytes_be(Sign::Plus, &blake2b256_hash(&concat)[1..])
Expand All @@ -72,15 +141,7 @@ impl AutolykosPowScheme {

/// Constant data to be added to hash function to increase its calculation time
pub fn calc_big_m(&self) -> Vec<u8> {
use byteorder::{BigEndian, WriteBytesExt};
(0u64..1024)
.flat_map(|x| {
let mut bytes = Vec::with_capacity(8);
#[allow(clippy::unwrap_used)]
bytes.write_u64::<BigEndian>(x).unwrap();
bytes
})
.collect()
(0u64..1024).flat_map(|x| x.to_be_bytes()).collect()
}

/// Computes `J` (denoted by `seed` in Ergo implementation) line 4, algorithm 1 of Autolykos v2
Expand Down Expand Up @@ -182,30 +243,36 @@ fn as_unsigned_byte_array(
let start = usize::from(bytes[0] == 0);
let count = bytes.len() - start;
if count > length {
return Err(AutolykosPowSchemeError::BigIntToFixedByteArrayError);
return Err(AutolykosPowSchemeError::BigIntToByteArrayError);
}
let mut res: Vec<_> = std::iter::repeat(0).take(length).collect();
let mut res: Vec<_> = vec![0; length];
res[(length - count)..].copy_from_slice(&bytes[start..]);
Ok(res)
}

#[derive(PartialEq, Eq, Debug, Clone, From)]
/// Autolykos POW scheme error
#[derive(PartialEq, Eq, Debug, Clone, From, Error)]
pub enum AutolykosPowSchemeError {
/// Scorex-serialization error
#[error("Scorex serialization error: {0}")]
ScorexSerializationError(ScorexSerializationError),
/// Error occurring when trying to convert a `BigInt` into a fixed-length byte-array.
BigIntToFixedByteArrayError,
#[error("Error converting BigInt to byte array")]
BigIntToByteArrayError,
/// Occurs when `Header.version == 1` and the `pow_distance` parameter is None.
#[error("PoW distance not found for Autolykos1 Header")]
MissingPowDistanceParameter,
/// Checking proof-of-work for AutolykosV1 is not supported
#[error("Header.check_pow is not supported for Autolykos1")]
Unsupported,
}

/// The following tests are taken from <https://github.com/ergoplatform/ergo/blob/f7b91c0be00531c6d042c10a8855149ca6924373/src/test/scala/org/ergoplatform/mining/AutolykosPowSchemeSpec.scala#L43-L130>
#[allow(clippy::unwrap_used)]
#[cfg(test)]
mod tests {
use ergotree_ir::{serialization::SigmaSerializable, sigma_protocol::dlog_group::order_bigint};

use crate::nipopow_algos::decode_compact_bits;
use num_bigint::ToBigInt;
use sigma_ser::ScorexSerializable;

use super::*;

Expand Down Expand Up @@ -300,7 +367,7 @@ mod tests {
&header
.autolykos_solution
.miner_pk
.sigma_serialize_bytes()
.scorex_serialize_bytes()
.unwrap()
),
"03bedaee069ff4829500b3c07c4d5fe6b3ea3d3bf76c5c28c1d4dcdb1bed0ade0c"
Expand Down Expand Up @@ -329,4 +396,47 @@ mod tests {

assert!(hit >= target_b);
}

#[test]
fn test_decode_n_bits() {
// Following example taken from https://btcinformation.org/en/developer-reference#target-nbits
let n_bits = 0x181bc330;
assert_eq!(
decode_compact_bits(n_bits),
BigInt::parse_bytes(b"1bc330000000000000000000000000000000000000000000", 16).unwrap()
);

let n_bits = 0x01003456;
assert_eq!(
decode_compact_bits(n_bits),
ToBigInt::to_bigint(&0x00).unwrap()
);

let n_bits = 0x01123456;
assert_eq!(
decode_compact_bits(n_bits),
ToBigInt::to_bigint(&0x12).unwrap()
);

let n_bits = 0x04923456;
assert_eq!(
decode_compact_bits(n_bits),
ToBigInt::to_bigint(&-0x12345600).unwrap()
);

let n_bits = 0x04123456;
assert_eq!(
decode_compact_bits(n_bits),
ToBigInt::to_bigint(&0x12345600).unwrap()
);

let n_bits = 0x05123456;
assert_eq!(
decode_compact_bits(n_bits),
ToBigInt::to_bigint(&0x1234560000i64).unwrap()
);

let n_bits = 16842752;
assert_eq!(decode_compact_bits(n_bits), BigInt::from(1_u8));
}
}
15 changes: 15 additions & 0 deletions ergo-chain-types/src/header.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
//! Block header
use crate::autolykos_pow_scheme::{
decode_compact_bits, order_bigint, AutolykosPowScheme, AutolykosPowSchemeError,
};
use crate::{ADDigest, BlockId, Digest32, EcPoint};
use alloc::boxed::Box;
use alloc::vec::Vec;
Expand Down Expand Up @@ -83,6 +86,16 @@ impl Header {
}
Ok(data)
}
/// Check that proof of work was valid for header. Only Autolykos2 is supported
pub fn check_pow(&self) -> Result<bool, AutolykosPowSchemeError> {
if self.version != 1 {
let hit = AutolykosPowScheme::default().pow_hit(self)?;
let target = order_bigint() / decode_compact_bits(self.n_bits);
Ok(hit < target)
} else {
Err(AutolykosPowSchemeError::Unsupported)
}
}
}

impl ScorexSerializable for Header {
Expand Down Expand Up @@ -419,6 +432,7 @@ mod tests {
}"#;
let header: Header = serde_json::from_str(json).unwrap();
assert_eq!(header.height, 471746);
assert!(header.check_pow().unwrap());
assert_eq!(
header.autolykos_solution.pow_distance,
Some(BigInt::from_str(
Expand Down Expand Up @@ -457,6 +471,7 @@ mod tests {
}"#;
let header: Header = serde_json::from_str(json).unwrap();
assert_eq!(header.height, 471746);
assert!(header.check_pow().unwrap());
assert_eq!(
header.autolykos_solution.pow_distance,
Some(BigInt::from_str(
Expand Down
1 change: 1 addition & 0 deletions ergo-chain-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#[macro_use]
extern crate alloc;

pub mod autolykos_pow_scheme;
mod base16_bytes;
mod block_id;
mod digest32;
Expand Down
3 changes: 1 addition & 2 deletions ergo-nipopow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@
#![deny(clippy::unimplemented)]
#![deny(clippy::panic)]

mod autolykos_pow_scheme;
mod nipopow_algos;
mod nipopow_proof;
mod nipopow_verifier;

pub use nipopow_algos::{decode_compact_bits, NipopowAlgos, INTERLINK_VECTOR_PREFIX};
pub use nipopow_algos::{NipopowAlgos, INTERLINK_VECTOR_PREFIX};
pub use nipopow_proof::{NipopowProof, NipopowProofError, PoPowHeader};
pub use nipopow_verifier::NipopowVerifier;
Loading
Loading