use std::fmt;
pub const MAX_SCRIPT_NUM_LENGTH_AFTER_GENESIS: usize = 750_000;
pub const MAX_SCRIPT_NUM_LENGTH_AFTER_CHRONICLE: usize = 32_000_000;
pub const DEFAULT_SCRIPT_NUM_LENGTH_POLICY: usize = 10_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ProtocolEra {
PostGenesis,
PostChronicle,
}
impl ProtocolEra {
pub const MAINNET_GENESIS_HEIGHT: u32 = 620_538;
pub const MAINNET_CHRONICLE_HEIGHT: u32 = 943_816;
pub fn at_height(height: u32, genesis_height: u32, chronicle_height: u32) -> Option<Self> {
if height < genesis_height {
None
} else if height < chronicle_height {
Some(ProtocolEra::PostGenesis)
} else {
Some(ProtocolEra::PostChronicle)
}
}
pub fn mainnet(height: u32) -> Option<Self> {
Self::at_height(
height,
Self::MAINNET_GENESIS_HEIGHT,
Self::MAINNET_CHRONICLE_HEIGHT,
)
}
pub fn is_chronicle(self) -> bool {
matches!(self, ProtocolEra::PostChronicle)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ScriptFlags(u32);
impl ScriptFlags {
pub const NONE: Self = Self(0);
pub const P2SH: Self = Self(1 << 0);
pub const STRICTENC: Self = Self(1 << 1);
pub const DERSIG: Self = Self(1 << 2);
pub const LOW_S: Self = Self(1 << 3);
pub const NULLDUMMY: Self = Self(1 << 4);
pub const SIGPUSHONLY: Self = Self(1 << 5);
pub const MINIMALDATA: Self = Self(1 << 6);
pub const DISCOURAGE_UPGRADABLE_NOPS: Self = Self(1 << 7);
pub const CLEANSTACK: Self = Self(1 << 8);
pub const CHECKLOCKTIMEVERIFY: Self = Self(1 << 9);
pub const CHECKSEQUENCEVERIFY: Self = Self(1 << 10);
pub const MINIMALIF: Self = Self(1 << 13);
pub const NULLFAIL: Self = Self(1 << 14);
pub const COMPRESSED_PUBKEYTYPE: Self = Self(1 << 15);
pub const SIGHASH_FORKID: Self = Self(1 << 16);
pub const GENESIS: Self = Self(1 << 18);
pub const UTXO_AFTER_GENESIS: Self = Self(1 << 19);
pub const CHRONICLE: Self = Self(1 << 20);
pub const UTXO_AFTER_CHRONICLE: Self = Self(1 << 21);
const NAMED: &'static [(Self, &'static str)] = &[
(Self::P2SH, "P2SH"),
(Self::STRICTENC, "STRICTENC"),
(Self::DERSIG, "DERSIG"),
(Self::LOW_S, "LOW_S"),
(Self::NULLDUMMY, "NULLDUMMY"),
(Self::SIGPUSHONLY, "SIGPUSHONLY"),
(Self::MINIMALDATA, "MINIMALDATA"),
(
Self::DISCOURAGE_UPGRADABLE_NOPS,
"DISCOURAGE_UPGRADABLE_NOPS",
),
(Self::CLEANSTACK, "CLEANSTACK"),
(Self::CHECKLOCKTIMEVERIFY, "CHECKLOCKTIMEVERIFY"),
(Self::CHECKSEQUENCEVERIFY, "CHECKSEQUENCEVERIFY"),
(Self::MINIMALIF, "MINIMALIF"),
(Self::NULLFAIL, "NULLFAIL"),
(Self::COMPRESSED_PUBKEYTYPE, "COMPRESSED_PUBKEYTYPE"),
(Self::SIGHASH_FORKID, "SIGHASH_FORKID"),
(Self::GENESIS, "GENESIS"),
(Self::UTXO_AFTER_GENESIS, "UTXO_AFTER_GENESIS"),
(Self::CHRONICLE, "CHRONICLE"),
(Self::UTXO_AFTER_CHRONICLE, "UTXO_AFTER_CHRONICLE"),
];
const KNOWN: u32 = {
let mut acc = 0u32;
let mut i = 0;
while i < Self::NAMED.len() {
acc |= Self::NAMED[i].0 .0;
i += 1;
}
acc
};
fn mandatory(era: ProtocolEra) -> Self {
let base =
Self::P2SH | Self::STRICTENC | Self::SIGHASH_FORKID | Self::NULLFAIL | Self::LOW_S;
if era.is_chronicle() {
base | Self::CHRONICLE
} else {
base
}
}
const STANDARD_ONLY: Self = Self(
Self::DERSIG.0
| Self::NULLDUMMY.0
| Self::DISCOURAGE_UPGRADABLE_NOPS.0
| Self::CHECKLOCKTIMEVERIFY.0
| Self::CHECKSEQUENCEVERIFY.0
| Self::CLEANSTACK.0
| Self::MINIMALDATA.0,
);
fn per_input(era: ProtocolEra) -> Self {
let mut f = Self::SIGPUSHONLY | Self::UTXO_AFTER_GENESIS;
if era.is_chronicle() {
f |= Self::UTXO_AFTER_CHRONICLE;
}
f
}
pub fn block(era: ProtocolEra) -> Self {
let mut f = Self::P2SH
| Self::DERSIG
| Self::CHECKLOCKTIMEVERIFY
| Self::CHECKSEQUENCEVERIFY
| Self::STRICTENC
| Self::SIGHASH_FORKID
| Self::LOW_S
| Self::NULLFAIL
| Self::GENESIS
| Self::SIGPUSHONLY;
if era.is_chronicle() {
f |= Self::CHRONICLE;
}
f | Self::per_input(era)
}
pub fn standard(era: ProtocolEra) -> Self {
Self::mandatory(era) | Self::STANDARD_ONLY | Self::GENESIS | Self::per_input(era)
}
const MEMPOOL_ONLY: Self = Self(
Self::NULLDUMMY.0
| Self::MINIMALDATA.0
| Self::DISCOURAGE_UPGRADABLE_NOPS.0
| Self::CLEANSTACK.0,
);
pub const fn is_mempool_word(self) -> bool {
self.contains(Self::MEMPOOL_ONLY)
}
pub const fn max_script_num_length(self, utxo_after_chronicle: bool, policy: usize) -> usize {
let consensus = if utxo_after_chronicle {
MAX_SCRIPT_NUM_LENGTH_AFTER_CHRONICLE
} else {
MAX_SCRIPT_NUM_LENGTH_AFTER_GENESIS
};
if self.is_mempool_word() && policy != 0 {
policy
} else {
consensus
}
}
pub const fn bits(self) -> u32 {
self.0
}
pub fn from_bits(bits: u32) -> Result<Self, ScriptFlagsError> {
let unknown = bits & !Self::KNOWN;
if unknown != 0 {
return Err(ScriptFlagsError::UnknownBits(unknown));
}
Ok(Self(bits))
}
pub fn from_names<'a>(
names: impl IntoIterator<Item = &'a str>,
) -> Result<Self, ScriptFlagsError> {
let mut f = Self::NONE;
for raw in names {
let name = raw.trim();
if name.is_empty() || name == "NONE" {
continue;
}
match Self::NAMED.iter().find(|(_, n)| *n == name) {
Some((bit, _)) => f |= *bit,
None => return Err(ScriptFlagsError::UnknownName(name.to_string())),
}
}
Ok(f)
}
pub fn names(self) -> Vec<&'static str> {
Self::NAMED
.iter()
.filter(|(bit, _)| self.contains(*bit))
.map(|(_, n)| *n)
.collect()
}
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn without(self, other: Self) -> Self {
Self(self.0 & !other.0)
}
pub fn check(self) -> Result<(), ScriptFlagsError> {
if !self.contains(Self::SIGHASH_FORKID) {
return Err(ScriptFlagsError::NotApplicable(
"SIGHASH_FORKID is not set: this interpreter computes the BIP-143 sighash only",
));
}
if !self.contains(Self::GENESIS) {
return Err(ScriptFlagsError::NotApplicable(
"GENESIS is not set: pre-Genesis block rules are not implemented by this interpreter",
));
}
if !self.contains(Self::UTXO_AFTER_GENESIS) {
return Err(ScriptFlagsError::NotApplicable(
"UTXO_AFTER_GENESIS is not set: pre-Genesis UTXO rules are not implemented by this interpreter",
));
}
if self.contains(Self::CHRONICLE) && !self.contains(Self::GENESIS) {
return Err(ScriptFlagsError::NotApplicable(
"CHRONICLE without GENESIS: no node derives this word",
));
}
if self.contains(Self::UTXO_AFTER_CHRONICLE) && !self.contains(Self::UTXO_AFTER_GENESIS) {
return Err(ScriptFlagsError::NotApplicable(
"UTXO_AFTER_CHRONICLE without UTXO_AFTER_GENESIS: the reference refuses this word (valid_flags)",
));
}
if self.contains(Self::CLEANSTACK) && !self.contains(Self::P2SH) {
return Err(ScriptFlagsError::NotApplicable(
"CLEANSTACK without P2SH: the reference refuses this word",
));
}
Ok(())
}
pub const fn enforce_non_malleability(self, tx_version: i32) -> bool {
!(self.contains(Self::CHRONICLE) && tx_version > 1)
}
pub const fn requires_push_only(self, tx_version: i32) -> bool {
self.contains(Self::SIGPUSHONLY)
&& ((self.contains(Self::GENESIS) && !self.contains(Self::CHRONICLE))
|| (self.contains(Self::CHRONICLE) && tx_version <= 1))
}
pub(crate) fn gates(self, tx_version: i32) -> Gates {
let enforce = self.enforce_non_malleability(tx_version);
Gates {
push_only: self.requires_push_only(tx_version),
minimal: self.contains(Self::MINIMALDATA) && enforce,
low_s: self.contains(Self::LOW_S) && enforce,
clean_stack: self.contains(Self::CLEANSTACK) && enforce,
null_dummy: self.contains(Self::NULLDUMMY) && enforce,
null_fail: self.contains(Self::NULLFAIL) && enforce,
minimal_if: self.contains(Self::MINIMALIF) && enforce,
discourage_upgradable_nops: self.contains(Self::DISCOURAGE_UPGRADABLE_NOPS),
compressed_pubkey: self.contains(Self::COMPRESSED_PUBKEYTYPE),
utxo_after_chronicle: self.contains(Self::UTXO_AFTER_CHRONICLE),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Gates {
pub push_only: bool,
pub minimal: bool,
pub low_s: bool,
pub clean_stack: bool,
pub null_dummy: bool,
pub null_fail: bool,
pub minimal_if: bool,
pub discourage_upgradable_nops: bool,
pub compressed_pubkey: bool,
pub utxo_after_chronicle: bool,
}
impl std::ops::BitOr for ScriptFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
impl std::ops::BitOrAssign for ScriptFlags {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl fmt::Debug for ScriptFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ScriptFlags({:#x}: {})", self.0, self.names().join("|"))
}
}
impl fmt::Display for ScriptFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0 == 0 {
return f.write_str("NONE");
}
f.write_str(&self.names().join(","))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ScriptFlagsError {
UnknownBits(u32),
UnknownName(String),
NotApplicable(&'static str),
}
impl fmt::Display for ScriptFlagsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ScriptFlagsError::UnknownBits(bits) => {
write!(f, "unknown script verification flag bits {:#x}", bits)
}
ScriptFlagsError::UnknownName(name) => {
write!(f, "unknown script verification flag name '{}'", name)
}
ScriptFlagsError::NotApplicable(why) => f.write_str(why),
}
}
}
impl std::error::Error for ScriptFlagsError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_post_chronicle_block_and_standard_words_have_the_reference_values() {
assert_eq!(
ScriptFlags::block(ProtocolEra::PostChronicle).bits(),
0x3D462F
);
assert_eq!(
ScriptFlags::standard(ProtocolEra::PostChronicle).bits(),
0x3D47FF
);
}
#[test]
fn the_post_genesis_words_drop_the_two_chronicle_bits_and_nothing_else() {
let block = ScriptFlags::block(ProtocolEra::PostGenesis);
let chronicle = ScriptFlags::block(ProtocolEra::PostChronicle);
assert_eq!(
chronicle.without(block),
ScriptFlags::CHRONICLE | ScriptFlags::UTXO_AFTER_CHRONICLE
);
assert_eq!(block.without(chronicle), ScriptFlags::NONE);
let standard = ScriptFlags::standard(ProtocolEra::PostGenesis);
assert_eq!(
ScriptFlags::standard(ProtocolEra::PostChronicle).without(standard),
ScriptFlags::CHRONICLE | ScriptFlags::UTXO_AFTER_CHRONICLE
);
}
#[test]
fn the_block_word_never_carries_a_standard_only_flag() {
for era in [ProtocolEra::PostGenesis, ProtocolEra::PostChronicle] {
let block = ScriptFlags::block(era);
for bit in [
ScriptFlags::NULLDUMMY,
ScriptFlags::MINIMALDATA,
ScriptFlags::DISCOURAGE_UPGRADABLE_NOPS,
ScriptFlags::CLEANSTACK,
] {
assert!(
!block.contains(bit),
"{era:?}: {bit:?} is in the block word"
);
}
assert_eq!(
ScriptFlags::standard(era).without(block),
ScriptFlags::NULLDUMMY
| ScriptFlags::MINIMALDATA
| ScriptFlags::DISCOURAGE_UPGRADABLE_NOPS
| ScriptFlags::CLEANSTACK
);
}
}
#[test]
fn the_version_gate_matches_the_references_static_asserts() {
let none = ScriptFlags::NONE;
let chronicle = ScriptFlags::CHRONICLE;
assert!(none.enforce_non_malleability(1));
assert!(none.enforce_non_malleability(2));
assert!(chronicle.enforce_non_malleability(1));
assert!(!chronicle.enforce_non_malleability(2));
assert!(chronicle.enforce_non_malleability(-1));
assert!(chronicle.enforce_non_malleability(0));
}
#[test]
fn push_only_is_required_pre_chronicle_at_every_version_and_post_chronicle_at_version_1() {
let genesis = ScriptFlags::block(ProtocolEra::PostGenesis);
assert!(genesis.requires_push_only(1));
assert!(genesis.requires_push_only(2));
let chronicle = ScriptFlags::block(ProtocolEra::PostChronicle);
assert!(chronicle.requires_push_only(1));
assert!(!chronicle.requires_push_only(2));
assert!(!chronicle
.without(ScriptFlags::SIGPUSHONLY)
.requires_push_only(1));
}
#[test]
fn the_gates_of_the_block_word_follow_the_version_at_chronicle() {
let word = ScriptFlags::block(ProtocolEra::PostChronicle);
let v1 = word.gates(1);
assert!(v1.low_s && v1.null_fail && v1.push_only);
assert!(!v1.minimal && !v1.clean_stack && !v1.null_dummy && !v1.minimal_if);
assert!(!v1.discourage_upgradable_nops && !v1.compressed_pubkey);
let v2 = word.gates(2);
assert_eq!(
v2,
Gates {
push_only: false,
minimal: false,
low_s: false,
clean_stack: false,
null_dummy: false,
null_fail: false,
minimal_if: false,
discourage_upgradable_nops: false,
compressed_pubkey: false,
utxo_after_chronicle: true,
}
);
assert!(
!ScriptFlags::block(ProtocolEra::PostGenesis)
.gates(2)
.utxo_after_chronicle
);
let standard_v1 = ScriptFlags::standard(ProtocolEra::PostChronicle).gates(1);
assert!(standard_v1.minimal && standard_v1.clean_stack && standard_v1.null_dummy);
assert!(standard_v1.discourage_upgradable_nops);
let standard_v2 = ScriptFlags::standard(ProtocolEra::PostChronicle).gates(2);
assert!(
standard_v2.discourage_upgradable_nops,
"not a malleability rule: no version gate"
);
assert!(!standard_v2.minimal && !standard_v2.clean_stack && !standard_v2.null_dummy);
}
#[test]
fn the_gates_of_the_standard_word_before_chronicle_ignore_the_version() {
let word = ScriptFlags::standard(ProtocolEra::PostGenesis);
assert_eq!(word.gates(1), word.gates(2));
assert!(word.gates(2).minimal && word.gates(2).null_fail && word.gates(2).push_only);
}
#[test]
fn names_round_trip_through_the_references_table() {
let word = ScriptFlags::standard(ProtocolEra::PostChronicle);
let names = word.names();
assert_eq!(
ScriptFlags::from_names(names.iter().copied()).unwrap(),
word
);
assert_eq!(
ScriptFlags::from_names(" NULLFAIL , SIGHASH_FORKID,NONE".split(',')).unwrap(),
ScriptFlags::NULLFAIL | ScriptFlags::SIGHASH_FORKID
);
assert_eq!(
ScriptFlags::from_names(["NULLFAIL", "BIP16"]).unwrap_err(),
ScriptFlagsError::UnknownName("BIP16".into())
);
assert_eq!(word.to_string(), names.join(","));
assert_eq!(ScriptFlags::NONE.to_string(), "NONE");
}
#[test]
fn from_bits_refuses_a_bit_the_crate_does_not_know() {
assert_eq!(
ScriptFlags::from_bits(0x3D462F).unwrap(),
ScriptFlags::block(ProtocolEra::PostChronicle)
);
assert_eq!(
ScriptFlags::from_bits(1 << 11).unwrap_err(),
ScriptFlagsError::UnknownBits(1 << 11)
);
assert_eq!(
ScriptFlags::from_bits(0x3D462F | (1 << 22)).unwrap_err(),
ScriptFlagsError::UnknownBits(1 << 22)
);
}
#[test]
fn check_refuses_what_the_interpreter_cannot_honor_and_what_the_reference_refuses() {
assert!(ScriptFlags::block(ProtocolEra::PostGenesis).check().is_ok());
assert!(ScriptFlags::standard(ProtocolEra::PostChronicle)
.check()
.is_ok());
let block = ScriptFlags::block(ProtocolEra::PostChronicle);
assert!(block.without(ScriptFlags::SIGHASH_FORKID).check().is_err());
assert!(block.without(ScriptFlags::GENESIS).check().is_err());
assert!(block
.without(ScriptFlags::UTXO_AFTER_GENESIS)
.check()
.is_err());
assert!((ScriptFlags::SIGHASH_FORKID
| ScriptFlags::GENESIS
| ScriptFlags::UTXO_AFTER_CHRONICLE)
.check()
.is_err());
assert!((block | ScriptFlags::CLEANSTACK)
.without(ScriptFlags::P2SH)
.check()
.is_err());
assert!((block | ScriptFlags::CLEANSTACK).check().is_ok());
}
#[test]
fn the_mainnet_eras_switch_at_the_activation_heights() {
assert_eq!(ProtocolEra::mainnet(620_537), None);
assert_eq!(
ProtocolEra::mainnet(620_538),
Some(ProtocolEra::PostGenesis)
);
assert_eq!(
ProtocolEra::mainnet(943_815),
Some(ProtocolEra::PostGenesis)
);
assert_eq!(
ProtocolEra::mainnet(943_816),
Some(ProtocolEra::PostChronicle)
);
assert_eq!(
ProtocolEra::at_height(20_000, 10_000, 15_000),
Some(ProtocolEra::PostChronicle)
);
assert_eq!(
ProtocolEra::at_height(12_000, 10_000, 15_000),
Some(ProtocolEra::PostGenesis)
);
}
}