From 68fd23c4a9cac043335e23dc5bf35f9c445b250d Mon Sep 17 00:00:00 2001 From: Hero-Gamer Date: Thu, 17 Sep 2026 22:28:25 -0400 Subject: [PATCH 1/3] q-56: miri islands for scriptnum + pack ints (FFI-free, no secp/store) --- crates/rbitcoin-primitives/src/compact.rs | 46 +++++++++++++++++++++ crates/rbitcoin-primitives/src/lib.rs | 5 +++ crates/rbitcoin-primitives/src/scriptnum.rs | 46 +++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 crates/rbitcoin-primitives/src/compact.rs create mode 100644 crates/rbitcoin-primitives/src/scriptnum.rs diff --git a/crates/rbitcoin-primitives/src/compact.rs b/crates/rbitcoin-primitives/src/compact.rs new file mode 100644 index 000000000..f99b658fc --- /dev/null +++ b/crates/rbitcoin-primitives/src/compact.rs @@ -0,0 +1,46 @@ +//! FFI-free pack integers for Miri (Q-56). +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CompactError { Empty, TruncatedU16, TruncatedU32, TruncatedU64, OverflowUleb, ShortDst } +impl std::fmt::Display for CompactError { fn fmt(&self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result { write!(f,"{self:?}") } } +impl std::error::Error for CompactError {} +#[inline] pub fn compact_size_len(n: u64) -> usize { if n<253 {1} else if n<=u16::MAX as u64 {3} else if n<=u32::MAX as u64 {5} else {9} } +pub fn write_compact_size(out: &mut Vec, n: u64) { + if n<253 { out.push(n as u8); } else if n<=u16::MAX as u64 { out.push(253); out.extend_from_slice(&(n as u16).to_le_bytes()); } + else if n<=u32::MAX as u64 { out.push(254); out.extend_from_slice(&(n as u32).to_le_bytes()); } else { out.push(255); out.extend_from_slice(&n.to_le_bytes()); } +} +pub fn read_compact_size(buf: &[u8]) -> Result<(u64,usize), CompactError> { + if buf.is_empty() { return Err(CompactError::Empty); } + match buf[0] { + n @ 0..=252 => Ok((n as u64,1)), + 253 => { if buf.len()<3 { return Err(CompactError::TruncatedU16); } let v=u16::from_le_bytes([buf[1],buf[2]]); Ok((v as u64,3)) }, + 254 => { if buf.len()<5 { return Err(CompactError::TruncatedU32); } let v=u32::from_le_bytes(buf[1..5].try_into().unwrap()); Ok((v as u64,5)) }, + 255 => { if buf.len()<9 { return Err(CompactError::TruncatedU64); } let v=u64::from_le_bytes(buf[1..9].try_into().unwrap()); Ok((v,9)) }, + } +} +#[inline] pub fn uleb128_len(mut n: u64) -> usize { let mut len=1; while n>=0x80 { n>>=7; len+=1; } len } +pub fn write_uleb128_into(dst: &mut [u8], mut n: u64) -> Result { + let mut i=0; loop { if i>=dst.len() { return Err(CompactError::ShortDst); } let mut b=(n & 0x7f) as u8; n>>=7; if n!=0 { b|=0x80; } dst[i]=b; i+=1; if n==0 { return Ok(i); } } +} +pub fn write_uleb128(out: &mut Vec, n: u64) { let mut tmp=[0u8;10]; let used=write_uleb128_into(&mut tmp,n).unwrap(); out.extend_from_slice(&tmp[..used]); } +pub fn read_uleb128(buf: &[u8]) -> Result<(u64,usize), CompactError> { + let mut result=0u64; let mut shift=0u32; + for (i,&b) in buf.iter().enumerate() { if shift>=64 { return Err(CompactError::OverflowUleb); } result|= u64::from(b & 0x7f) << shift; if b & 0x80==0 { return Ok((result,i+1)); } shift+=7; } + Err(CompactError::Empty) +} +#[cfg(test)] +mod tests { + use super::*; + #[test] fn compact_roundtrip() { + for n in [0,1,252,253,254,255,256,65535,65536,u32::MAX as u64,u32::MAX as u64+1,u64::MAX] { + let mut out=Vec::new(); write_compact_size(&mut out,n); assert_eq!(out.len(), compact_size_len(n)); + let (dec,used)=read_compact_size(&out).unwrap(); assert_eq!(dec,n); assert_eq!(used,out.len()); + } + } + #[test] fn uleb_roundtrip() { + for n in [0,1,127,128,255,16383,16384,u64::MAX] { + let mut out=Vec::new(); write_uleb128(&mut out,n); assert_eq!(out.len(), uleb128_len(n)); + let (dec,_)=read_uleb128(&out).unwrap(); assert_eq!(dec,n); + } + } + #[cfg(miri)] #[test] fn miri_compact() { for n in 0..10000u64 { let mut out=Vec::new(); write_compact_size(&mut out,n); let (dec,_)=read_compact_size(&out).unwrap(); assert_eq!(dec,n); } } +} diff --git a/crates/rbitcoin-primitives/src/lib.rs b/crates/rbitcoin-primitives/src/lib.rs index 57fd4c300..565d34d53 100644 --- a/crates/rbitcoin-primitives/src/lib.rs +++ b/crates/rbitcoin-primitives/src/lib.rs @@ -410,3 +410,8 @@ mod tests { assert_eq!(s, format!("/rbitcoin:{VERSION}/")); } } + +mod compact; +mod scriptnum; +pub use compact::{compact_size_len, read_compact_size, write_compact_size, read_uleb128, write_uleb128, uleb128_len, CompactError}; +pub use scriptnum::{encode_scriptnum, decode_scriptnum, decode_scriptnum_4, is_minimal_scriptnum, ScriptNumError}; diff --git a/crates/rbitcoin-primitives/src/scriptnum.rs b/crates/rbitcoin-primitives/src/scriptnum.rs new file mode 100644 index 000000000..453d687cf --- /dev/null +++ b/crates/rbitcoin-primitives/src/scriptnum.rs @@ -0,0 +1,46 @@ +//! FFI-free Bitcoin script number encoding (Q-56 Miri island). +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ScriptNumError { Overflow, NonMinimal } +impl std::fmt::Display for ScriptNumError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result { write!(f, "{self:?}") } +} +impl std::error::Error for ScriptNumError {} +pub fn encode_scriptnum(mut n: i64) -> Vec { + if n==0 { return vec![]; } + let neg = n<0; if neg { n=-n; } + let mut out=Vec::new(); + while n>0 { out.push((n & 0xff) as u8); n>>=8; } + if out.last().map(|b| b & 0x80!=0).unwrap_or(false) { out.push(if neg {0x80} else {0x00}); } + else if neg { *out.last_mut().unwrap()|=0x80; } + out +} +pub fn is_minimal_scriptnum(vch: &[u8]) -> bool { + if vch.is_empty() { return true; } + if vch[vch.len()-1] & 0x7f ==0 && (vch.len()<=1 || (vch[vch.len()-2] & 0x80)==0) { return false; } + true +} +pub fn decode_scriptnum(v: &[u8], require_minimal: bool, max_len: usize) -> Result { + if v.len()>max_len { return Err(ScriptNumError::Overflow); } + if require_minimal &&!is_minimal_scriptnum(v) { return Err(ScriptNumError::NonMinimal); } + if v.is_empty() { return Ok(0); } + let mut result: i64=0; + for (i,&b) in v.iter().enumerate() { result|= (b as i64) << (8*i); } + if v.last().unwrap() & 0x80!=0 { result &=!(0x80i64 << (8*(v.len()-1))); result = -result; } + Ok(result) +} +pub fn decode_scriptnum_4(v: &[u8], require_minimal: bool) -> Result { decode_scriptnum(v, require_minimal, 4) } +#[cfg(test)] +mod tests { + use super::*; + #[test] fn roundtrip_basic() { + for n in [0,1,-1,2,16,17,127,128,255,256,-255,-1000,0x7fffffff_i64,-0x7fffffff_i64] { + let enc=encode_scriptnum(n); let dec=decode_scriptnum_4(&enc,true).unwrap(); assert_eq!(dec,n); + } + } + #[test] fn minimality() { + assert!(is_minimal_scriptnum(&[])); assert!(is_minimal_scriptnum(&[0x01])); assert!(!is_minimal_scriptnum(&[0x01,0x00])); + } + #[cfg(miri)] #[test] fn miri_roundtrip() { + for n in -1000..1000 { let enc=encode_scriptnum(n as i64); if enc.len()<=4 { assert_eq!(decode_scriptnum_4(&enc,true).unwrap(), n as i64); } } + } +} From bbd26c3d5871c769210e904b7f431bfc6d1667e7 Mon Sep 17 00:00:00 2001 From: Hero-Gamer Date: Thu, 17 Sep 2026 22:36:57 -0400 Subject: [PATCH 2/3] fmt+clippy: rustfmt compact/scriptnum --- crates/rbitcoin-primitives/src/compact.rs | 172 +++++++++++++++++--- crates/rbitcoin-primitives/src/lib.rs | 9 +- crates/rbitcoin-primitives/src/scriptnum.rs | 115 ++++++++++--- 3 files changed, 243 insertions(+), 53 deletions(-) diff --git a/crates/rbitcoin-primitives/src/compact.rs b/crates/rbitcoin-primitives/src/compact.rs index f99b658fc..e7120512a 100644 --- a/crates/rbitcoin-primitives/src/compact.rs +++ b/crates/rbitcoin-primitives/src/compact.rs @@ -1,46 +1,166 @@ //! FFI-free pack integers for Miri (Q-56). #[derive(Debug, Clone, PartialEq, Eq)] -pub enum CompactError { Empty, TruncatedU16, TruncatedU32, TruncatedU64, OverflowUleb, ShortDst } -impl std::fmt::Display for CompactError { fn fmt(&self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result { write!(f,"{self:?}") } } +pub enum CompactError { + Empty, + TruncatedU16, + TruncatedU32, + TruncatedU64, + OverflowUleb, + ShortDst, +} +impl std::fmt::Display for CompactError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} impl std::error::Error for CompactError {} -#[inline] pub fn compact_size_len(n: u64) -> usize { if n<253 {1} else if n<=u16::MAX as u64 {3} else if n<=u32::MAX as u64 {5} else {9} } +#[inline] +pub fn compact_size_len(n: u64) -> usize { + if n < 253 { + 1 + } else if n <= u16::MAX as u64 { + 3 + } else if n <= u32::MAX as u64 { + 5 + } else { + 9 + } +} pub fn write_compact_size(out: &mut Vec, n: u64) { - if n<253 { out.push(n as u8); } else if n<=u16::MAX as u64 { out.push(253); out.extend_from_slice(&(n as u16).to_le_bytes()); } - else if n<=u32::MAX as u64 { out.push(254); out.extend_from_slice(&(n as u32).to_le_bytes()); } else { out.push(255); out.extend_from_slice(&n.to_le_bytes()); } + if n < 253 { + out.push(n as u8); + } else if n <= u16::MAX as u64 { + out.push(253); + out.extend_from_slice(&(n as u16).to_le_bytes()); + } else if n <= u32::MAX as u64 { + out.push(254); + out.extend_from_slice(&(n as u32).to_le_bytes()); + } else { + out.push(255); + out.extend_from_slice(&n.to_le_bytes()); + } } -pub fn read_compact_size(buf: &[u8]) -> Result<(u64,usize), CompactError> { - if buf.is_empty() { return Err(CompactError::Empty); } +pub fn read_compact_size(buf: &[u8]) -> Result<(u64, usize), CompactError> { + if buf.is_empty() { + return Err(CompactError::Empty); + } match buf[0] { - n @ 0..=252 => Ok((n as u64,1)), - 253 => { if buf.len()<3 { return Err(CompactError::TruncatedU16); } let v=u16::from_le_bytes([buf[1],buf[2]]); Ok((v as u64,3)) }, - 254 => { if buf.len()<5 { return Err(CompactError::TruncatedU32); } let v=u32::from_le_bytes(buf[1..5].try_into().unwrap()); Ok((v as u64,5)) }, - 255 => { if buf.len()<9 { return Err(CompactError::TruncatedU64); } let v=u64::from_le_bytes(buf[1..9].try_into().unwrap()); Ok((v,9)) }, + n @ 0..=252 => Ok((n as u64, 1)), + 253 => { + if buf.len() < 3 { + return Err(CompactError::TruncatedU16); + } + let v = u16::from_le_bytes([buf[1], buf[2]]); + Ok((v as u64, 3)) + } + 254 => { + if buf.len() < 5 { + return Err(CompactError::TruncatedU32); + } + let v = u32::from_le_bytes(buf[1..5].try_into().unwrap()); + Ok((v as u64, 5)) + } + 255 => { + if buf.len() < 9 { + return Err(CompactError::TruncatedU64); + } + let v = u64::from_le_bytes(buf[1..9].try_into().unwrap()); + Ok((v, 9)) + } + } +} +#[inline] +pub fn uleb128_len(mut n: u64) -> usize { + let mut len = 1; + while n >= 0x80 { + n >>= 7; + len += 1; } + len } -#[inline] pub fn uleb128_len(mut n: u64) -> usize { let mut len=1; while n>=0x80 { n>>=7; len+=1; } len } pub fn write_uleb128_into(dst: &mut [u8], mut n: u64) -> Result { - let mut i=0; loop { if i>=dst.len() { return Err(CompactError::ShortDst); } let mut b=(n & 0x7f) as u8; n>>=7; if n!=0 { b|=0x80; } dst[i]=b; i+=1; if n==0 { return Ok(i); } } + let mut i = 0; + loop { + if i >= dst.len() { + return Err(CompactError::ShortDst); + } + let mut b = (n & 0x7f) as u8; + n >>= 7; + if n != 0 { + b |= 0x80; + } + dst[i] = b; + i += 1; + if n == 0 { + return Ok(i); + } + } +} +pub fn write_uleb128(out: &mut Vec, n: u64) { + let mut tmp = [0u8; 10]; + let used = write_uleb128_into(&mut tmp, n).unwrap(); + out.extend_from_slice(&tmp[..used]); } -pub fn write_uleb128(out: &mut Vec, n: u64) { let mut tmp=[0u8;10]; let used=write_uleb128_into(&mut tmp,n).unwrap(); out.extend_from_slice(&tmp[..used]); } -pub fn read_uleb128(buf: &[u8]) -> Result<(u64,usize), CompactError> { - let mut result=0u64; let mut shift=0u32; - for (i,&b) in buf.iter().enumerate() { if shift>=64 { return Err(CompactError::OverflowUleb); } result|= u64::from(b & 0x7f) << shift; if b & 0x80==0 { return Ok((result,i+1)); } shift+=7; } +pub fn read_uleb128(buf: &[u8]) -> Result<(u64, usize), CompactError> { + let mut result = 0u64; + let mut shift = 0u32; + for (i, &b) in buf.iter().enumerate() { + if shift >= 64 { + return Err(CompactError::OverflowUleb); + } + result |= u64::from(b & 0x7f) << shift; + if b & 0x80 == 0 { + return Ok((result, i + 1)); + } + shift += 7; + } Err(CompactError::Empty) } #[cfg(test)] mod tests { use super::*; - #[test] fn compact_roundtrip() { - for n in [0,1,252,253,254,255,256,65535,65536,u32::MAX as u64,u32::MAX as u64+1,u64::MAX] { - let mut out=Vec::new(); write_compact_size(&mut out,n); assert_eq!(out.len(), compact_size_len(n)); - let (dec,used)=read_compact_size(&out).unwrap(); assert_eq!(dec,n); assert_eq!(used,out.len()); + #[test] + fn compact_roundtrip() { + for n in [ + 0, + 1, + 252, + 253, + 254, + 255, + 256, + 65535, + 65536, + u32::MAX as u64, + u32::MAX as u64 + 1, + u64::MAX, + ] { + let mut out = Vec::new(); + write_compact_size(&mut out, n); + assert_eq!(out.len(), compact_size_len(n)); + let (dec, used) = read_compact_size(&out).unwrap(); + assert_eq!(dec, n); + assert_eq!(used, out.len()); + } + } + #[test] + fn uleb_roundtrip() { + for n in [0, 1, 127, 128, 255, 16383, 16384, u64::MAX] { + let mut out = Vec::new(); + write_uleb128(&mut out, n); + assert_eq!(out.len(), uleb128_len(n)); + let (dec, _) = read_uleb128(&out).unwrap(); + assert_eq!(dec, n); } } - #[test] fn uleb_roundtrip() { - for n in [0,1,127,128,255,16383,16384,u64::MAX] { - let mut out=Vec::new(); write_uleb128(&mut out,n); assert_eq!(out.len(), uleb128_len(n)); - let (dec,_)=read_uleb128(&out).unwrap(); assert_eq!(dec,n); + #[cfg(miri)] + #[test] + fn miri_compact() { + for n in 0..10000u64 { + let mut out = Vec::new(); + write_compact_size(&mut out, n); + let (dec, _) = read_compact_size(&out).unwrap(); + assert_eq!(dec, n); } } - #[cfg(miri)] #[test] fn miri_compact() { for n in 0..10000u64 { let mut out=Vec::new(); write_compact_size(&mut out,n); let (dec,_)=read_compact_size(&out).unwrap(); assert_eq!(dec,n); } } } diff --git a/crates/rbitcoin-primitives/src/lib.rs b/crates/rbitcoin-primitives/src/lib.rs index 565d34d53..ad630a8e8 100644 --- a/crates/rbitcoin-primitives/src/lib.rs +++ b/crates/rbitcoin-primitives/src/lib.rs @@ -413,5 +413,10 @@ mod tests { mod compact; mod scriptnum; -pub use compact::{compact_size_len, read_compact_size, write_compact_size, read_uleb128, write_uleb128, uleb128_len, CompactError}; -pub use scriptnum::{encode_scriptnum, decode_scriptnum, decode_scriptnum_4, is_minimal_scriptnum, ScriptNumError}; +pub use compact::{ + compact_size_len, read_compact_size, read_uleb128, uleb128_len, write_compact_size, + write_uleb128, CompactError, +}; +pub use scriptnum::{ + decode_scriptnum, decode_scriptnum_4, encode_scriptnum, is_minimal_scriptnum, ScriptNumError, +}; diff --git a/crates/rbitcoin-primitives/src/scriptnum.rs b/crates/rbitcoin-primitives/src/scriptnum.rs index 453d687cf..f83d094eb 100644 --- a/crates/rbitcoin-primitives/src/scriptnum.rs +++ b/crates/rbitcoin-primitives/src/scriptnum.rs @@ -1,46 +1,111 @@ //! FFI-free Bitcoin script number encoding (Q-56 Miri island). #[derive(Debug, Clone, PartialEq, Eq)] -pub enum ScriptNumError { Overflow, NonMinimal } +pub enum ScriptNumError { + Overflow, + NonMinimal, +} impl std::fmt::Display for ScriptNumError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result { write!(f, "{self:?}") } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } impl std::error::Error for ScriptNumError {} pub fn encode_scriptnum(mut n: i64) -> Vec { - if n==0 { return vec![]; } - let neg = n<0; if neg { n=-n; } - let mut out=Vec::new(); - while n>0 { out.push((n & 0xff) as u8); n>>=8; } - if out.last().map(|b| b & 0x80!=0).unwrap_or(false) { out.push(if neg {0x80} else {0x00}); } - else if neg { *out.last_mut().unwrap()|=0x80; } + if n == 0 { + return vec![]; + } + let neg = n < 0; + if neg { + n = -n; + } + let mut out = Vec::new(); + while n > 0 { + out.push((n & 0xff) as u8); + n >>= 8; + } + if out.last().map(|b| b & 0x80 != 0).unwrap_or(false) { + out.push(if neg { 0x80 } else { 0x00 }); + } else if neg { + *out.last_mut().unwrap() |= 0x80; + } out } pub fn is_minimal_scriptnum(vch: &[u8]) -> bool { - if vch.is_empty() { return true; } - if vch[vch.len()-1] & 0x7f ==0 && (vch.len()<=1 || (vch[vch.len()-2] & 0x80)==0) { return false; } + if vch.is_empty() { + return true; + } + if vch[vch.len() - 1] & 0x7f == 0 && (vch.len() <= 1 || (vch[vch.len() - 2] & 0x80) == 0) { + return false; + } true } -pub fn decode_scriptnum(v: &[u8], require_minimal: bool, max_len: usize) -> Result { - if v.len()>max_len { return Err(ScriptNumError::Overflow); } - if require_minimal &&!is_minimal_scriptnum(v) { return Err(ScriptNumError::NonMinimal); } - if v.is_empty() { return Ok(0); } - let mut result: i64=0; - for (i,&b) in v.iter().enumerate() { result|= (b as i64) << (8*i); } - if v.last().unwrap() & 0x80!=0 { result &=!(0x80i64 << (8*(v.len()-1))); result = -result; } +pub fn decode_scriptnum( + v: &[u8], + require_minimal: bool, + max_len: usize, +) -> Result { + if v.len() > max_len { + return Err(ScriptNumError::Overflow); + } + if require_minimal && !is_minimal_scriptnum(v) { + return Err(ScriptNumError::NonMinimal); + } + if v.is_empty() { + return Ok(0); + } + let mut result: i64 = 0; + for (i, &b) in v.iter().enumerate() { + result |= (b as i64) << (8 * i); + } + if v.last().unwrap() & 0x80 != 0 { + result &= !(0x80i64 << (8 * (v.len() - 1))); + result = -result; + } Ok(result) } -pub fn decode_scriptnum_4(v: &[u8], require_minimal: bool) -> Result { decode_scriptnum(v, require_minimal, 4) } +pub fn decode_scriptnum_4(v: &[u8], require_minimal: bool) -> Result { + decode_scriptnum(v, require_minimal, 4) +} #[cfg(test)] mod tests { use super::*; - #[test] fn roundtrip_basic() { - for n in [0,1,-1,2,16,17,127,128,255,256,-255,-1000,0x7fffffff_i64,-0x7fffffff_i64] { - let enc=encode_scriptnum(n); let dec=decode_scriptnum_4(&enc,true).unwrap(); assert_eq!(dec,n); + #[test] + fn roundtrip_basic() { + for n in [ + 0, + 1, + -1, + 2, + 16, + 17, + 127, + 128, + 255, + 256, + -255, + -1000, + 0x7fffffff_i64, + -0x7fffffff_i64, + ] { + let enc = encode_scriptnum(n); + let dec = decode_scriptnum_4(&enc, true).unwrap(); + assert_eq!(dec, n); } } - #[test] fn minimality() { - assert!(is_minimal_scriptnum(&[])); assert!(is_minimal_scriptnum(&[0x01])); assert!(!is_minimal_scriptnum(&[0x01,0x00])); + #[test] + fn minimality() { + assert!(is_minimal_scriptnum(&[])); + assert!(is_minimal_scriptnum(&[0x01])); + assert!(!is_minimal_scriptnum(&[0x01, 0x00])); } - #[cfg(miri)] #[test] fn miri_roundtrip() { - for n in -1000..1000 { let enc=encode_scriptnum(n as i64); if enc.len()<=4 { assert_eq!(decode_scriptnum_4(&enc,true).unwrap(), n as i64); } } + #[cfg(miri)] + #[test] + fn miri_roundtrip() { + for n in -1000..1000 { + let enc = encode_scriptnum(n as i64); + if enc.len() <= 4 { + assert_eq!(decode_scriptnum_4(&enc, true).unwrap(), n as i64); + } + } } } From 6fb4ce7107926f105c068f4ada12dd43909cd661 Mon Sep 17 00:00:00 2001 From: Hero-Gamer Date: Thu, 17 Sep 2026 22:55:22 -0400 Subject: [PATCH 3/3] q-56: close quality + CHANGELOG --- CHANGELOG.md | 3 +++ docs/quality.md | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0360dc42..ea010bc57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ before 1.0). ### Added +- **Q-56 Miri islands beyond primitives:** `scriptnum` (encode/decode/is_minimal) + `CompactSize` + `ULEB128` peeled into `rbitcoin-primitives` (FFI-free, 0 deps). Adds `cfg(miri)` islands (1000-range scriptnum, 0..10000 compact) so `cargo +nightly miri test -p rbitcoin-primitives` works. Never `--workspace` miri. R-10 peel allowed. + + - **Wallet-protocol leftovers:** Electrum **1.6** `blockchain.transaction.broadcast_package` (local `accept_package`) and `mempool.get_info`; `protocol_max` **1.6** with 1.6 `block.headers` as a list. Electrum **1.7** `blockchain.outpoint.*`. diff --git a/docs/quality.md b/docs/quality.md index 4f9d2203d..a103953e7 100644 --- a/docs/quality.md +++ b/docs/quality.md @@ -30,7 +30,6 @@ evidence (failed Core corpus, new dual path, red required CI, MSRV drift). | 3 | **Q-31** | Hermetic tip fixtures | Frozen signet/mainnet tip packs for offline consensus/Electrum regression (no live API). Fuzz already merges tiny `signet_block_*.bin` / `mainnet_block_290329.bin`. Electrum hermetic packs still Open. | | 4 | **R-10** | Residual god-files | Peel **only** when a higher row needs a seam. Do not split `interpreter.rs` opcode `match` or io_uring machines. Named extracts: **Q-61** Completed. | | 5 | **Q-54** | ast-grep named-cap rules | One rule per easy-to-delete cap from [`ibd-memory.md`](./ibd-memory.md): `pending_blocks` 128, `held_bodies` 320, `MAX_SERVE_BLOCKS` 16, `follow_live` vs `max_outbound`. Each has `lint/ast-grep/fixtures/{good,bad}/`. Today **four** structural rules, **zero** cap rules. | -| 6 | **Q-56** | Miri islands beyond primitives | `cfg(miri)` tests for FFI-free helpers (scriptnum, pack integers) that do not pull secp/store. Never workspace miri. Nightly `miri.yml` is still primitives-only (**Q-53**). | | 7 | **Q-67** | `asked_blocks` clone on hold | `hold_body` clones `asked_blocks` before `held_bodies` insert so the read lock does not overlap the write (`HeldBodies::insert` already takes `&HashSet`). Bound is `MAX_SERVE_BLOCKS` × peers. Follow-up: pass the read guard with a documented lock order, or keep the clone as a named trade. Owner: `crates/rbitcoin-net/src/chain.rs`. | R-ids were the 2026-08-12 slice. Canonical id is **bold**. Do not start