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

feat: added sorted_multisig #258

Merged
merged 26 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d674030
feat: add submit-signed-psbt
thevaibhav-dixit Jun 27, 2023
48ea100
chore: some refactoring
thevaibhav-dixit Jun 28, 2023
76aaf27
chore: remove redundant clone
thevaibhav-dixit Jun 28, 2023
27715ae
chore: add error for missing sig and witness
thevaibhav-dixit Jun 28, 2023
875cd85
chore: add psbt_validator and corresponding tests
thevaibhav-dixit Jun 29, 2023
88bf712
chore: remove redundant errors
thevaibhav-dixit Jun 29, 2023
2584ef5
chore: address pr reviews
thevaibhav-dixit Jun 29, 2023
d917a6d
feat: add support for sorted_multisig
thevaibhav-dixit Jun 30, 2023
04e4048
chore: fix naming
thevaibhav-dixit Jun 30, 2023
01398e4
chore: address pr reviews
thevaibhav-dixit Jun 30, 2023
d94e4b1
refactor: flatten match in batch_signing
bodymindarts Jun 30, 2023
326a585
chore: add e2e test for multisig signing
thevaibhav-dixit Jul 3, 2023
f000632
chore: parameterize functions
thevaibhav-dixit Jul 3, 2023
52869ef
chore: change psbt_validator logic
thevaibhav-dixit Jul 3, 2023
d5436e2
chore: use lnd_key and improve validation logic
thevaibhav-dixit Jul 4, 2023
77e7567
chore: fix name
thevaibhav-dixit Jul 4, 2023
3cc27ad
test: extract retry_cmd in bria_init helper
bodymindarts Jul 4, 2023
4d93d40
chore: incomplete finalization step
bodymindarts Jul 4, 2023
419df37
chore: address pr reviews
thevaibhav-dixit Jul 4, 2023
8ed7722
chore: create a new bitcoin-signer to sign psbt
thevaibhav-dixit Jul 4, 2023
ec5911c
chore: add new bitcoin-signer descriptor
thevaibhav-dixit Jul 4, 2023
e3b5092
revert: revert last 2 commits
thevaibhav-dixit Jul 4, 2023
e45d51c
chore: add some more events in batch_signing
bodymindarts Jul 4, 2023
a0bc7ca
chore: add another bitcoind signer
thevaibhav-dixit Jul 4, 2023
a8fd55f
chore: add better assertions
thevaibhav-dixit Jul 4, 2023
46bbb92
chore: add remote signer
thevaibhav-dixit Jul 5, 2023
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
15 changes: 15 additions & 0 deletions proto/api/bria.proto
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ service BriaService {
rpc ListXpubs (ListXpubsRequest) returns(ListXpubsResponse) {}
rpc SetSignerConfig (SetSignerConfigRequest) returns (SetSignerConfigResponse) {}

rpc SubmitSignedPsbt (SubmitSignedPsbtRequest) returns (SubmitSignedPsbtResponse) {}

rpc CreateWallet (CreateWalletRequest) returns (CreateWalletResponse) {}
rpc ListWallets (ListWalletsRequest) returns (ListWalletsResponse) {}
rpc GetWalletBalanceSummary (GetWalletBalanceSummaryRequest) returns (GetWalletBalanceSummaryResponse) {}
Expand Down Expand Up @@ -104,6 +106,14 @@ message BitcoindSignerConfig {

message SetSignerConfigResponse {}

message SubmitSignedPsbtRequest {
string batch_id = 1;
string xpub_ref = 2;
string signed_psbt = 3;
}

message SubmitSignedPsbtResponse {}

message KeychainConfig {
message Wpkh {
string xpub = 1;
Expand All @@ -113,9 +123,14 @@ message KeychainConfig {
string external = 1;
string internal = 2;
}
message SortedMultisig {
repeated string xpubs = 1;
uint32 threshold = 2;
}
oneof config {
Wpkh wpkh = 1;
Descriptors descriptors = 2;
SortedMultisig sorted_multisig = 3;
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/api/server/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,21 @@ impl From<ApplicationError> for tonic::Status {
ApplicationError::DestinationBlocked(_) => {
tonic::Status::permission_denied(err.to_string())
}
ApplicationError::SigningSessionNotFoundForBatchId(_) => {
tonic::Status::not_found(err.to_string())
}
ApplicationError::SigningSessionNotFoundForXPubId(_) => {
tonic::Status::not_found(err.to_string())
}
ApplicationError::WalletError(WalletError::PsbtDoesNotHaveValidSignatures) => {
tonic::Status::invalid_argument(err.to_string())
}
ApplicationError::WalletError(WalletError::UnsignedTxnMismatch) => {
tonic::Status::invalid_argument(err.to_string())
}
ApplicationError::CouldNotParseIncomingPsbt(_) => {
tonic::Status::invalid_argument(err.to_string())
}
_ => tonic::Status::internal(err.to_string()),
}
}
Expand All @@ -608,6 +623,7 @@ impl ToTraceLevel for tonic::Status {
tonic::Code::NotFound => tracing::Level::WARN,
tonic::Code::AlreadyExists => tracing::Level::WARN,
tonic::Code::PermissionDenied => tracing::Level::WARN,
tonic::Code::InvalidArgument => tracing::Level::WARN,
_ => tracing::Level::ERROR,
}
}
Expand Down
44 changes: 43 additions & 1 deletion src/api/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,38 @@ impl BriaService for Bria {
.await
}

#[instrument(name = "bria.submit_signed_psbt", skip_all, fields(error, error.level, error.message), err)]
async fn submit_signed_psbt(
&self,
request: Request<SubmitSignedPsbtRequest>,
) -> Result<Response<SubmitSignedPsbtResponse>, Status> {
crate::tracing::record_error(|| async move {
extract_tracing(&request);
let key = extract_api_token(&request)?;
let profile = self.app.authenticate(key).await?;
let request = request.into_inner();
let SubmitSignedPsbtRequest {
batch_id,
xpub_ref,
signed_psbt,
} = request;
self.app
.submit_signed_psbt(
profile,
batch_id
.parse()
.map_err(ApplicationError::CouldNotParseIncomingUuid)?,
bodymindarts marked this conversation as resolved.
Show resolved Hide resolved
xpub_ref,
signed_psbt
.parse::<bitcoin::psbt::PartiallySignedTransaction>()
.map_err(ApplicationError::CouldNotParseIncomingPsbt)?,
)
.await?;
Ok(Response::new(SubmitSignedPsbtResponse {}))
})
.await
}

#[instrument(name = "bria.create_wallet", skip_all, fields(error, error.level, error.message), err)]
async fn create_wallet(
&self,
Expand Down Expand Up @@ -200,6 +232,16 @@ impl BriaService for Bria {
.create_descriptors_wallet(profile, name, external, internal)
.await?
}
Some(KeychainConfig {
config:
Some(keychain_config::Config::SortedMultisig(
keychain_config::SortedMultisig {
xpubs,
threshold,
})),
}) => {
self.app.create_sorted_multisig_wallet(profile, name, xpubs, threshold).await?
}
_ => {
return Err(Status::invalid_argument("invalid keychain config"));
}
Expand Down Expand Up @@ -364,7 +406,7 @@ impl BriaService for Bria {
.app
.find_address_by_external_id(profile, external_id)
.await?;
let wallet_id = address.wallet_id.clone().to_string();
let wallet_id = address.wallet_id.to_string();
let proto_address: proto::WalletAddress = proto::WalletAddress::from(address);
Ok(Response::new(FindAddressByExternalIdResponse {
wallet_id,
Expand Down
28 changes: 22 additions & 6 deletions src/app/error.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
use thiserror::Error;

use crate::{
address::error::AddressError, batch::error::BatchError, bdk::error::BdkError,
descriptor::error::DescriptorError, fees::error::FeeEstimationError, job::error::JobError,
ledger::error::LedgerError, outbox::error::OutboxError, payout::error::PayoutError,
payout_queue::error::PayoutQueueError, primitives::PayoutDestination,
profile::error::ProfileError, signing_session::error::SigningSessionError,
utxo::error::UtxoError, wallet::error::WalletError, xpub::error::XPubError,
address::error::AddressError,
batch::error::BatchError,
bdk::error::BdkError,
descriptor::error::DescriptorError,
fees::error::FeeEstimationError,
job::error::JobError,
ledger::error::LedgerError,
outbox::error::OutboxError,
payout::error::PayoutError,
payout_queue::error::PayoutQueueError,
primitives::{bitcoin, PayoutDestination},
profile::error::ProfileError,
signing_session::error::SigningSessionError,
utxo::error::UtxoError,
wallet::error::WalletError,
xpub::error::XPubError,
};

#[derive(Error, Debug)]
Expand Down Expand Up @@ -53,4 +63,10 @@ pub enum ApplicationError {
CouldNotParseIncomingUuid(uuid::Error),
#[error("DestinationBlocked - sending to '{0}' is prohibited")]
DestinationBlocked(PayoutDestination),
#[error("Signing Session not found for batch id: {0}")]
SigningSessionNotFoundForBatchId(crate::primitives::BatchId),
#[error("Signing Session not found for xpub id: {0}")]
SigningSessionNotFoundForXPubId(crate::primitives::XPubId),
#[error("Could not parse incoming psbt: {0}")]
CouldNotParseIncomingPsbt(bitcoin::psbt::PsbtParseError),
}
70 changes: 70 additions & 0 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,50 @@ impl App {
Ok(())
}

#[instrument(name = "app.submit_signed_psbt", skip(self), err)]
pub async fn submit_signed_psbt(
&self,
profile: Profile,
batch_id: BatchId,
xpub_ref: String,
signed_psbt: bitcoin::psbt::PartiallySignedTransaction,
) -> Result<(), ApplicationError> {
let xpub = self
.xpubs
.find_from_ref(
profile.account_id,
xpub_ref
.parse::<XPubRef>()
.expect("ref should always parse"),
)
.await?;
let xpub_id = xpub.id();
let xpub = xpub.value;
let unsigned_psbt = self
.batches
.find_by_id(profile.account_id, batch_id)
.await?
.unsigned_psbt;
psbt_validator::validate_psbt(&signed_psbt, xpub, &unsigned_psbt)?;
let mut sessions = self
.signing_sessions
.list_for_batch(profile.account_id, batch_id)
.await?
.ok_or(ApplicationError::SigningSessionNotFoundForBatchId(batch_id))?
.xpub_sessions;
let session = sessions
.get_mut(&xpub_id)
.ok_or_else(|| ApplicationError::SigningSessionNotFoundForXPubId(xpub_id))?;

let mut tx = self.pool.begin().await?;
session.submit_externally_signed_psbt(signed_psbt);
self.signing_sessions
.update_sessions(&mut tx, &sessions)
.await?;
job::spawn_all_batch_signings(tx, std::iter::once((profile.account_id, batch_id))).await?;
Ok(())
}

#[instrument(name = "app.create_wpkh_wallet", skip(self), err)]
pub async fn create_wpkh_wallet(
&self,
Expand Down Expand Up @@ -242,6 +286,32 @@ impl App {
self.create_wallet(profile, wallet_name, keychain).await
}

#[instrument(name = "app.create_sorted_multisig_wallet", skip(self), err)]
pub async fn create_sorted_multisig_wallet(
&self,
profile: Profile,
wallet_name: String,
xpubs: Vec<String>,
threshold: u32,
) -> Result<(WalletId, Vec<XPubId>), ApplicationError> {
let xpub_values: Vec<XPub> = futures::future::try_join_all(
xpubs
.iter()
.map(|xpub| {
xpub.parse::<XPubRef>()
.expect("xpub_ref should always parse")
})
.map(|xpub_ref| self.xpubs.find_from_ref(profile.account_id, xpub_ref)),
)
.await?
.into_iter()
.map(|xpub| xpub.value)
.collect();

let keychain = KeychainConfig::sorted_multisig(xpub_values, threshold);
self.create_wallet(profile, wallet_name, keychain).await
}

async fn create_wallet(
&self,
profile: Profile,
Expand Down
19 changes: 19 additions & 0 deletions src/cli/api_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,25 @@ impl ApiClient {
output_json(response)
}

pub async fn submit_signed_psbt(
&self,
batch_id: String,
xpub_ref: String,
signed_psbt: String,
) -> anyhow::Result<()> {
let request = tonic::Request::new(proto::SubmitSignedPsbtRequest {
batch_id,
xpub_ref,
signed_psbt,
});
let response = self
.connect()
.await?
.submit_signed_psbt(self.inject_auth_token(request)?)
.await?;
output_json(response)
}

pub async fn create_wallet(
&self,
name: String,
Expand Down
43 changes: 43 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,25 @@ enum Command {
#[clap(subcommand)]
command: SetSignerConfigCommand,
},
/// Submit a signed psbt
SubmitSignedPsbt {
#[clap(
short,
long,
value_parser,
default_value = "http://localhost:2742",
env = "BRIA_API_URL"
)]
url: Option<Url>,
#[clap(env = "BRIA_API_KEY", default_value = "")]
api_key: String,
#[clap(short, long)]
batch_id: String,
#[clap(short, long)]
xpub_ref: String,
#[clap(short, long)]
signed_psbt: String,
},
/// Create a wallet from imported xpubs
CreateWallet {
#[clap(
Expand Down Expand Up @@ -553,6 +572,12 @@ enum CreateWalletCommand {
#[clap(short, long)]
change_descriptor: String,
},
SortedMultisig {
#[clap(short, long, num_args(2..=15) )]
xpub: Vec<String>,
#[clap(short, long)]
threshold: u32,
},
}

#[derive(Subcommand)]
Expand Down Expand Up @@ -695,6 +720,18 @@ pub async fn run() -> anyhow::Result<()> {
let client = api_client(cli.bria_home, url, api_key);
client.set_signer_config(xpub, command).await?;
}
Command::SubmitSignedPsbt {
url,
api_key,
batch_id,
xpub_ref,
signed_psbt,
} => {
let client = api_client(cli.bria_home, url, api_key);
client
.submit_signed_psbt(batch_id, xpub_ref, signed_psbt)
.await?;
}
Command::CreateWallet {
url,
api_key,
Expand Down Expand Up @@ -1124,6 +1161,12 @@ impl From<CreateWalletCommand> for crate::api::proto::keychain_config::Config {
external: descriptor,
internal: change_descriptor,
}),
CreateWalletCommand::SortedMultisig { xpub, threshold } => {
Config::SortedMultisig(SortedMultisig {
xpubs: xpub,
threshold,
})
}
}
}
}
Loading
Loading