Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow disabling the checksum with alternate Display #478

Merged
merged 3 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This can be written as self.engine.input(s).map_err(|| fmt::Error)

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