Skip to content

Commit bf86f73

Browse files
committed
Add walletsignmessage and verifymessage rpc commands
1 parent 5e6a63a commit bf86f73

File tree

3 files changed

+119
-17
lines changed

3 files changed

+119
-17
lines changed

node/src/rpc.rs

+51-14
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,18 @@ use bdk::{
1515
};
1616
use jsonrpsee::{core::async_trait, proc_macros::rpc, server::Server, types::ErrorObjectOwned};
1717
use log::info;
18-
use protocol::{
19-
bitcoin,
20-
bitcoin::{
21-
bip32::Xpriv,
22-
Network::{Regtest, Testnet},
23-
OutPoint,
24-
},
25-
constants::ChainAnchor,
26-
hasher::{BaseHash, KeyHasher, SpaceKey},
27-
prepare::DataSource,
28-
slabel::SLabel,
29-
validate::TxChangeSet,
30-
FullSpaceOut, SpaceOut,
31-
};
18+
use protocol::{bitcoin, bitcoin::{
19+
bip32::Xpriv,
20+
Network::{Regtest, Testnet},
21+
OutPoint,
22+
}, constants::ChainAnchor, hasher::{BaseHash, KeyHasher, SpaceKey}, prepare::DataSource, slabel::SLabel, validate::TxChangeSet, Bytes, FullSpaceOut, SpaceOut};
3223
use serde::{Deserialize, Serialize};
3324
use tokio::{
3425
select,
3526
sync::{broadcast, mpsc, oneshot, RwLock},
3627
task::JoinSet,
3728
};
29+
use protocol::bitcoin::secp256k1;
3830
use wallet::{bdk_wallet as bdk, bdk_wallet::template::Bip86, bitcoin::hashes::Hash, export::WalletExport, Balance, DoubleUtxo, Listing, SpacesWallet, WalletConfig, WalletDescriptors, WalletInfo, WalletOutput};
3931

4032
use crate::{
@@ -58,6 +50,13 @@ pub struct ServerInfo {
5850
pub tip: ChainAnchor,
5951
}
6052

53+
#[derive(Debug, Clone, Serialize, Deserialize)]
54+
pub struct SignedMessage {
55+
pub space: String,
56+
pub message: protocol::Bytes,
57+
pub signature: secp256k1::schnorr::Signature,
58+
}
59+
6160
pub enum ChainStateCommand {
6261
CheckPackage {
6362
txs: Vec<String>,
@@ -99,6 +98,10 @@ pub enum ChainStateCommand {
9998
listing: Listing,
10099
resp: Responder<anyhow::Result<()>>,
101100
},
101+
VerifyMessage {
102+
msg: SignedMessage,
103+
resp: Responder<anyhow::Result<()>>,
104+
},
102105
}
103106

104107
#[derive(Clone)]
@@ -153,6 +156,12 @@ pub trait Rpc {
153156
#[method(name = "walletimport")]
154157
async fn wallet_import(&self, wallet: WalletExport) -> Result<(), ErrorObjectOwned>;
155158

159+
#[method(name = "verifymessage")]
160+
async fn verify_message(&self, msg: SignedMessage) -> Result<(), ErrorObjectOwned>;
161+
162+
#[method(name = "walletsignmessage")]
163+
async fn wallet_sign_message(&self, wallet: &str, space: &str, msg: protocol::Bytes) -> Result<SignedMessage, ErrorObjectOwned>;
164+
156165
#[method(name = "walletgetinfo")]
157166
async fn wallet_get_info(&self, name: &str) -> Result<WalletInfo, ErrorObjectOwned>;
158167

@@ -797,13 +806,28 @@ impl RpcServer for RpcServerImpl {
797806
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))
798807
}
799808

809+
async fn wallet_sign_message(&self, wallet: &str, space: &str, msg: Bytes) -> Result<SignedMessage, ErrorObjectOwned> {
810+
self.wallet(&wallet)
811+
.await?
812+
.send_sign_message(space, msg)
813+
.await
814+
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))
815+
}
816+
800817
async fn verify_listing(&self, listing: Listing) -> Result<(), ErrorObjectOwned> {
801818
self.store
802819
.verify_listing(listing)
803820
.await
804821
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))
805822
}
806823

824+
async fn verify_message(&self, msg: SignedMessage) -> Result<(), ErrorObjectOwned> {
825+
self.store
826+
.verify_message(msg)
827+
.await
828+
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))
829+
}
830+
807831
async fn wallet_list_transactions(
808832
&self,
809833
wallet: &str,
@@ -1006,6 +1030,11 @@ impl AsyncChainState {
10061030
ChainStateCommand::VerifyListing { listing, resp } => {
10071031
_ = resp.send(SpacesWallet::verify_listing::<Sha256>(chain_state, &listing).map(|_| ()));
10081032
}
1033+
ChainStateCommand::VerifyMessage { msg, resp } => {
1034+
_ = resp.send(SpacesWallet::verify_message::<Sha256>(
1035+
chain_state, &msg.space, msg.message.as_slice(), &msg.signature
1036+
).map(|_| ()));
1037+
}
10091038
}
10101039
}
10111040

@@ -1047,6 +1076,14 @@ impl AsyncChainState {
10471076
resp_rx.await?
10481077
}
10491078

1079+
pub async fn verify_message(&self, msg: SignedMessage) -> anyhow::Result<()> {
1080+
let (resp, resp_rx) = oneshot::channel();
1081+
self.sender
1082+
.send(ChainStateCommand::VerifyMessage { msg, resp })
1083+
.await?;
1084+
resp_rx.await?
1085+
}
1086+
10501087
pub async fn get_rollout(&self, target: usize) -> anyhow::Result<Vec<RolloutEntry>> {
10511088
let (resp, resp_rx) = oneshot::channel();
10521089
self.sender

node/src/wallets.rs

+36
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use wallet::{address::SpaceAddress, bdk_wallet::{
2222
use crate::{checker::TxChecker, config::ExtendedNetwork, node::BlockSource, rpc::{RpcWalletRequest, RpcWalletTxBuilder, WalletLoadRequest}, source::{
2323
BitcoinBlockSource, BitcoinRpc, BitcoinRpcError, BlockEvent, BlockFetchError, BlockFetcher,
2424
}, std_wait, store::{ChainState, LiveSnapshot, Sha256}};
25+
use crate::rpc::SignedMessage;
2526

2627
const MEMPOOL_CHECK_INTERVAL: Duration = Duration::from_millis(
2728
if cfg!(debug_assertions) { 500 } else { 10_000 }
@@ -111,6 +112,11 @@ pub enum WalletCommand {
111112
resp: crate::rpc::Responder<anyhow::Result<Balance>>,
112113
},
113114
UnloadWallet,
115+
SignMessage {
116+
space: String,
117+
msg: protocol::Bytes,
118+
resp: crate::rpc::Responder<anyhow::Result<SignedMessage>>,
119+
}
114120
}
115121

116122
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ValueEnum)]
@@ -375,6 +381,20 @@ impl RpcWallet {
375381
WalletCommand::Sell { space, price, resp } => {
376382
_ = resp.send(wallet.sell::<Sha256>(state, &space, Amount::from_sat(price)));
377383
}
384+
WalletCommand::SignMessage { space, msg, resp } => {
385+
match wallet.sign_message::<Sha256>(state, &space, msg.as_slice()) {
386+
Ok(signature) => {
387+
_ = resp.send(Ok(SignedMessage {
388+
space,
389+
message: msg,
390+
signature,
391+
}));
392+
}
393+
Err(err) => {
394+
_ = resp.send(Err(err));
395+
}
396+
}
397+
}
378398
}
379399
Ok(())
380400
}
@@ -1172,6 +1192,22 @@ impl RpcWallet {
11721192
resp_rx.await?
11731193
}
11741194

1195+
pub async fn send_sign_message(
1196+
&self,
1197+
space: &str,
1198+
msg: protocol::Bytes
1199+
) -> anyhow::Result<SignedMessage> {
1200+
let (resp, resp_rx) = oneshot::channel();
1201+
self.sender
1202+
.send(WalletCommand::SignMessage {
1203+
space: space.to_string(),
1204+
msg,
1205+
resp,
1206+
})
1207+
.await?;
1208+
resp_rx.await?
1209+
}
1210+
11751211
pub async fn send_list_transactions(
11761212
&self,
11771213
count: usize,

node/tests/integration_tests.rs

+32-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::{path::PathBuf, str::FromStr};
2-
use protocol::{bitcoin::{Amount, FeeRate}, constants::RENEWAL_INTERVAL, script::SpaceScript, Covenant};
2+
use protocol::{bitcoin::{Amount, FeeRate}, constants::RENEWAL_INTERVAL, script::SpaceScript, Bytes, Covenant};
33
use spaced::{
44
rpc::{
55
BidParams, ExecuteParams, OpenParams, RegisterParams, RpcClient, RpcWalletRequest,
@@ -1025,6 +1025,34 @@ async fn it_should_allow_buy_sell(rig: &TestRig) -> anyhow::Result<()> {
10251025
Ok(())
10261026
}
10271027

1028+
async fn it_should_allow_sign_verify_messages(rig: &TestRig) -> anyhow::Result<()> {
1029+
rig.wait_until_wallet_synced(BOB).await.expect("synced");
1030+
1031+
let alice_spaces = rig.spaced.client.wallet_list_spaces(BOB).await.expect("bob spaces");
1032+
let space = alice_spaces.owned.first().expect("bob should have at least 1 space");
1033+
1034+
let space_name = space.spaceout.space.as_ref().unwrap().name.to_string();
1035+
1036+
let msg = Bytes::new(b"hello world".to_vec());
1037+
let signed = rig.spaced.client.wallet_sign_message(BOB, &space_name, msg.clone()).await.expect("sign");
1038+
1039+
println!("signed\n{}", serde_json::to_string_pretty(&signed).unwrap());
1040+
assert_eq!(signed.space, space_name, "bad signer");
1041+
assert_eq!(signed.message.as_slice(), msg.as_slice(), "msg content must match");
1042+
1043+
rig.spaced.client.verify_message(signed.clone()).await.expect("verify");
1044+
1045+
let mut bad_signer = signed.clone();
1046+
bad_signer.space = "@nothanks".to_string();
1047+
rig.spaced.client.verify_message(bad_signer).await.expect_err("bad signer");
1048+
1049+
let mut bad_msg = signed.clone();
1050+
bad_msg.message = Bytes::new(b"hello world 2".to_vec());
1051+
rig.spaced.client.verify_message(bad_msg).await.expect_err("bad msg");
1052+
1053+
Ok(())
1054+
}
1055+
10281056
async fn it_should_handle_reorgs(rig: &TestRig) -> anyhow::Result<()> {
10291057
rig.wait_until_wallet_synced(ALICE).await.expect("synced");
10301058
const NAME: &str = "hello_world";
@@ -1066,10 +1094,11 @@ async fn run_auction_tests() -> anyhow::Result<()> {
10661094
.expect("should not allow register/transfer multiple times");
10671095
it_can_batch_txs(&rig).await.expect("bump fee");
10681096
it_can_use_reserved_op_codes(&rig).await.expect("should use reserved opcodes");
1069-
it_should_allow_buy_sell(&rig).await.expect("should use reserved opcodes");
1097+
it_should_allow_buy_sell(&rig).await.expect("should allow buy sell");
1098+
it_should_allow_sign_verify_messages(&rig).await.expect("should sign verify");
10701099

10711100
// keep reorgs last as it can drop some txs from mempool and mess up wallet state
1072-
it_should_handle_reorgs(&rig).await.expect("should make wallet");
1101+
it_should_handle_reorgs(&rig).await.expect("should handle reorgs wallet");
10731102
Ok(())
10741103
}
10751104

0 commit comments

Comments
 (0)