Skip to content

Commit

Permalink
BIP322 sign file (ordinals#4026)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Nov 7, 2024
1 parent b5c7f88 commit c525fcd
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 20 deletions.
40 changes: 27 additions & 13 deletions src/subcommand/verify.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
use super::*;
use {
super::*,
base64::{engine::general_purpose, Engine},
};

#[derive(Debug, Parser)]
#[clap(group(
#[clap(
group(
ArgGroup::new("input")
.required(true)
.args(&["message", "file"])),
group(
ArgGroup::new("signature")
.required(true)
.args(&["transaction", "witness"]))
Expand All @@ -10,7 +18,9 @@ pub(crate) struct Verify {
#[arg(long, help = "Verify signature made by <ADDRESS>.")]
address: Address<NetworkUnchecked>,
#[arg(long, help = "Verify signature over <MESSAGE>.")]
message: String,
message: Option<String>,
#[arg(long, help = "Verify signature over contents of <FILE>.")]
file: Option<PathBuf>,
#[arg(long, help = "Verify base64-encoded <WITNESS>.")]
witness: Option<String>,
#[arg(long, help = "Verify base64-encoded <TRANSACTION>.")]
Expand All @@ -19,18 +29,22 @@ pub(crate) struct Verify {

impl Verify {
pub(crate) fn run(self) -> SubcommandResult {
let message = if let Some(message) = &self.message {
message.as_bytes()
} else if let Some(file) = &self.file {
&fs::read(file)?
} else {
unreachable!()
};

if let Some(witness) = self.witness {
bip322::verify_simple_encoded(
&self.address.assume_checked().to_string(),
&self.message,
&witness,
)?;
let mut cursor = bitcoin::io::Cursor::new(general_purpose::STANDARD.decode(witness)?);
let witness = Witness::consensus_decode_from_finite_reader(&mut cursor)?;
bip322::verify_simple(&self.address.assume_checked(), message, witness)?;
} else if let Some(transaction) = self.transaction {
bip322::verify_full_encoded(
&self.address.assume_checked().to_string(),
&self.message,
&transaction,
)?;
let mut cursor = bitcoin::io::Cursor::new(general_purpose::STANDARD.decode(transaction)?);
let transaction = Transaction::consensus_decode_from_finite_reader(&mut cursor)?;
bip322::verify_full(&self.address.assume_checked(), message, transaction)?;
} else {
unreachable!();
}
Expand Down
30 changes: 24 additions & 6 deletions src/subcommand/wallet/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,41 @@ use {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Output {
pub address: Address<NetworkUnchecked>,
pub message: String,
pub message: Option<String>,
pub witness: String,
}

#[derive(Debug, Parser)]
#[clap(group(
ArgGroup::new("input")
.required(true)
.args(&["message", "file"]))
)]
pub(crate) struct Sign {
#[arg(long, help = "Sign for <ADDRESS>.")]
address: Address<NetworkUnchecked>,
#[arg(long, help = "Sign <MESSAGE>.")]
message: String,
message: Option<String>,
#[arg(long, help = "Sign contents of <FILE>.")]
file: Option<PathBuf>,
}

impl Sign {
pub(crate) fn run(self, wallet: Wallet) -> SubcommandResult {
let address = self.address.require_network(wallet.chain().network())?;
pub(crate) fn run(&self, wallet: Wallet) -> SubcommandResult {
let address = &self
.address
.clone()
.require_network(wallet.chain().network())?;

let to_spend = bip322::create_to_spend(&address, self.message.as_bytes())?;
let message = if let Some(message) = &self.message {
message.as_bytes()
} else if let Some(file) = &self.file {
&fs::read(file)?
} else {
unreachable!()
};

let to_spend = bip322::create_to_spend(address, message)?;

let to_sign = bip322::create_to_sign(&to_spend, None)?;

Expand All @@ -46,7 +64,7 @@ impl Sign {

Ok(Some(Box::new(Output {
address: address.as_unchecked().clone(),
message: self.message,
message: self.message.clone(),
witness: general_purpose::STANDARD.encode(buffer),
})))
}
Expand Down
54 changes: 53 additions & 1 deletion tests/wallet/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn sign() {
.run_and_deserialize_output::<SignOutput>();

assert_eq!(address, &sign.address);
assert_eq!(message, &sign.message);
assert_eq!(message, &sign.message.unwrap());

CommandBuilder::new(format!(
"verify --address {} --message {message} --witness {}",
Expand All @@ -42,3 +42,55 @@ fn sign() {
.ord(&ord)
.run_and_extract_stdout();
}

#[test]
fn sign_file() {
let core = mockcore::spawn();

let ord = TestServer::spawn_with_server_args(&core, &[], &[]);

create_wallet(&core, &ord);

core.mine_blocks(1);

let addresses = CommandBuilder::new("wallet addresses")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<BTreeMap<Address<NetworkUnchecked>, Vec<AddressesOutput>>>();

let address = addresses.first_key_value().unwrap().0;

let sign = CommandBuilder::new(format!(
"wallet sign --address {} --file hello.txt",
address.clone().assume_checked(),
))
.write("hello.txt", "Hello World")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<SignOutput>();

assert_eq!(address, &sign.address);
assert!(sign.message.is_none());

CommandBuilder::new(format!(
"verify --address {} --file hello.txt --witness {}",
address.clone().assume_checked(),
sign.witness,
))
.write("hello.txt", "Hello World")
.core(&core)
.ord(&ord)
.run_and_extract_stdout();

CommandBuilder::new(format!(
"verify --address {} --file hello.txt --witness {}",
address.clone().assume_checked(),
sign.witness,
))
.write("hello.txt", "FAIL")
.core(&core)
.ord(&ord)
.expected_exit_code(1)
.stderr_regex("error: Invalid signature.*")
.run_and_extract_stdout();
}

0 comments on commit c525fcd

Please sign in to comment.