diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 434dc329..93b65077 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -21,7 +21,7 @@ // Version 0.10.10: // TODO: Ensure all serde uses both string and binary version -// TODO: Complete taproot consensus structure implementation (ControlBlock) +// TODO: Complete consensus encoding for taproot data // TODO: Complete block data type implementation // TODO: Move consensus-level timelocks and sequence locks from other libraries // Version 1.0: @@ -77,9 +77,9 @@ pub use script::{OpCode, RedeemScript, ScriptBytes, ScriptPubkey, SigScript}; pub use segwit::{SegwitError, Witness, WitnessProgram, WitnessScript, WitnessVer, Wtxid}; pub use sigtypes::{Bip340Sig, LegacySig, SigError, SighashFlag, SighashType}; pub use taproot::{ - FutureLeafVer, InternalPk, IntoTapHash, InvalidLeafVer, LeafScript, LeafVer, TapBranchHash, - TapCode, TapLeafHash, TapMerklePath, TapNodeHash, TapScript, TAPROOT_ANNEX_PREFIX, - TAPROOT_LEAF_MASK, TAPROOT_LEAF_TAPSCRIPT, + ControlBlock, FutureLeafVer, InternalPk, IntoTapHash, InvalidLeafVer, LeafScript, LeafVer, + Parity, TapBranchHash, TapCode, TapLeafHash, TapMerklePath, TapNodeHash, TapScript, + TAPROOT_ANNEX_PREFIX, TAPROOT_LEAF_MASK, TAPROOT_LEAF_TAPSCRIPT, }; pub use tx::{ LockTime, Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxParseError, TxVer, diff --git a/primitives/src/stl.rs b/primitives/src/stl.rs index d0344b6c..81134e9b 100644 --- a/primitives/src/stl.rs +++ b/primitives/src/stl.rs @@ -23,10 +23,9 @@ use strict_types::{CompileError, LibBuilder, TypeLib}; use crate::{ - Bip340Sig, BlockHeader, ByteStr, Chain, FutureLeafVer, InternalPk, LeafScript, LeafVer, - LegacySig, OpCode, RedeemScript, TapBranchHash, TapCode, TapLeafHash, TapMerklePath, - TapNodeHash, TapScript, Tx, VBytes, VarInt, WeightUnits, WitnessProgram, WitnessScript, - WitnessVer, Wtxid, LIB_NAME_BITCOIN, + Bip340Sig, BlockHeader, ByteStr, Chain, ControlBlock, FutureLeafVer, LeafScript, LegacySig, + OpCode, RedeemScript, TapCode, TapLeafHash, TapNodeHash, TapScript, Tx, VBytes, VarInt, + WeightUnits, WitnessProgram, WitnessScript, WitnessVer, Wtxid, LIB_NAME_BITCOIN, }; #[deprecated(since = "0.10.8", note = "use LIB_ID_BP_TX instead")] @@ -35,7 +34,7 @@ pub const LIB_ID_BITCOIN: &str = pub const LIB_ID_BP_TX: &str = "urn:ubideco:stl:6GgF7biXPVNcus2FfQj2pQuRzau11rXApMQLfCZhojgi#money-pardon-parody"; pub const LIB_ID_BP_CONSENSUS: &str = - "urn:ubideco:stl:DrkRxZuxVjPSc5CtPmXhxQ14wyfVDz787WEfdrgqgffB#gray-prepare-proton"; + "urn:ubideco:stl:A6tfQFthqmb39wR5sWvrfgf3oiAyazm8rh7ff35ruioi#russian-emerald-extra"; #[deprecated(since = "0.10.8", note = "use _bp_tx_stl instead")] fn _bitcoin_stl() -> Result { _bp_tx_stl() } @@ -59,15 +58,12 @@ fn _bp_consensus_stl() -> Result { .transpile::() .transpile::() .transpile::() - .transpile::() .transpile::() - .transpile::() - .transpile::() - .transpile::() .transpile::() .transpile::() .transpile::() .transpile::() + .transpile::() .transpile::() .transpile::() .transpile::() diff --git a/primitives/src/taproot.rs b/primitives/src/taproot.rs index d7847195..3b0aaeaf 100644 --- a/primitives/src/taproot.rs +++ b/primitives/src/taproot.rs @@ -23,6 +23,7 @@ use std::borrow::Borrow; use std::fmt::{self, Formatter, LowerHex, UpperHex}; +use std::ops::BitXor; use std::{cmp, io}; use amplify::confinement::{Confined, U32}; @@ -457,3 +458,81 @@ impl ScriptPubkey { self.len() == 34 && self[0] == WitnessVer::V1.op_code() as u8 && self[1] == OP_PUSHBYTES_32 } } + +/// invalid parity value {0} - must be 0 or 1 +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, Error)] +#[display(doc_comments)] +pub struct InvalidParityValue(pub u8); + +/// Represents the parity passed between FFI function calls. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)] +#[display(lowercase)] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN, tags = repr, into_u8, try_from_u8)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +#[repr(u8)] +pub enum Parity { + /// Even parity. + #[strict_type(dumb)] + Even = 0, + /// Odd parity. + Odd = 1, +} + +impl Parity { + /// Converts parity into an integer (byte) value. + /// + /// This returns `0` for even parity and `1` for odd parity. + pub fn to_consensus_u8(self) -> u8 { self as u8 } + + /// Constructs a [`Parity`] from a byte. + /// + /// The only allowed values are `0` meaning even parity and `1` meaning odd. + /// Other values result in error being returned. + pub fn from_consensus_u8(parity: u8) -> Result { + match parity { + 0 => Ok(Parity::Even), + 1 => Ok(Parity::Odd), + invalid => Err(InvalidParityValue(invalid)), + } + } +} + +/// Returns even parity if the operands are equal, odd otherwise. +impl BitXor for Parity { + type Output = Parity; + + fn bitxor(self, rhs: Parity) -> Self::Output { + // This works because Parity has only two values (i.e. only 1 bit of + // information). + if self == rhs { + Parity::Even // 1^1==0 and 0^0==0 + } else { + Parity::Odd // 1^0==1 and 0^1==1 + } + } +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct ControlBlock { + /// The tapleaf version. + pub leaf_version: LeafVer, + /// The parity of the output key (NOT THE INTERNAL KEY WHICH IS ALWAYS + /// XONLY). + pub output_key_parity: Parity, + /// The internal key. + pub internal_key: InternalPk, + /// The merkle proof of a script associated with this leaf. + pub merkle_branch: TapMerklePath, +} diff --git a/stl/Bitcoin@0.1.0.sta b/stl/Bitcoin@0.1.0.sta index 820cd249..c37e080a 100644 --- a/stl/Bitcoin@0.1.0.sta +++ b/stl/Bitcoin@0.1.0.sta @@ -1,5 +1,5 @@ -----BEGIN STRICT TYPE LIB----- -Id: urn:ubideco:stl:DrkRxZuxVjPSc5CtPmXhxQ14wyfVDz787WEfdrgqgffB +Id: urn:ubideco:stl:A6tfQFthqmb39wR5sWvrfgf3oiAyazm8rh7ff35ruioi Name: Bitcoin Dependencies: urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ @@ -12,48 +12,52 @@ AQAHAABAIAALQmxvY2tIZWFkZXIGBgd2ZXJzaW9uAABEDXByZXZCbG9ja0hhc2gB 136XKN8Qx+HJT3+gvLwHRRJSZMs+SsX4k0LE/8jKtLcKbWVya2xlUm9vdAAHAABA IAAEdGltZQAABARiaXRzAAAEBW5vbmNlAAAEB0J5dGVTdHIFAQAIAABAAAAAAAAA AAD/////AAAAAAVDaGFpbgMEB2JpdGNvaW4AB3JlZ3Rlc3SACHRlc3RuZXQzgwZz -aWduZXSEDUZ1dHVyZUxlYWZWZXIFAQAAAQpJbnRlcm5hbFBrBQEABwAAQCAACkxl -YWZTY3JpcHQGAgd2ZXJzaW9uAbYzCakYv7aSDW7IWKQkhyNGWmk/ckMHv/8d1zpz -gU7JBnNjcmlwdAElq/W5EhQXuqM51F97FXWncUvhenUYXdAxNXrUyS3phgdMZWFm -VmVyBQEAAAEJTGVnYWN5U2lnBgIDc2lnAAgAAEAAAAAAAAAAAP8AAAAAAAAADHNp -Z2hhc2hfdHlwZQHbF2Q+dwkTJ+gEmSSB1dxgFSMIlVM55Yszn9RMT3JIKghMb2Nr -VGltZQUBAAAEBk9wQ29kZQMSCnB1c2hCeXRlczAAC3B1c2hCeXRlczMyIAlwdXNo -RGF0YTFMCXB1c2hEYXRhMk0JcHVzaERhdGE0TghyZXNlcnZlZFAIcHVzaE51bTFR -BnJldHVybmoDZHVwdgVlcXVhbIcLZXF1YWxWZXJpZnmICXJpcGVtZDE2MKYEc2hh -MacGc2hhMjU2qAdoYXNoMTYwqQdoYXNoMjU2qghjaGVja1NpZ6wOY2hlY2tTaWdW -ZXJpZnmtCE91dHBvaW50BgIEdHhpZAGjgkLzy9fR0KES2o3hYC9W1PhvDsTEdsXA -aFlMSwRlVgR2b3V0ASHjPkPFqlzyKSdTozjBZ+07Y5xN2c69qY80aRe6yUN1DFJl -ZGVlbVNjcmlwdAUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGBFNh -dHMFAQAACAtTY3JpcHRCeXRlcwUBAAgAAEAAAAAAAAAAAP////8AAAAADFNjcmlw -dFB1YmtleQUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGBVNlcU5v -BQEAAAQJU2lnU2NyaXB0BQEBJav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt -6YYLU2lnaGFzaEZsYWcDAwNhbGwBBG5vbmUCBnNpbmdsZQMLU2lnaGFzaFR5cGUG -AgRmbGFnAf8+an/Ix4VDTvyO53SzALGb7Jhxopoe8QCL7xDrKNU0DGFueW9uZUNh -blBheQJ7hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLtx2bKcGGGItF7rvBmAt/n -dcmA4LNrbrroCQ2AdfdRO+xLk/ZNDVRhcEJyYW5jaEhhc2gFAQAHAABAIAAHVGFw -Q29kZQMGC3B1c2hCeXRlczMyIAlwdXNoRGF0YTFMCXB1c2hEYXRhMk0JcHVzaERh -dGE0TghyZXNlcnZlZFAGcmV0dXJuagtUYXBMZWFmSGFzaAUBAAcAAEAgAA1UYXBN -ZXJrbGVQYXRoBQEACAGv68Wd2P1QRx2YXcJBKC6rqYxIivJkY8F2flEGIsKS2AAA -AAAAAAAA/wAAAAAAAAALVGFwTm9kZUhhc2gFAQAHAABAIAAJVGFwU2NyaXB0BQEB -Jav1uRIUF7qjOdRfexV1p3FL4Xp1GF3QMTV61Mkt6YYCVHgGBAd2ZXJzaW9uAah8 -xnlkZ+VX10TlyWI64AzLldkaDS8D33TAdRJPvseeBmlucHV0cwAIARlHLRfYYgen -DyExqVihaKVuKtZTP5xmTBl32lz5/mKGAAAAAAAAAAD/////AAAAAAdvdXRwdXRz -AAgBkDtkcHmEjxsmUyrkzsamiUSgU1i48IHLJrO7+C2eO/MAAAAAAAAAAP////8A -AAAACGxvY2tUaW1lATXaHRU5IG673dykwz2HMerym6fadN89yIIgHE4WtbkcBFR4 -SW4GBApwcmV2T3V0cHV0AehqQM1cJfm94oT/aaURMqdBKyFVvQ5WEsG/44SVYMUG -CXNpZ1NjcmlwdAE4dQSxS3wORm1HnhdHfSR0JH/4A2TsPUuq9zog90F0awhzZXF1 -ZW5jZQEBGW2FKcj22kRNFU6NnIy9ng+NiQJaO7CRIcY9UrAehwd3aXRuZXNzAXN3 -Q3A2kyBJzSiVCKpxfOOCnbJFLlXoTtT8LjzNLgCdBVR4T3V0BgIFdmFsdWUBl/XX -BkKuKjOSJTuoTh3OxJPjvz7TcbGHc4Y1TsyIgmsMc2NyaXB0UHVia2V5Ab78Hvxm -pRn9ZFJqOhOHQOfxEC0Lvv86wUZO8/dAdnRcBVR4VmVyBQEAAEQEVHhpZAUBAAcA -AEAgAAZWQnl0ZXMFAQAABAZWYXJJbnQFAQAACARWb3V0BQEAAAQLV2VpZ2h0VW5p -dHMFAQAABAdXaXRuZXNzBQEACAAIAABAAAAAAAAAAAD/////AAAAAAAAAAAAAAAA -/////wAAAAAOV2l0bmVzc1Byb2dyYW0GAgd2ZXJzaW9uAdHs2nZn5ELtTRJppmcD -NuX+9DevXs4raa66DEZWxPqtB3Byb2dyYW0ACAAAQAIAAAAAAAAAKAAAAAAAAAAN -V2l0bmVzc1NjcmlwdAUBASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemG -CldpdG5lc3NWZXIDEQJ2MAACdjFRAnYyUgJ2M1MCdjRUAnY1VQJ2NlYCdjdXAnY4 -WAJ2OVkDdjEwWgN2MTFbA3YxMlwDdjEzXQN2MTReA3YxNV8DdjE2YAVXdHhpZAUB -AAcAAEAgAA== +aWduZXSEDENvbnRyb2xCbG9jawYEC2xlYWZWZXJzaW9uAbYzCakYv7aSDW7IWKQk +hyNGWmk/ckMHv/8d1zpzgU7JD291dHB1dEtleVBhcml0eQGPyiYjolVEI5WPpnqn +bUNqFd5k6xCz9PmZFCIvlRFmiAtpbnRlcm5hbEtleQHf+AA8gf5H/FQjsdLG4g8N +36pmvczzfXQTyR9l2gA8iAxtZXJrbGVCcmFuY2gB7gMeZlFQaLZ0GVxRd/QKg8QA +UlvBpDAUGABpT73wc6QNRnV0dXJlTGVhZlZlcgUBAAABCkludGVybmFsUGsFAQAH +AABAIAAKTGVhZlNjcmlwdAYCB3ZlcnNpb24BtjMJqRi/tpINbshYpCSHI0ZaaT9y +Qwe//x3XOnOBTskGc2NyaXB0ASWr9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJ +LemGB0xlYWZWZXIFAQAAAQlMZWdhY3lTaWcGAgNzaWcACAAAQAAAAAAAAAAA/wAA +AAAAAAAMc2lnaGFzaF90eXBlAdsXZD53CRMn6ASZJIHV3GAVIwiVUznlizOf1ExP +ckgqCExvY2tUaW1lBQEAAAQGT3BDb2RlAxIKcHVzaEJ5dGVzMAALcHVzaEJ5dGVz +MzIgCXB1c2hEYXRhMUwJcHVzaERhdGEyTQlwdXNoRGF0YTROCHJlc2VydmVkUAhw +dXNoTnVtMVEGcmV0dXJuagNkdXB2BWVxdWFshwtlcXVhbFZlcmlmeYgJcmlwZW1k +MTYwpgRzaGExpwZzaGEyNTaoB2hhc2gxNjCpB2hhc2gyNTaqCGNoZWNrU2lnrA5j +aGVja1NpZ1Zlcmlmea0IT3V0cG9pbnQGAgR0eGlkAaOCQvPL19HQoRLajeFgL1bU ++G8OxMR2xcBoWUxLBGVWBHZvdXQBIeM+Q8WqXPIpJ1OjOMFn7TtjnE3Zzr2pjzRp +F7rJQ3UGUGFyaXR5AwIEZXZlbgADb2RkAQxSZWRlZW1TY3JpcHQFAQElq/W5EhQX +uqM51F97FXWncUvhenUYXdAxNXrUyS3phgRTYXRzBQEAAAgLU2NyaXB0Qnl0ZXMF +AQAIAABAAAAAAAAAAAD/////AAAAAAxTY3JpcHRQdWJrZXkFAQElq/W5EhQXuqM5 +1F97FXWncUvhenUYXdAxNXrUyS3phgVTZXFObwUBAAAECVNpZ1NjcmlwdAUBASWr +9bkSFBe6oznUX3sVdadxS+F6dRhd0DE1etTJLemGC1NpZ2hhc2hGbGFnAwMDYWxs +AQRub25lAgZzaW5nbGUDC1NpZ2hhc2hUeXBlBgIEZmxhZwH/Pmp/yMeFQ078jud0 +swCxm+yYcaKaHvEAi+8Q6yjVNAxhbnlvbmVDYW5QYXkCe4SAPJ764hElp3wsObxw +0v3o+UOuDf2c9OaC7cdmynBhhiLRe67wZgLf53XJgOCza2666AkNgHX3UTvsS5P2 +TQ1UYXBCcmFuY2hIYXNoBQEABwAAQCAAB1RhcENvZGUDBgtwdXNoQnl0ZXMzMiAJ +cHVzaERhdGExTAlwdXNoRGF0YTJNCXB1c2hEYXRhNE4IcmVzZXJ2ZWRQBnJldHVy +bmoLVGFwTGVhZkhhc2gFAQAHAABAIAANVGFwTWVya2xlUGF0aAUBAAgBr+vFndj9 +UEcdmF3CQSguq6mMSIryZGPBdn5RBiLCktgAAAAAAAAAAIAAAAAAAAAAC1RhcE5v +ZGVIYXNoBQEABwAAQCAACVRhcFNjcmlwdAUBASWr9bkSFBe6oznUX3sVdadxS+F6 +dRhd0DE1etTJLemGAlR4BgQHdmVyc2lvbgGofMZ5ZGflV9dE5cliOuAMy5XZGg0v +A990wHUST77HngZpbnB1dHMACAEZRy0X2GIHpw8hMalYoWilbirWUz+cZkwZd9pc ++f5ihgAAAAAAAAAA/////wAAAAAHb3V0cHV0cwAIAZA7ZHB5hI8bJlMq5M7GpolE +oFNYuPCByyazu/gtnjvzAAAAAAAAAAD/////AAAAAAhsb2NrVGltZQE12h0VOSBu +u93cpMM9hzHq8pun2nTfPciCIBxOFrW5HARUeEluBgQKcHJldk91dHB1dAHoakDN +XCX5veKE/2mlETKnQSshVb0OVhLBv+OElWDFBglzaWdTY3JpcHQBOHUEsUt8DkZt +R54XR30kdCR/+ANk7D1Lqvc6IPdBdGsIc2VxdWVuY2UBARlthSnI9tpETRVOjZyM +vZ4PjYkCWjuwkSHGPVKwHocHd2l0bmVzcwFzd0NwNpMgSc0olQiqcXzjgp2yRS5V +6E7U/C48zS4AnQVUeE91dAYCBXZhbHVlAZf11wZCriozkiU7qE4dzsST478+03Gx +h3OGNU7MiIJrDHNjcmlwdFB1YmtleQG+/B78ZqUZ/WRSajoTh0Dn8RAtC77/OsFG +TvP3QHZ0XAVUeFZlcgUBAABEBFR4aWQFAQAHAABAIAAGVkJ5dGVzBQEAAAQGVmFy +SW50BQEAAAgEVm91dAUBAAAEC1dlaWdodFVuaXRzBQEAAAQHV2l0bmVzcwUBAAgA +CAAAQAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAADldpdG5lc3NQcm9n +cmFtBgIHdmVyc2lvbgHR7Np2Z+RC7U0SaaZnAzbl/vQ3r17OK2muugxGVsT6rQdw +cm9ncmFtAAgAAEACAAAAAAAAACgAAAAAAAAADVdpdG5lc3NTY3JpcHQFAQElq/W5 +EhQXuqM51F97FXWncUvhenUYXdAxNXrUyS3phgpXaXRuZXNzVmVyAxECdjAAAnYx +UQJ2MlICdjNTAnY0VAJ2NVUCdjZWAnY3VwJ2OFgCdjlZA3YxMFoDdjExWwN2MTJc +A3YxM10DdjE0XgN2MTVfA3YxNmAFV3R4aWQFAQAHAABAIAA= -----END STRICT TYPE LIB----- diff --git a/stl/Bitcoin@0.1.0.stl b/stl/Bitcoin@0.1.0.stl index ff8596f5..75bdc339 100644 Binary files a/stl/Bitcoin@0.1.0.stl and b/stl/Bitcoin@0.1.0.stl differ diff --git a/stl/Bitcoin@0.1.0.sty b/stl/Bitcoin@0.1.0.sty index bf30d710..113c8d71 100644 --- a/stl/Bitcoin@0.1.0.sty +++ b/stl/Bitcoin@0.1.0.sty @@ -1,5 +1,5 @@ {- - Id: urn:ubideco:stl:DrkRxZuxVjPSc5CtPmXhxQ14wyfVDz787WEfdrgqgffB#gray-prepare-proton + Id: urn:ubideco:stl:A6tfQFthqmb39wR5sWvrfgf3oiAyazm8rh7ff35ruioi#russian-emerald-extra Name: Bitcoin Version: 0.1.0 Description: Consensus library for bitcoin protocol @@ -32,6 +32,11 @@ data ByteStr :: [Byte ^ ..0xffffffff] -- urn:ubideco:semid:6aRP3odHaTGySvSWHjreC8HsbX5ss9LxkQqwcjaoxhpv#aspirin-brown-alpine data Chain :: bitcoin:0 | regtest:128 | testnet3:131 | signet:132 +-- urn:ubideco:semid:C7rC7icVsoUF43k8QpzxWSx6BAbT8uyY2PWGjsQuf2kd#mega-optic-type +data ControlBlock :: leafVersion LeafVer + , outputKeyParity Parity + , internalKey InternalPk + , merkleBranch TapMerklePath -- urn:ubideco:semid:CvDS9EgqtBkWLvADynNeR7VGwVAy14EXViKnLaBkqtac#student-formula-circus data FutureLeafVer :: U8 -- urn:ubideco:semid:G5HFVaWwWNYSzqk548JgGZ8WKy6dQ2ftVgkJvHjgRudZ#horse-major-vienna @@ -53,6 +58,9 @@ data OpCode :: pushBytes0:0 | pushBytes32:32 | pushData1:76 | pushData -- urn:ubideco:semid:FWt2MSo8A4nsYgYbuBqMRNLiKgtzvLBgUn774iKzTcuf#pocket-pegasus-frank data Outpoint :: txid Txid, vout Vout +-- urn:ubideco:semid:AgJ5n58hrH761B4MV7giZ1FhMipaDrUmnFYCLno74HDy#method-editor-echo +data Parity :: even:0 | odd:1 + -- urn:ubideco:semid:85m9Qv56neQKaqiPSZ8G8NPWz5DDeJeeXhBwnUpcZGug#delta-jumbo-clone data RedeemScript :: ScriptBytes -- urn:ubideco:semid:BEBz6h7AGjYSDRCxVHnjYkkkxzBsjN3EvyNiD4ZrzmRL#pyramid-spray-star