Skip to content
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
17 changes: 11 additions & 6 deletions ergo-chain-types/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,11 @@ impl ScorexSerializable for Header {
pow_distance,
}
} else {
// autolykos v2
let pow_onetime_pk = None;
// autolykos v2: the wire carries no one-time pk; the reference impl
// fills it with the group generator at parse
// (`ErgoHeader.scala`: `wForV2 = CryptoConstants.dlogGroup.generator`),
// and that is what `Header.powOnetimePk` surfaces in scripts.
let pow_onetime_pk = Some(Box::new(crate::ec_point::generator()));
let pow_distance = None;
let miner_pk = EcPoint::scorex_parse(r)?.into();
let mut nonce: Vec<u8> = vec![0; 8];
Expand Down Expand Up @@ -366,11 +369,13 @@ mod arbitrary {
let id = BlockId(blake2b256_hash(&id_bytes));
header.id = id;

// Manually set the following parameters to `None` for autolykos v2. This is
// allowable since serialization/deserialization of the `Header` ignores
// these fields for version > 1.
// For autolykos v2 the serialized form carries neither field:
// parsing fills the one-time pk with the group generator (as the
// reference impl does) and leaves the distance empty, so generate
// headers in that post-parse shape for roundtrips.
if header.version > 1 {
header.autolykos_solution.pow_onetime_pk = None;
header.autolykos_solution.pow_onetime_pk =
Some(Box::new(crate::ec_point::generator()));
header.autolykos_solution.pow_distance = None;
}

Expand Down
89 changes: 81 additions & 8 deletions ergotree-interpreter/src/eval/sheader.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Evaluating predefined `Header` (or SHeader) type properties

use alloc::boxed::Box;
use alloc::sync::Arc;
use core::convert::TryInto;
use num_bigint::ToBigInt;
Expand All @@ -8,7 +9,11 @@ use alloc::vec::Vec;
use ergo_chain_types::Header;
use ergotree_ir::{
bigint256::BigInt256,
mir::{constant::TryExtractInto, value::Value},
mir::{
avl_tree_data::{AvlTreeData, AvlTreeFlags},
constant::TryExtractInto,
value::Value,
},
};

use super::{EvalError, EvalFn};
Expand All @@ -35,7 +40,16 @@ pub(crate) static AD_PROOFS_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args|

pub(crate) static STATE_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
let header = obj.try_extract_into::<Header>()?;
Ok(Into::<Vec<i8>>::into(header.state_root).into())
// `stateRoot` is typed `SAvlTree` (and so declared in our method descriptor):
// the reference impl wraps the digest with all operations enabled and
// keyLength = 32 (`CHeader.stateRoot` → `AvlTreeData.avlTreeFromDigest`),
// rather than returning the raw digest bytes.
Ok(Value::AvlTree(Box::new(AvlTreeData {
digest: header.state_root,
tree_flags: AvlTreeFlags::new(true, true, true),
key_length: 32,
value_length_opt: None,
})))
};

pub(crate) static TRANSACTION_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
Expand Down Expand Up @@ -70,7 +84,15 @@ pub(crate) static MINER_PK_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {

pub(crate) static POW_ONETIME_PK_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
let header = obj.try_extract_into::<Header>()?;
Ok((*header.autolykos_solution.pow_onetime_pk.unwrap_or_default()).into())
// The reference impl has no absent state for the one-time pk: autolykos v2
// headers carry the group generator (`ErgoHeader.scala`: `wForV2`). Wire
// parsing fills it in; fall back to the generator (not the identity) for
// headers built without it (e.g. from JSON with no `w` field).
Ok((*header
.autolykos_solution
.pow_onetime_pk
.unwrap_or_else(|| Box::new(ergo_chain_types::ec_point::generator())))
.into())
};

pub(crate) static POW_NONCE_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
Expand Down Expand Up @@ -112,21 +134,26 @@ mod tests {
use core::convert::{TryFrom, TryInto};

use alloc::{boxed::Box, vec::Vec};
use ergo_chain_types::{BlockId, Digest, Digest32, EcPoint, Votes};
use ergo_chain_types::{ec_point, BlockId, Digest, Digest32, EcPoint, Header, Votes};
use ergotree_ir::{
bigint256::BigInt256,
chain::context::Context,
mir::{
coll_by_index::ByIndex, expr::Expr, method_call::MethodCall,
avl_tree_data::{AvlTreeData, AvlTreeFlags},
coll_by_index::ByIndex,
expr::Expr,
method_call::MethodCall,
property_call::PropertyCall,
},
serialization::SigmaSerializable,
types::{
scontext::{self, HEADERS_PROPERTY},
sheader,
smethod::SMethod,
},
};
use num_bigint::ToBigInt;
use sigma_ser::ScorexSerializable;
use sigma_test_util::force_any_val;
use sigma_util::AsVecU8;

Expand Down Expand Up @@ -250,11 +277,37 @@ mod tests {
fn test_eval_state_root() {
let expr = create_get_header_property_expr(sheader::STATE_ROOT_PROPERTY.clone());
let ctx = force_any_val::<Context>();
let expected = ctx.headers[HEADER_INDEX].state_root;
let actual = digest_from_bytes_signed::<33>(eval_out::<Vec<i8>>(&expr, &ctx));
// `stateRoot` is an AvlTree wrapping the digest, all operations enabled,
// keyLength 32 (`CHeader.stateRoot` → `AvlTreeData.avlTreeFromDigest`).
let expected = AvlTreeData {
digest: ctx.headers[HEADER_INDEX].state_root,
tree_flags: AvlTreeFlags::new(true, true, true),
key_length: 32,
value_length_opt: None,
};
let actual = eval_out::<AvlTreeData>(&expr, &ctx);
assert_eq!(expected, actual);
}

// JVM-blessed byte vector (santa-eval `Header.property_accessors`, entry
// `h.stateRoot#nominal`): the blessed result is the serialized AvlTree —
// digest ‖ flags 0x07 ‖ keyLength 32 ‖ no value length.
#[test]
fn test_eval_state_root_blessed_bytes() {
let header = Header::scorex_parse_bytes(&base16::decode("02ac2101807f0000ca01ff0119db227f202201007f62000177a080005d440896d05d3f80dcff7f5e7f59007294c180808d0158d1ff6ba10000f901c7f0ef87dcfff17fffacb6ff7f7f1180d2ff7f1e24ffffe1ff937f807f0797b9ff6ebdae007e5c8c00b8403d3701557181c8df800001b6d5009e2201c6ff807d71808c00019780f087adb3fcdbc0b3441480887f80007f4b01cf7f013ff1ffff564a0000b9a54f00770e807f41ff88c00240000080c0250000000003bedaee069ff4829500b3c07c4d5fe6b3ea3d3bf76c5c28c1d4dcdb1bed0ade0c0000000000003105").unwrap()).unwrap();
let mut ctx = force_any_val::<Context>();
ctx.headers[HEADER_INDEX] = header;
let expr = create_get_header_property_expr(sheader::STATE_ROOT_PROPERTY.clone());
let actual = eval_out::<AvlTreeData>(&expr, &ctx);
assert_eq!(
actual.sigma_serialize_bytes().unwrap(),
base16::decode(
"5c8c00b8403d3701557181c8df800001b6d5009e2201c6ff807d71808c00019780072000"
)
.unwrap()
);
}

#[test]
fn test_eval_timestamp() {
let expr = create_get_header_property_expr(sheader::TIMESTAMP_PROPERTY.clone());
Expand Down Expand Up @@ -291,17 +344,37 @@ mod tests {
.map(|h| {
[
h.autolykos_solution.miner_pk.clone(),
// an absent one-time pk surfaces as the group generator,
// mirroring the reference impl's autolykos v2 default
h.autolykos_solution
.pow_onetime_pk
.clone()
.unwrap_or_default(),
.unwrap_or_else(|| Box::new(ec_point::generator())),
]
})
.expect("internal error: empty headers array");
let actual = eval_header_pks(&ctx);
assert_eq!(expected, actual);
}

// JVM-blessed byte vector (santa-eval `Header.property_accessors`, entry
// `h.powOnetimePk#nominal`): on an autolykos-v2 header the one-time pk is
// the group generator.
#[test]
fn test_eval_pow_onetime_pk_blessed_bytes() {
let header = Header::scorex_parse_bytes(&base16::decode("02ac2101807f0000ca01ff0119db227f202201007f62000177a080005d440896d05d3f80dcff7f5e7f59007294c180808d0158d1ff6ba10000f901c7f0ef87dcfff17fffacb6ff7f7f1180d2ff7f1e24ffffe1ff937f807f0797b9ff6ebdae007e5c8c00b8403d3701557181c8df800001b6d5009e2201c6ff807d71808c00019780f087adb3fcdbc0b3441480887f80007f4b01cf7f013ff1ffff564a0000b9a54f00770e807f41ff88c00240000080c0250000000003bedaee069ff4829500b3c07c4d5fe6b3ea3d3bf76c5c28c1d4dcdb1bed0ade0c0000000000003105").unwrap()).unwrap();
let mut ctx = force_any_val::<Context>();
ctx.headers[HEADER_INDEX] = header;
let expr = create_get_header_property_expr(sheader::POW_ONETIME_PK_PROPERTY.clone());
let actual = eval_out::<EcPoint>(&expr, &ctx);
assert_eq!(actual, ec_point::generator());
assert_eq!(
actual.scorex_serialize_bytes().unwrap(),
base16::decode("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
.unwrap()
);
}

#[test]
fn test_eval_pow_distance() {
let expr = create_get_header_property_expr(sheader::POW_DISTANCE_PROPERTY.clone());
Expand Down
Loading