Skip to content

Commit

Permalink
btc: add multisig support
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Aug 14, 2024
1 parent 23de208 commit cacfd92
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 8 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG-npm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

## Unreleased

## 0.6.0

- btc: add support for multisig script configs
7 changes: 7 additions & 0 deletions CHANGELOG-rust.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

## Unreleased

## 0.5.0

- btc: add `make_script_config_multisig()`
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "bitbox-api"
authors = ["Marko Bencun <[email protected]>"]
version = "0.4.1"
version = "0.5.0"
homepage = "https://bitbox.swiss/"
repository = "https://github.com/BitBoxSwiss/bitbox-api-rs/"
readme = "README-rust.md"
Expand Down
2 changes: 1 addition & 1 deletion NPM_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.0
0.6.0
2 changes: 1 addition & 1 deletion sandbox/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 74 additions & 3 deletions sandbox/src/Bitcoin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ function BtcXPub({ bb02 } : Props) {
<ShowError err={err} />
</form>
</div>

);
}

Expand Down Expand Up @@ -195,7 +194,7 @@ function BtcSignPSBT({ bb02 }: Props) {
}

return (
<div>
<div>
<h4>Sign PSBT</h4>
<form className="verticalForm" onSubmit={submitForm}>
<label>
Expand Down Expand Up @@ -295,7 +294,7 @@ function BtcSignMessage({ bb02 }: Props) {
<button type='submit' disabled={running}>Sign message</button>
{result ? (
<div className="resultContainer">
<label>Result:
<label>Result:
{
<textarea
rows={32}
Expand All @@ -313,6 +312,75 @@ function BtcSignMessage({ bb02 }: Props) {

}

function BtcMultisigAddress({ bb02 }: Props) {
const [coin, setCoin] = useState<bitbox.BtcCoin>('tbtc');
const [multisigScriptType, setMultisigScriptType] = useState<bitbox.BtcMultisigScriptType>('p2wsh');
const [running, setRunning] = useState(false);
const [result, setResult] = useState('');
const [err, setErr] = useState<bitbox.Error>();

const keypath = "m/48'/1'/0'/2'";
const someXPub = "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk";

const submitForm = async (e: FormEvent) => {
e.preventDefault();
setRunning(true);
setResult('');
setErr(undefined);
try {
const ourXPub = await bb02.btcXpub(coin, keypath, 'tpub', false);
const scriptConfig: bitbox.BtcScriptConfig = {
multisig: {
threshold: 1,
xpubs: [ourXPub, someXPub],
ourXpubIndex: 0,
scriptType: multisigScriptType,
},
};
const is_script_config_registered = await bb02.btcIsScriptConfigRegistered(coin, scriptConfig, keypath);
if (!is_script_config_registered) {
await bb02.btcRegisterScriptConfig(coin, scriptConfig, keypath, 'autoXpubTpub', undefined);
}
const address = await bb02.btcAddress(coin, keypath + "/0/10", scriptConfig, true);
setResult(address);
} catch (err) {
setErr(bitbox.ensureError(err));
} finally {
setRunning(false);
}
}

return (
<div>
<h4>Multisig</h4>
<form className="verticalForm" onSubmit={submitForm}>
Address for a multisig wallet using the BitBox02 xpub at
<pre>{keypath}</pre>
<p>and some other arbitrary xpub: <code>{someXPub}</code></p>
<label>
Coin
<select value={coin} onChange={(e: ChangeEvent<HTMLSelectElement>) => setCoin(e.target.value as bitbox.BtcCoin)}>
{btcCoinOptions.map(option => <option key={option} value={option}>{option}</option>)}
</select>
</label>
<label>
Script type
<select value={multisigScriptType} onChange={(e: ChangeEvent<HTMLSelectElement>) => setMultisigScriptType(e.target.value as bitbox.BtcMultisigScriptType)}>
{['p2wsh', 'p2wshP2sh'].map(option => <option key={option} value={option}>{option}</option>)}
</select>
</label>
<button type='submit' disabled={running}>Multisig address</button>
{result ? (
<div className="resultContainer">
<label>Result: <b><code>{result}</code></b></label>
</div>
) : null }
<ShowError err={err} />
</form>
</div>
);
}

function BtcMiniscriptAddress({ bb02 }: Props) {
const [running, setRunning] = useState(false);
const [result, setResult] = useState('');
Expand Down Expand Up @@ -391,6 +459,9 @@ export function Bitcoin({ bb02 } : Props) {
<div className="action">
<BtcSignMessage bb02={bb02} />
</div>
<div className="action">
<BtcMultisigAddress bb02={bb02} />
</div>
<div className="action">
<BtcMiniscriptAddress bb02={bb02} />
</div>
Expand Down
4 changes: 4 additions & 0 deletions scripts/build-protos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ fn add_serde_attrs(c: &mut prost_build::Config) {
"shiftcrypto.bitbox02.BTCScriptConfig.config.simple_type",
"serde(deserialize_with = \"crate::btc::serde_deserialize_simple_type\")",
),
(
"shiftcrypto.bitbox02.BTCScriptConfig.config.multisig",
"serde(deserialize_with = \"crate::btc::serde_deserialize_multisig\")",
),
(
"shiftcrypto.bitbox02.RootFingerprintResponse.fingerprint",
"serde(deserialize_with = \"hex::serde::deserialize\")",
Expand Down
52 changes: 52 additions & 0 deletions src/btc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,39 @@ where
Ok(pb::btc_script_config::SimpleType::deserialize(deserializer)?.into())
}

#[cfg(feature = "wasm")]
pub(crate) fn serde_deserialize_multisig<'de, D>(
deserializer: D,
) -> Result<pb::btc_script_config::Multisig, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
use std::str::FromStr;

#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct Multisig {
threshold: u32,
xpubs: Vec<String>,
our_xpub_index: u32,
script_type: pb::btc_script_config::multisig::ScriptType,
}
let ms = Multisig::deserialize(deserializer)?;
let xpubs = ms
.xpubs
.iter()
.map(|s| Xpub::from_str(s.as_str()))
.collect::<Result<Vec<Xpub>, _>>()
.map_err(serde::de::Error::custom)?;
Ok(pb::btc_script_config::Multisig {
threshold: ms.threshold,
xpubs: xpubs.iter().map(convert_xpub).collect(),
our_xpub_index: ms.our_xpub_index,
script_type: ms.script_type.into(),
})
}

#[cfg(feature = "wasm")]
#[derive(serde::Deserialize)]
pub(crate) struct SerdeScriptConfig(pb::btc_script_config::Config);
Expand Down Expand Up @@ -500,6 +533,25 @@ impl From<KeyOriginInfo> for pb::KeyOriginInfo {
}
}

/// Create a multi-sig script config.
pub fn make_script_config_multisig(
threshold: u32,
xpubs: &[bitcoin::bip32::Xpub],
our_xpub_index: u32,
script_type: pb::btc_script_config::multisig::ScriptType,
) -> pb::BtcScriptConfig {
pb::BtcScriptConfig {
config: Some(pb::btc_script_config::Config::Multisig(
pb::btc_script_config::Multisig {
threshold,
xpubs: xpubs.iter().map(convert_xpub).collect(),
our_xpub_index,
script_type: script_type as _,
},
)),
}
}

/// Create a wallet policy script config according to the wallet policies BIP:
/// <https://github.com/bitcoin/bips/pull/1389>
///
Expand Down
4 changes: 4 additions & 0 deletions src/shiftcrypto.bitbox02.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,10 @@ pub mod btc_script_config {
)]
SimpleType(i32),
#[prost(message, tag = "2")]
#[cfg_attr(
feature = "wasm",
serde(deserialize_with = "crate::btc::serde_deserialize_multisig")
)]
Multisig(Multisig),
#[prost(message, tag = "3")]
Policy(Policy),
Expand Down
9 changes: 8 additions & 1 deletion src/wasm/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@ type KeyOriginInfo = {
xpub: XPub;
};
type BtcRegisterXPubType = 'autoElectrum' | 'autoXpubTpub';
type BtcMultisigScriptType = 'p2wsh' | 'p2wshP2sh';
type BtcMultisig = {
threshold: number;
xpubs: XPub[];
ourXpubIndex: number;
scriptType: BtcMultisigScriptType;
};
type BtcPolicy = { policy: string; keys: KeyOriginInfo[] };
type BtcScriptConfig = { simpleType: BtcSimpleType; } | { policy: BtcPolicy };
type BtcScriptConfig = { simpleType: BtcSimpleType; } | { multisig: BtcMultisig } | { policy: BtcPolicy };
type BtcScriptConfigWithKeypath = {
scriptConfig: BtcScriptConfig;
keypath: Keypath;
Expand Down
Loading

0 comments on commit cacfd92

Please sign in to comment.