Skip to content

Commit 974fced

Browse files
Merge branch 'master' into output_recursive
2 parents f5e988e + a009a4e commit 974fced

File tree

11 files changed

+162
-37
lines changed

11 files changed

+162
-37
lines changed

crates/ordinals/src/charm.rs

+8
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ impl FromStr for Charm {
117117
"lost" => Self::Lost,
118118
"mythic" => Self::Mythic,
119119
"nineball" => Self::Nineball,
120+
"palindrome" => Self::Palindrome,
120121
"rare" => Self::Rare,
121122
"reinscription" => Self::Reinscription,
122123
"unbound" => Self::Unbound,
@@ -153,4 +154,11 @@ mod tests {
153154
let flags = Charm::Coin.unset(flags);
154155
assert!(!Charm::Coin.is_set(flags));
155156
}
157+
158+
#[test]
159+
fn from_str() {
160+
for charm in Charm::ALL {
161+
assert_eq!(charm.to_string().parse::<Charm>().unwrap(), charm);
162+
}
163+
}
156164
}

src/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ pub enum SnafuError {
7474
path: PathBuf,
7575
source: io::Error,
7676
},
77+
#[snafu(display("Unrecognized signer: `{}`", input))]
78+
SignerParse { input: String },
7779
}
7880

7981
impl From<Error> for SnafuError {

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use {
2727
outgoing::Outgoing,
2828
representation::Representation,
2929
settings::Settings,
30+
signer::Signer,
3031
subcommand::{OutputFormat, Subcommand, SubcommandResult},
3132
tally::Tally,
3233
},
@@ -125,6 +126,7 @@ mod re;
125126
mod representation;
126127
pub mod runes;
127128
pub mod settings;
129+
mod signer;
128130
pub mod subcommand;
129131
mod tally;
130132
pub mod templates;

src/signer.rs

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use super::*;
2+
3+
#[derive(Debug, PartialEq, Clone, DeserializeFromStr)]
4+
pub(crate) enum Signer {
5+
Address(Address<NetworkUnchecked>),
6+
Inscription(InscriptionId),
7+
Output(OutPoint),
8+
}
9+
10+
impl FromStr for Signer {
11+
type Err = SnafuError;
12+
13+
fn from_str(input: &str) -> Result<Self, Self::Err> {
14+
if re::ADDRESS.is_match(input) {
15+
Ok(Signer::Address(
16+
input.parse().snafu_context(error::AddressParse { input })?,
17+
))
18+
} else if re::OUTPOINT.is_match(input) {
19+
Ok(Signer::Output(
20+
input
21+
.parse()
22+
.snafu_context(error::OutPointParse { input })?,
23+
))
24+
} else if re::INSCRIPTION_ID.is_match(input) {
25+
Ok(Signer::Inscription(
26+
input
27+
.parse()
28+
.snafu_context(error::InscriptionIdParse { input })?,
29+
))
30+
} else {
31+
Err(SnafuError::SignerParse {
32+
input: input.to_string(),
33+
})
34+
}
35+
}
36+
}

src/subcommand/verify.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use {
88
group(
99
ArgGroup::new("input")
1010
.required(true)
11-
.args(&["message", "file"])),
11+
.args(&["text", "file"])),
1212
group(
1313
ArgGroup::new("signature")
1414
.required(true)
@@ -17,8 +17,8 @@ group(
1717
pub(crate) struct Verify {
1818
#[arg(long, help = "Verify signature made by <ADDRESS>.")]
1919
address: Address<NetworkUnchecked>,
20-
#[arg(long, help = "Verify signature over <MESSAGE>.")]
21-
message: Option<String>,
20+
#[arg(long, help = "Verify signature over <TEXT>.")]
21+
text: Option<String>,
2222
#[arg(long, help = "Verify signature over contents of <FILE>.")]
2323
file: Option<PathBuf>,
2424
#[arg(long, help = "Verify base64-encoded <WITNESS>.")]
@@ -29,8 +29,8 @@ pub(crate) struct Verify {
2929

3030
impl Verify {
3131
pub(crate) fn run(self) -> SubcommandResult {
32-
let message = if let Some(message) = &self.message {
33-
message.as_bytes()
32+
let message = if let Some(text) = &self.text {
33+
text.as_bytes()
3434
} else if let Some(file) = &self.file {
3535
&fs::read(file)?
3636
} else {

src/subcommand/wallet/burn.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ impl Burn {
9999
Ok(Some(Box::new(send::Output {
100100
txid,
101101
psbt,
102-
outgoing: Outgoing::InscriptionId(self.inscription),
102+
asset: Outgoing::InscriptionId(self.inscription),
103103
fee,
104104
})))
105105
}

src/subcommand/wallet/send.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,22 @@ pub(crate) struct Send {
1212
value_name = "AMOUNT"
1313
)]
1414
pub(crate) postage: Option<Amount>,
15+
#[arg(help = "Recipient address")]
1516
address: Address<NetworkUnchecked>,
16-
outgoing: Outgoing,
17+
#[arg(
18+
help = "Outgoing asset formatted as a bitcoin amount, rune amount, sat name, satpoint, or \
19+
inscription ID. Bitcoin amounts are `DECIMAL UNIT` where `UNIT` is one of \
20+
`bit btc cbtc mbtc msat nbtc pbtc sat satoshi ubtc`. Rune amounts are `DECIMAL:RUNE` and \
21+
respect divisibility"
22+
)]
23+
asset: Outgoing,
1724
}
1825

1926
#[derive(Debug, Serialize, Deserialize)]
2027
pub struct Output {
2128
pub txid: Txid,
2229
pub psbt: String,
23-
pub outgoing: Outgoing,
30+
pub asset: Outgoing,
2431
pub fee: u64,
2532
}
2633

@@ -31,7 +38,7 @@ impl Send {
3138
.clone()
3239
.require_network(wallet.chain().network())?;
3340

34-
let unsigned_transaction = match self.outgoing {
41+
let unsigned_transaction = match self.asset {
3542
Outgoing::Amount(amount) => {
3643
Self::create_unsigned_send_amount_transaction(&wallet, address, amount, self.fee_rate)?
3744
}
@@ -79,7 +86,7 @@ impl Send {
7986
Ok(Some(Box::new(Output {
8087
txid,
8188
psbt,
82-
outgoing: self.outgoing,
89+
asset: self.asset,
8390
fee,
8491
})))
8592
}

src/subcommand/wallet/sign.rs

+34-16
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,60 @@ use {
66
#[derive(Debug, PartialEq, Serialize, Deserialize)]
77
pub struct Output {
88
pub address: Address<NetworkUnchecked>,
9-
pub message: Option<String>,
109
pub witness: String,
1110
}
1211

1312
#[derive(Debug, Parser)]
14-
#[clap(group(
13+
#[clap(
14+
group(
1515
ArgGroup::new("input")
1616
.required(true)
17-
.args(&["message", "file"]))
18-
)]
17+
.args(&["text", "file"])))
18+
]
1919
pub(crate) struct Sign {
20-
#[arg(long, help = "Sign for <ADDRESS>.")]
21-
address: Address<NetworkUnchecked>,
22-
#[arg(long, help = "Sign <MESSAGE>.")]
23-
message: Option<String>,
20+
#[arg(
21+
long,
22+
help = "Sign with public key associated with address, output, or inscription."
23+
)]
24+
signer: Signer,
25+
#[arg(long, help = "Sign <TEXT>.")]
26+
text: Option<String>,
2427
#[arg(long, help = "Sign contents of <FILE>.")]
2528
file: Option<PathBuf>,
2629
}
2730

2831
impl Sign {
2932
pub(crate) fn run(&self, wallet: Wallet) -> SubcommandResult {
30-
let address = &self
31-
.address
32-
.clone()
33-
.require_network(wallet.chain().network())?;
33+
let address = match &self.signer {
34+
Signer::Address(address) => address.clone().require_network(wallet.chain().network())?,
35+
Signer::Inscription(inscription) => Address::from_str(
36+
&wallet
37+
.inscription_info()
38+
.get(inscription)
39+
.ok_or_else(|| anyhow!("inscription {inscription} not in wallet"))?
40+
.address
41+
.clone()
42+
.ok_or_else(|| anyhow!("inscription {inscription} in output without address"))?,
43+
)?
44+
.require_network(wallet.chain().network())?,
45+
Signer::Output(output) => wallet.chain().address_from_script(
46+
&wallet
47+
.utxos()
48+
.get(output)
49+
.ok_or_else(|| anyhow!("output {output} has no address"))?
50+
.script_pubkey,
51+
)?,
52+
};
3453

35-
let message = if let Some(message) = &self.message {
36-
message.as_bytes()
54+
let message = if let Some(text) = &self.text {
55+
text.as_bytes()
3756
} else if let Some(file) = &self.file {
3857
&fs::read(file)?
3958
} else {
4059
unreachable!()
4160
};
4261

43-
let to_spend = bip322::create_to_spend(address, message)?;
62+
let to_spend = bip322::create_to_spend(&address, message)?;
4463

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

@@ -64,7 +83,6 @@ impl Sign {
6483

6584
Ok(Some(Box::new(Output {
6685
address: address.as_unchecked().clone(),
67-
message: self.message.clone(),
6886
witness: general_purpose::STANDARD.encode(buffer),
6987
})))
7088
}

tests/verify.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ fn verify() {
66
CommandBuilder::new([
77
"verify",
88
"--address", "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
9-
"--message", "Hello World",
9+
"--text", "Hello World",
1010
"--witness", "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
1111
])
1212
.run_and_extract_stdout(),
@@ -19,7 +19,7 @@ fn verify_fails() {
1919
CommandBuilder::new([
2020
"verify",
2121
"--address", "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
22-
"--message", "Hello World - this should fail",
22+
"--text", "Hello World - this should fail",
2323
"--witness", "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
2424
])
2525
.expected_exit_code(1)
@@ -32,7 +32,7 @@ fn witness_and_transaction_conflict() {
3232
CommandBuilder::new([
3333
"verify",
3434
"--address", "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
35-
"--message", "Hello World",
35+
"--text", "Hello World",
3636
"--transaction", "asdf",
3737
"--witness", "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
3838
])
@@ -55,7 +55,7 @@ fn verify_with_transaction() {
5555
"verify",
5656
"--address",
5757
"bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
58-
"--message",
58+
"--text",
5959
"Hello World",
6060
"--transaction",
6161
&tx,

tests/wallet/send.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ fn send_dry_run() {
671671
.to_sat(),
672672
output.fee
673673
);
674-
assert_eq!(output.outgoing, Outgoing::InscriptionId(inscription));
674+
assert_eq!(output.asset, Outgoing::InscriptionId(inscription));
675675
}
676676

677677
#[test]

tests/wallet/sign.rs

+58-6
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,20 @@ fn sign() {
2020

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

23-
let message = "HelloWorld";
23+
let text = "HelloWorld";
2424

2525
let sign = CommandBuilder::new(format!(
26-
"wallet sign --address {} --message {message}",
26+
"wallet sign --signer {} --text {text}",
2727
address.clone().assume_checked(),
2828
))
2929
.core(&core)
3030
.ord(&ord)
3131
.run_and_deserialize_output::<SignOutput>();
3232

3333
assert_eq!(address, &sign.address);
34-
assert_eq!(message, &sign.message.unwrap());
3534

3635
CommandBuilder::new(format!(
37-
"verify --address {} --message {message} --witness {}",
36+
"verify --address {} --text {text} --witness {}",
3837
address.clone().assume_checked(),
3938
sign.witness,
4039
))
@@ -61,7 +60,7 @@ fn sign_file() {
6160
let address = addresses.first_key_value().unwrap().0;
6261

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

7271
assert_eq!(address, &sign.address);
73-
assert!(sign.message.is_none());
7472

7573
CommandBuilder::new(format!(
7674
"verify --address {} --file hello.txt --witness {}",
@@ -94,3 +92,57 @@ fn sign_file() {
9492
.stderr_regex("error: Invalid signature.*")
9593
.run_and_extract_stdout();
9694
}
95+
96+
#[test]
97+
fn sign_for_inscription() {
98+
let core = mockcore::spawn();
99+
100+
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
101+
102+
create_wallet(&core, &ord);
103+
104+
let (inscription, _reveal) = inscribe(&core, &ord);
105+
106+
core.mine_blocks(1);
107+
108+
let addresses = CommandBuilder::new("wallet addresses")
109+
.core(&core)
110+
.ord(&ord)
111+
.run_and_deserialize_output::<BTreeMap<Address<NetworkUnchecked>, Vec<AddressesOutput>>>();
112+
113+
let text = "HelloWorld";
114+
115+
let sign = CommandBuilder::new(format!("wallet sign --signer {inscription} --text {text}",))
116+
.core(&core)
117+
.ord(&ord)
118+
.run_and_deserialize_output::<SignOutput>();
119+
120+
assert!(addresses.contains_key(&sign.address));
121+
}
122+
123+
#[test]
124+
fn sign_for_output() {
125+
let core = mockcore::spawn();
126+
127+
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
128+
129+
create_wallet(&core, &ord);
130+
131+
core.mine_blocks(1);
132+
133+
let addresses = CommandBuilder::new("wallet addresses")
134+
.core(&core)
135+
.ord(&ord)
136+
.run_and_deserialize_output::<BTreeMap<Address<NetworkUnchecked>, Vec<AddressesOutput>>>();
137+
138+
let output = addresses.first_key_value().unwrap().1[0].output;
139+
140+
let text = "HelloWorld";
141+
142+
let sign = CommandBuilder::new(format!("wallet sign --signer {output} --text {text}",))
143+
.core(&core)
144+
.ord(&ord)
145+
.run_and_deserialize_output::<SignOutput>();
146+
147+
assert!(addresses.contains_key(&sign.address));
148+
}

0 commit comments

Comments
 (0)