Skip to content

Commit

Permalink
Merge rust-bitcoin/rust-miniscript#478: allow disabling the checksum …
Browse files Browse the repository at this point in the history
…with alternate `Display`

7f5bbdf fixes for 1.41.0 (Andrew Poelstra)
7577e8c checksum: use wrapper around fmt::Formatter for all descriptor types (Andrew Poelstra)
84f0292 checksum: pull computation apart into an engine (Andrew Poelstra)

Pull request description:

  Fixes #477

ACKs for top commit:
  sanket1729:
    ACK 7f5bbdf. The nit is just a stylistic comment.

Tree-SHA512: f4a5bd67bf15d6e59258ec42f0b046b8321103f1baa0e2f893d4e3379623c636f2092596c0c79a0d3b949a436307f052efca72ad2784e42dd23ad861e3036e85
  • Loading branch information
sanket1729 committed Oct 18, 2022
2 parents d5615ac + 7f5bbdf commit 72dab64
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 87 deletions.
16 changes: 9 additions & 7 deletions src/descriptor/bare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use core::fmt;
use bitcoin::blockdata::script;
use bitcoin::{Address, Network, Script};

use super::checksum::{desc_checksum, verify_checksum};
use super::checksum::{self, verify_checksum};
use crate::expression::{self, FromTree};
use crate::miniscript::context::ScriptContext;
use crate::policy::{semantic, Liftable};
Expand Down Expand Up @@ -132,9 +132,10 @@ impl<Pk: MiniscriptKey> fmt::Debug for Bare<Pk> {

impl<Pk: MiniscriptKey> fmt::Display for Bare<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let desc = format!("{}", self.ms);
let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?;
write!(f, "{}#{}", &desc, &checksum)
use fmt::Write;
let mut wrapped_f = checksum::Formatter::new(f);
write!(wrapped_f, "{}", self.ms)?;
wrapped_f.write_checksum_if_not_alt()
}
}

Expand Down Expand Up @@ -285,9 +286,10 @@ impl<Pk: MiniscriptKey> fmt::Debug for Pkh<Pk> {

impl<Pk: MiniscriptKey> fmt::Display for Pkh<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let desc = format!("pkh({})", self.pk);
let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?;
write!(f, "{}#{}", &desc, &checksum)
use fmt::Write;
let mut wrapped_f = checksum::Formatter::new(f);
write!(wrapped_f, "pkh({})", self.pk)?;
wrapped_f.write_checksum_if_not_alt()
}
}

Expand Down
145 changes: 110 additions & 35 deletions src/descriptor/checksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
//! This module contains a re-implementation of the function used by Bitcoin Core to calculate the
//! checksum of a descriptor

use core::fmt;
use core::iter::FromIterator;

use crate::prelude::*;
use crate::Error;

const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
const CHECKSUM_CHARSET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";

fn poly_mod(mut c: u64, val: u64) -> u64 {
let c0 = c >> 35;
Expand Down Expand Up @@ -39,40 +40,9 @@ fn poly_mod(mut c: u64, val: u64) -> u64 {
/// descriptor string is syntactically correct or not.
/// This only computes the checksum
pub fn desc_checksum(desc: &str) -> Result<String, Error> {
let mut c = 1;
let mut cls = 0;
let mut clscount = 0;

for ch in desc.chars() {
let pos = INPUT_CHARSET.find(ch).ok_or_else(|| {
Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch))
})? as u64;
c = poly_mod(c, pos & 31);
cls = cls * 3 + (pos >> 5);
clscount += 1;
if clscount == 3 {
c = poly_mod(c, cls);
cls = 0;
clscount = 0;
}
}
if clscount > 0 {
c = poly_mod(c, cls);
}
(0..8).for_each(|_| c = poly_mod(c, 0));
c ^= 1;

let mut chars = Vec::with_capacity(8);
for j in 0..8 {
chars.push(
CHECKSUM_CHARSET
.chars()
.nth(((c >> (5 * (7 - j))) & 31) as usize)
.unwrap(),
);
}

Ok(String::from_iter(chars))
let mut eng = Engine::new();
eng.input(desc)?;
Ok(eng.checksum())
}

/// Helper function for FromStr for various
Expand All @@ -99,6 +69,111 @@ pub(super) fn verify_checksum(s: &str) -> Result<&str, Error> {
}
Ok(desc_str)
}

/// An engine to compute a checksum from a string
pub struct Engine {
c: u64,
cls: u64,
clscount: u64,
}

impl Engine {
/// Construct an engine with no input
pub fn new() -> Self {
Engine {
c: 1,
cls: 0,
clscount: 0,
}
}

/// Checksum some data
///
/// If this function returns an error, the `Engine` will be left in an indeterminate
/// state! It is safe to continue feeding it data but the result will not be meaningful.
pub fn input(&mut self, s: &str) -> Result<(), Error> {
for ch in s.chars() {
let pos = INPUT_CHARSET.find(ch).ok_or_else(|| {
Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch))
})? as u64;
self.c = poly_mod(self.c, pos & 31);
self.cls = self.cls * 3 + (pos >> 5);
self.clscount += 1;
if self.clscount == 3 {
self.c = poly_mod(self.c, self.cls);
self.cls = 0;
self.clscount = 0;
}
}
Ok(())
}

/// Obtain the checksum of all the data thus-far fed to the engine
pub fn checksum_chars(&mut self) -> [char; 8] {
if self.clscount > 0 {
self.c = poly_mod(self.c, self.cls);
}
(0..8).for_each(|_| self.c = poly_mod(self.c, 0));
self.c ^= 1;

let mut chars = [0 as char; 8];
for j in 0..8 {
chars[j] = CHECKSUM_CHARSET[((self.c >> (5 * (7 - j))) & 31) as usize] as char;
}
chars
}

/// Obtain the checksum of all the data thus-far fed to the engine
pub fn checksum(&mut self) -> String {
String::from_iter(self.checksum_chars().iter().copied())
}
}

/// A wrapper around a `fmt::Formatter` which provides checksumming ability
pub struct Formatter<'f, 'a> {
fmt: &'f mut fmt::Formatter<'a>,
eng: Engine,
}

impl<'f, 'a> Formatter<'f, 'a> {
/// Contruct a new `Formatter`, wrapping a given `fmt::Formatter`
pub fn new(f: &'f mut fmt::Formatter<'a>) -> Self {
Formatter {
fmt: f,
eng: Engine::new(),
}
}

/// Writes the checksum into the underlying `fmt::Formatter`
pub fn write_checksum(&mut self) -> fmt::Result {
use fmt::Write;
self.fmt.write_char('#')?;
for ch in self.eng.checksum_chars().iter().copied() {
self.fmt.write_char(ch)?;
}
Ok(())
}

/// Writes the checksum into the underlying `fmt::Formatter`, unless it has "alternate" display on
pub fn write_checksum_if_not_alt(&mut self) -> fmt::Result {
if !self.fmt.alternate() {
self.write_checksum()?;
}
Ok(())
}
}

impl<'f, 'a> fmt::Write for Formatter<'f, 'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.fmt.write_str(s)?;
if self.eng.input(s).is_ok() {
Ok(())
} else {
Err(fmt::Error)
}
}
}

#[cfg(test)]
mod test {
use core::str;
Expand Down
104 changes: 92 additions & 12 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,25 +808,25 @@ impl_from_str!(
impl<Pk: MiniscriptKey> fmt::Debug for Descriptor<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Descriptor::Bare(ref sub) => write!(f, "{:?}", sub),
Descriptor::Pkh(ref pkh) => write!(f, "{:?}", pkh),
Descriptor::Wpkh(ref wpkh) => write!(f, "{:?}", wpkh),
Descriptor::Sh(ref sub) => write!(f, "{:?}", sub),
Descriptor::Wsh(ref sub) => write!(f, "{:?}", sub),
Descriptor::Tr(ref tr) => write!(f, "{:?}", tr),
Descriptor::Bare(ref sub) => fmt::Debug::fmt(sub, f),
Descriptor::Pkh(ref pkh) => fmt::Debug::fmt(pkh, f),
Descriptor::Wpkh(ref wpkh) => fmt::Debug::fmt(wpkh, f),
Descriptor::Sh(ref sub) => fmt::Debug::fmt(sub, f),
Descriptor::Wsh(ref sub) => fmt::Debug::fmt(sub, f),
Descriptor::Tr(ref tr) => fmt::Debug::fmt(tr, f),
}
}
}

impl<Pk: MiniscriptKey> fmt::Display for Descriptor<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Descriptor::Bare(ref sub) => write!(f, "{}", sub),
Descriptor::Pkh(ref pkh) => write!(f, "{}", pkh),
Descriptor::Wpkh(ref wpkh) => write!(f, "{}", wpkh),
Descriptor::Sh(ref sub) => write!(f, "{}", sub),
Descriptor::Wsh(ref sub) => write!(f, "{}", sub),
Descriptor::Tr(ref tr) => write!(f, "{}", tr),
Descriptor::Bare(ref sub) => fmt::Display::fmt(sub, f),
Descriptor::Pkh(ref pkh) => fmt::Display::fmt(pkh, f),
Descriptor::Wpkh(ref wpkh) => fmt::Display::fmt(wpkh, f),
Descriptor::Sh(ref sub) => fmt::Display::fmt(sub, f),
Descriptor::Wsh(ref sub) => fmt::Display::fmt(sub, f),
Descriptor::Tr(ref tr) => fmt::Display::fmt(tr, f),
}
}
}
Expand Down Expand Up @@ -1757,4 +1757,84 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
Ok(Some((1, expected_concrete)))
);
}

#[test]
fn display_alternate() {
let bare = StdDescriptor::from_str(
"pk(020000000000000000000000000000000000000000000000000000000000000002)",
)
.unwrap();
assert_eq!(
format!("{}", bare),
"pk(020000000000000000000000000000000000000000000000000000000000000002)#7yxkn84h",
);
assert_eq!(
format!("{:#}", bare),
"pk(020000000000000000000000000000000000000000000000000000000000000002)",
);

let pkh = StdDescriptor::from_str(
"pkh(020000000000000000000000000000000000000000000000000000000000000002)",
)
.unwrap();
assert_eq!(
format!("{}", pkh),
"pkh(020000000000000000000000000000000000000000000000000000000000000002)#ma7nspkf",
);
assert_eq!(
format!("{:#}", pkh),
"pkh(020000000000000000000000000000000000000000000000000000000000000002)",
);

let wpkh = StdDescriptor::from_str(
"wpkh(020000000000000000000000000000000000000000000000000000000000000002)",
)
.unwrap();
assert_eq!(
format!("{}", wpkh),
"wpkh(020000000000000000000000000000000000000000000000000000000000000002)#d3xz2xye",
);
assert_eq!(
format!("{:#}", wpkh),
"wpkh(020000000000000000000000000000000000000000000000000000000000000002)",
);

let shwpkh = StdDescriptor::from_str(
"sh(wpkh(020000000000000000000000000000000000000000000000000000000000000002))",
)
.unwrap();
assert_eq!(
format!("{}", shwpkh),
"sh(wpkh(020000000000000000000000000000000000000000000000000000000000000002))#45zpjtet",
);
assert_eq!(
format!("{:#}", shwpkh),
"sh(wpkh(020000000000000000000000000000000000000000000000000000000000000002))",
);

let wsh = StdDescriptor::from_str("wsh(1)").unwrap();
assert_eq!(format!("{}", wsh), "wsh(1)#mrg7xj7p");
assert_eq!(format!("{:#}", wsh), "wsh(1)");

let sh = StdDescriptor::from_str("sh(1)").unwrap();
assert_eq!(format!("{}", sh), "sh(1)#l8r75ggs");
assert_eq!(format!("{:#}", sh), "sh(1)");

let shwsh = StdDescriptor::from_str("sh(wsh(1))").unwrap();
assert_eq!(format!("{}", shwsh), "sh(wsh(1))#hcyfl07f");
assert_eq!(format!("{:#}", shwsh), "sh(wsh(1))");

let tr = StdDescriptor::from_str(
"tr(020000000000000000000000000000000000000000000000000000000000000002)",
)
.unwrap();
assert_eq!(
format!("{}", tr),
"tr(020000000000000000000000000000000000000000000000000000000000000002)#8hc7wq5h",
);
assert_eq!(
format!("{:#}", tr),
"tr(020000000000000000000000000000000000000000000000000000000000000002)",
);
}
}
28 changes: 16 additions & 12 deletions src/descriptor/segwitv0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use core::fmt;

use bitcoin::{self, Address, Network, Script};

use super::checksum::{desc_checksum, verify_checksum};
use super::checksum::{self, verify_checksum};
use super::SortedMultiVec;
use crate::expression::{self, FromTree};
use crate::miniscript::context::{ScriptContext, ScriptContextError};
Expand Down Expand Up @@ -68,11 +68,9 @@ impl<Pk: MiniscriptKey> Wsh<Pk> {
}

/// Get the descriptor without the checksum
#[deprecated(since = "8.0.0", note = "use format!(\"{:#}\") instead")]
pub fn to_string_no_checksum(&self) -> String {
match self.inner {
WshInner::SortedMulti(ref smv) => format!("wsh({})", smv),
WshInner::Ms(ref ms) => format!("wsh({})", ms),
}
format!("{:#}", self)
}

/// Checks whether the descriptor is safe.
Expand Down Expand Up @@ -229,9 +227,13 @@ impl<Pk: MiniscriptKey> fmt::Debug for Wsh<Pk> {

impl<Pk: MiniscriptKey> fmt::Display for Wsh<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let desc = self.to_string_no_checksum();
let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?;
write!(f, "{}#{}", &desc, &checksum)
use fmt::Write;
let mut wrapped_f = checksum::Formatter::new(f);
match self.inner {
WshInner::SortedMulti(ref smv) => write!(wrapped_f, "wsh({})", smv)?,
WshInner::Ms(ref ms) => write!(wrapped_f, "wsh({})", ms)?,
}
wrapped_f.write_checksum_if_not_alt()
}
}

Expand Down Expand Up @@ -307,8 +309,9 @@ impl<Pk: MiniscriptKey> Wpkh<Pk> {
}

/// Get the descriptor without the checksum
#[deprecated(since = "8.0.0", note = "use format!(\"{:#}\") instead")]
pub fn to_string_no_checksum(&self) -> String {
format!("wpkh({})", self.pk)
format!("{:#}", self)
}

/// Checks whether the descriptor is safe.
Expand Down Expand Up @@ -398,9 +401,10 @@ impl<Pk: MiniscriptKey> fmt::Debug for Wpkh<Pk> {

impl<Pk: MiniscriptKey> fmt::Display for Wpkh<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let desc = self.to_string_no_checksum();
let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?;
write!(f, "{}#{}", &desc, &checksum)
use fmt::Write;
let mut wrapped_f = checksum::Formatter::new(f);
write!(wrapped_f, "wpkh({})", self.pk)?;
wrapped_f.write_checksum_if_not_alt()
}
}

Expand Down
Loading

0 comments on commit 72dab64

Please sign in to comment.