diff --git a/ergo-chain-types/src/header.rs b/ergo-chain-types/src/header.rs index 44d40b3f4..88dafb6c5 100644 --- a/ergo-chain-types/src/header.rs +++ b/ergo-chain-types/src/header.rs @@ -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 = vec![0; 8]; @@ -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; } diff --git a/ergotree-interpreter/src/eval/sheader.rs b/ergotree-interpreter/src/eval/sheader.rs index 11d7f1c05..d01acc3cd 100644 --- a/ergotree-interpreter/src/eval/sheader.rs +++ b/ergotree-interpreter/src/eval/sheader.rs @@ -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; @@ -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}; @@ -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::
()?; - Ok(Into::>::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| { @@ -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::
()?; - 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| { @@ -112,14 +134,18 @@ 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, @@ -127,6 +153,7 @@ mod tests { }, }; use num_bigint::ToBigInt; + use sigma_ser::ScorexSerializable; use sigma_test_util::force_any_val; use sigma_util::AsVecU8; @@ -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::(); - let expected = ctx.headers[HEADER_INDEX].state_root; - let actual = digest_from_bytes_signed::<33>(eval_out::>(&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::(&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::(); + ctx.headers[HEADER_INDEX] = header; + let expr = create_get_header_property_expr(sheader::STATE_ROOT_PROPERTY.clone()); + let actual = eval_out::(&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()); @@ -291,10 +344,12 @@ 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"); @@ -302,6 +357,24 @@ mod tests { 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::(); + ctx.headers[HEADER_INDEX] = header; + let expr = create_get_header_property_expr(sheader::POW_ONETIME_PK_PROPERTY.clone()); + let actual = eval_out::(&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());