Skip to content

Commit

Permalink
Sign for inscription and output (#4027)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Nov 26, 2024
1 parent 487b8a9 commit a009a4e
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 31 deletions.
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ pub enum SnafuError {
path: PathBuf,
source: io::Error,
},
#[snafu(display("Unrecognized signer: `{}`", input))]
SignerParse { input: String },
}

impl From<Error> for SnafuError {
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use {
outgoing::Outgoing,
representation::Representation,
settings::Settings,
signer::Signer,
subcommand::{OutputFormat, Subcommand, SubcommandResult},
tally::Tally,
},
Expand Down Expand Up @@ -125,6 +126,7 @@ mod re;
mod representation;
pub mod runes;
pub mod settings;
mod signer;
pub mod subcommand;
mod tally;
pub mod templates;
Expand Down
36 changes: 36 additions & 0 deletions src/signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use super::*;

#[derive(Debug, PartialEq, Clone, DeserializeFromStr)]
pub(crate) enum Signer {
Address(Address<NetworkUnchecked>),
Inscription(InscriptionId),
Output(OutPoint),
}

impl FromStr for Signer {
type Err = SnafuError;

fn from_str(input: &str) -> Result<Self, Self::Err> {
if re::ADDRESS.is_match(input) {
Ok(Signer::Address(
input.parse().snafu_context(error::AddressParse { input })?,
))
} else if re::OUTPOINT.is_match(input) {
Ok(Signer::Output(
input
.parse()
.snafu_context(error::OutPointParse { input })?,
))
} else if re::INSCRIPTION_ID.is_match(input) {
Ok(Signer::Inscription(
input
.parse()
.snafu_context(error::InscriptionIdParse { input })?,
))
} else {
Err(SnafuError::SignerParse {
input: input.to_string(),
})
}
}
}
10 changes: 5 additions & 5 deletions src/subcommand/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use {
group(
ArgGroup::new("input")
.required(true)
.args(&["message", "file"])),
.args(&["text", "file"])),
group(
ArgGroup::new("signature")
.required(true)
Expand All @@ -17,8 +17,8 @@ group(
pub(crate) struct Verify {
#[arg(long, help = "Verify signature made by <ADDRESS>.")]
address: Address<NetworkUnchecked>,
#[arg(long, help = "Verify signature over <MESSAGE>.")]
message: Option<String>,
#[arg(long, help = "Verify signature over <TEXT>.")]
text: Option<String>,
#[arg(long, help = "Verify signature over contents of <FILE>.")]
file: Option<PathBuf>,
#[arg(long, help = "Verify base64-encoded <WITNESS>.")]
Expand All @@ -29,8 +29,8 @@ pub(crate) struct Verify {

impl Verify {
pub(crate) fn run(self) -> SubcommandResult {
let message = if let Some(message) = &self.message {
message.as_bytes()
let message = if let Some(text) = &self.text {
text.as_bytes()
} else if let Some(file) = &self.file {
&fs::read(file)?
} else {
Expand Down
50 changes: 34 additions & 16 deletions src/subcommand/wallet/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,60 @@ use {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Output {
pub address: Address<NetworkUnchecked>,
pub message: Option<String>,
pub witness: String,
}

#[derive(Debug, Parser)]
#[clap(group(
#[clap(
group(
ArgGroup::new("input")
.required(true)
.args(&["message", "file"]))
)]
.args(&["text", "file"])))
]
pub(crate) struct Sign {
#[arg(long, help = "Sign for <ADDRESS>.")]
address: Address<NetworkUnchecked>,
#[arg(long, help = "Sign <MESSAGE>.")]
message: Option<String>,
#[arg(
long,
help = "Sign with public key associated with address, output, or inscription."
)]
signer: Signer,
#[arg(long, help = "Sign <TEXT>.")]
text: 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
.clone()
.require_network(wallet.chain().network())?;
let address = match &self.signer {
Signer::Address(address) => address.clone().require_network(wallet.chain().network())?,
Signer::Inscription(inscription) => Address::from_str(
&wallet
.inscription_info()
.get(inscription)
.ok_or_else(|| anyhow!("inscription {inscription} not in wallet"))?
.address
.clone()
.ok_or_else(|| anyhow!("inscription {inscription} in output without address"))?,
)?
.require_network(wallet.chain().network())?,
Signer::Output(output) => wallet.chain().address_from_script(
&wallet
.utxos()
.get(output)
.ok_or_else(|| anyhow!("output {output} has no address"))?
.script_pubkey,
)?,
};

let message = if let Some(message) = &self.message {
message.as_bytes()
let message = if let Some(text) = &self.text {
text.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_spend = bip322::create_to_spend(&address, message)?;

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

Expand All @@ -64,7 +83,6 @@ impl Sign {

Ok(Some(Box::new(Output {
address: address.as_unchecked().clone(),
message: self.message.clone(),
witness: general_purpose::STANDARD.encode(buffer),
})))
}
Expand Down
8 changes: 4 additions & 4 deletions tests/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ fn verify() {
CommandBuilder::new([
"verify",
"--address", "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
"--message", "Hello World",
"--text", "Hello World",
"--witness", "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
])
.run_and_extract_stdout(),
Expand All @@ -19,7 +19,7 @@ fn verify_fails() {
CommandBuilder::new([
"verify",
"--address", "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
"--message", "Hello World - this should fail",
"--text", "Hello World - this should fail",
"--witness", "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
])
.expected_exit_code(1)
Expand All @@ -32,7 +32,7 @@ fn witness_and_transaction_conflict() {
CommandBuilder::new([
"verify",
"--address", "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
"--message", "Hello World",
"--text", "Hello World",
"--transaction", "asdf",
"--witness", "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
])
Expand All @@ -55,7 +55,7 @@ fn verify_with_transaction() {
"verify",
"--address",
"bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
"--message",
"--text",
"Hello World",
"--transaction",
&tx,
Expand Down
64 changes: 58 additions & 6 deletions tests/wallet/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,20 @@ fn sign() {

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

let message = "HelloWorld";
let text = "HelloWorld";

let sign = CommandBuilder::new(format!(
"wallet sign --address {} --message {message}",
"wallet sign --signer {} --text {text}",
address.clone().assume_checked(),
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<SignOutput>();

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

CommandBuilder::new(format!(
"verify --address {} --message {message} --witness {}",
"verify --address {} --text {text} --witness {}",
address.clone().assume_checked(),
sign.witness,
))
Expand All @@ -61,7 +60,7 @@ fn sign_file() {
let address = addresses.first_key_value().unwrap().0;

let sign = CommandBuilder::new(format!(
"wallet sign --address {} --file hello.txt",
"wallet sign --signer {} --file hello.txt",
address.clone().assume_checked(),
))
.write("hello.txt", "Hello World")
Expand All @@ -70,7 +69,6 @@ fn sign_file() {
.run_and_deserialize_output::<SignOutput>();

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

CommandBuilder::new(format!(
"verify --address {} --file hello.txt --witness {}",
Expand All @@ -94,3 +92,57 @@ fn sign_file() {
.stderr_regex("error: Invalid signature.*")
.run_and_extract_stdout();
}

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

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

create_wallet(&core, &ord);

let (inscription, _reveal) = inscribe(&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 text = "HelloWorld";

let sign = CommandBuilder::new(format!("wallet sign --signer {inscription} --text {text}",))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<SignOutput>();

assert!(addresses.contains_key(&sign.address));
}

#[test]
fn sign_for_output() {
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 output = addresses.first_key_value().unwrap().1[0].output;

let text = "HelloWorld";

let sign = CommandBuilder::new(format!("wallet sign --signer {output} --text {text}",))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<SignOutput>();

assert!(addresses.contains_key(&sign.address));
}

0 comments on commit a009a4e

Please sign in to comment.