Skip to content

Commit ab0fbc5

Browse files
authored
Merge pull request #209 from BitGo/BTC-3033.add-bip322-storage
feat(wasm-utxo): add BIP322 message storage and retrieval
2 parents b0d1ced + 034a37f commit ab0fbc5

5 files changed

Lines changed: 86 additions & 2 deletions

File tree

packages/wasm-utxo/js/bip322/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,14 @@ export function addBip322Input(psbt: BitGoPsbt, params: AddBip322InputParams): n
133133
);
134134
}
135135

136+
/**
137+
* Get the BIP322 message stored at a PSBT input index.
138+
* Returns null if no message is stored.
139+
*/
140+
export function getBip322Message(psbt: BitGoPsbt, inputIndex: number): string | null {
141+
return Bip322Namespace.get_bip322_message(psbt.wasm, inputIndex) ?? null;
142+
}
143+
136144
/**
137145
* Verify a single input of a BIP-0322 transaction proof
138146
*

packages/wasm-utxo/src/bip322/bitgo_psbt.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
//! with BitGo fixed-script wallets.
55
66
use crate::fixed_script_wallet::bitgo_psbt::{
7-
create_bip32_derivation, create_tap_bip32_derivation, BitGoPsbt,
7+
create_bip32_derivation, create_tap_bip32_derivation, find_kv, BitGoKeyValue, BitGoPsbt,
8+
ProprietaryKeySubtype,
89
};
910
use crate::fixed_script_wallet::wallet_scripts::{
1011
build_multisig_script_2_of_3, build_p2tr_ns_script, ScriptP2tr,
@@ -194,9 +195,38 @@ pub fn add_bip322_input(
194195
}
195196
}
196197

198+
// Store the BIP322 message as a proprietary field for later extraction
199+
let (prop_key, prop_value) = BitGoKeyValue::new(
200+
ProprietaryKeySubtype::Bip322Message,
201+
vec![],
202+
message.as_bytes().to_vec(),
203+
)
204+
.to_key_value();
205+
inner_psbt.inputs[input_index]
206+
.proprietary
207+
.insert(prop_key, prop_value);
208+
197209
Ok(input_index)
198210
}
199211

212+
/// Extract the BIP322 message stored in a PSBT input's proprietary fields.
213+
/// Returns None if no message is stored at that index.
214+
pub fn get_bip322_message(psbt: &BitGoPsbt, input_index: usize) -> Result<Option<String>, String> {
215+
let input = psbt
216+
.psbt()
217+
.inputs
218+
.get(input_index)
219+
.ok_or_else(|| format!("Input index {} out of bounds", input_index))?;
220+
221+
let mut iter = find_kv(ProprietaryKeySubtype::Bip322Message, &input.proprietary);
222+
match iter.next() {
223+
Some(kv) => String::from_utf8(kv.value)
224+
.map(Some)
225+
.map_err(|e| format!("Invalid UTF-8 in BIP322 message: {e}")),
226+
None => Ok(None),
227+
}
228+
}
229+
200230
/// Verify a single input of a BIP-0322 transaction proof
201231
///
202232
/// # Arguments

packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub mod zcash_psbt;
1717
use crate::Network;
1818
pub use dash_psbt::DashBitGoPsbt;
1919
use miniscript::bitcoin::{psbt::Psbt, secp256k1, CompressedPublicKey, Txid};
20-
pub use propkv::{BitGoKeyValue, ProprietaryKeySubtype, WasmUtxoVersionInfo, BITGO};
20+
pub use propkv::{find_kv, BitGoKeyValue, ProprietaryKeySubtype, WasmUtxoVersionInfo, BITGO};
2121
pub use sighash::validate_sighash_type;
2222
pub use zcash_psbt::{
2323
decode_zcash_transaction_meta, ZcashBitGoPsbt, ZcashTransactionMeta,

packages/wasm-utxo/src/wasm/bip322.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,17 @@ impl Bip322Namespace {
204204
Ok(indices.into_iter().map(|i| i as u32).collect())
205205
}
206206

207+
/// Get the BIP322 message stored at a PSBT input index.
208+
/// Returns null if no message is stored.
209+
#[wasm_bindgen]
210+
pub fn get_bip322_message(
211+
psbt: &super::fixed_script_wallet::BitGoPsbt,
212+
input_index: u32,
213+
) -> Result<Option<String>, WasmUtxoError> {
214+
bitgo_psbt::get_bip322_message(&psbt.psbt, input_index as usize)
215+
.map_err(|e| WasmUtxoError::new(&e))
216+
}
217+
207218
/// Verify a single input of a BIP-0322 transaction proof using pubkeys directly
208219
///
209220
/// # Arguments

packages/wasm-utxo/test/bip322/index.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,41 @@ describe("BIP-0322", function () {
9191
});
9292
}, /BIP-0322 PSBT must have version 0/);
9393
});
94+
95+
it("should store and retrieve BIP322 message via proprietary field", function () {
96+
const psbt = fixedScriptWallet.BitGoPsbt.createEmpty("testnet", walletKeys, { version: 0 });
97+
98+
bip322.addBip322Input(psbt, {
99+
message: "Hello, BitGo!",
100+
scriptId: { chain: 10, index: 0 },
101+
rootWalletKeys: walletKeys,
102+
});
103+
104+
assert.strictEqual(bip322.getBip322Message(psbt, 0), "Hello, BitGo!");
105+
});
106+
107+
it("should store different messages for multiple inputs", function () {
108+
const psbt = fixedScriptWallet.BitGoPsbt.createEmpty("testnet", walletKeys, { version: 0 });
109+
110+
bip322.addBip322Input(psbt, {
111+
message: "Message A",
112+
scriptId: { chain: 10, index: 0 },
113+
rootWalletKeys: walletKeys,
114+
});
115+
bip322.addBip322Input(psbt, {
116+
message: "Message B",
117+
scriptId: { chain: 10, index: 1 },
118+
rootWalletKeys: walletKeys,
119+
});
120+
121+
assert.strictEqual(bip322.getBip322Message(psbt, 0), "Message A");
122+
assert.strictEqual(bip322.getBip322Message(psbt, 1), "Message B");
123+
});
124+
125+
it("should throw for input index out of bounds in getBip322Message", function () {
126+
const psbt = fixedScriptWallet.BitGoPsbt.createEmpty("testnet", walletKeys, { version: 0 });
127+
assert.throws(() => bip322.getBip322Message(psbt, 0), /out of bounds/);
128+
});
94129
});
95130

96131
describe("sign and verify per-input", function () {

0 commit comments

Comments
 (0)