Skip to content

Commit c2926c0

Browse files
Merge pull request #167 from BitGo/BTC-2650.fix-paygo-check
fix(wasm-utxo): correct paygo address validation in tests
2 parents 5489ce8 + c8dde9b commit c2926c0

14 files changed

Lines changed: 93 additions & 66 deletions

File tree

packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,15 @@ export type AddWalletOutputOptions = {
115115
value: bigint;
116116
};
117117

118+
export type ParseTransactionOptions = {
119+
replayProtection: ReplayProtectionArg;
120+
payGoPubkeys?: ECPairArg[];
121+
};
122+
123+
export type ParseOutputsOptions = {
124+
payGoPubkeys?: ECPairArg[];
125+
};
126+
118127
export class BitGoPsbt implements IPsbtIntrospectionWithAddress {
119128
protected constructor(protected _wasm: WasmBitGoPsbt) {}
120129

@@ -424,18 +433,18 @@ export class BitGoPsbt implements IPsbtIntrospectionWithAddress {
424433
/**
425434
* Parse transaction with wallet keys to identify wallet inputs/outputs
426435
* @param walletKeys - The wallet keys to use for identification
427-
* @param replayProtection - Scripts that are allowed as inputs without wallet validation
428-
* @param payGoPubkeys - Optional public keys for PayGo attestation verification
436+
* @param options - Options for parsing
437+
* @param options.replayProtection - Scripts that are allowed as inputs without wallet validation
438+
* @param options.payGoPubkeys - Optional public keys for PayGo attestation verification
429439
* @returns Parsed transaction information
430440
*/
431441
parseTransactionWithWalletKeys(
432442
walletKeys: WalletKeysArg,
433-
replayProtection: ReplayProtectionArg,
434-
payGoPubkeys?: ECPairArg[],
443+
options: ParseTransactionOptions,
435444
): ParsedTransaction {
436445
const keys = RootWalletKeys.from(walletKeys);
437-
const rp = ReplayProtection.from(replayProtection, this._wasm.network());
438-
const pubkeys = payGoPubkeys?.map((arg) => ECPair.from(arg).wasm);
446+
const rp = ReplayProtection.from(options.replayProtection, this._wasm.network());
447+
const pubkeys = options.payGoPubkeys?.map((arg) => ECPair.from(arg).wasm);
439448
return this._wasm.parse_transaction_with_wallet_keys(
440449
keys.wasm,
441450
rp.wasm,
@@ -451,16 +460,17 @@ export class BitGoPsbt implements IPsbtIntrospectionWithAddress {
451460
* wallet than the inputs.
452461
*
453462
* @param walletKeys - The wallet keys to use for identification
454-
* @param payGoPubkeys - Optional public keys for PayGo attestation verification
463+
* @param options - Optional options for parsing
464+
* @param options.payGoPubkeys - Optional public keys for PayGo attestation verification
455465
* @returns Array of parsed outputs
456466
* @note This method does NOT validate wallet inputs. It only parses outputs.
457467
*/
458468
parseOutputsWithWalletKeys(
459469
walletKeys: WalletKeysArg,
460-
payGoPubkeys?: ECPairArg[],
470+
options?: ParseOutputsOptions,
461471
): ParsedOutput[] {
462472
const keys = RootWalletKeys.from(walletKeys);
463-
const pubkeys = payGoPubkeys?.map((arg) => ECPair.from(arg).wasm);
473+
const pubkeys = options?.payGoPubkeys?.map((arg) => ECPair.from(arg).wasm);
464474
return this._wasm.parse_outputs_with_wallet_keys(keys.wasm, pubkeys) as ParsedOutput[];
465475
}
466476

@@ -719,7 +729,7 @@ export class BitGoPsbt implements IPsbtIntrospectionWithAddress {
719729
* const counterpartyPsbt = BitGoPsbt.fromBytes(counterpartyPsbtBytes, network);
720730
* psbt.combineMusig2Nonces(counterpartyPsbt);
721731
* // Sign MuSig2 key path inputs
722-
* const parsed = psbt.parseTransactionWithWalletKeys(walletKeys, replayProtection);
732+
* const parsed = psbt.parseTransactionWithWalletKeys(walletKeys, { replayProtection });
723733
* for (let i = 0; i < parsed.inputs.length; i++) {
724734
* if (parsed.inputs[i].scriptType === "p2trMusig2KeyPath") {
725735
* psbt.sign(i, userXpriv);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export {
2828
type AddOutputOptions,
2929
type AddWalletInputOptions,
3030
type AddWalletOutputOptions,
31+
type ParseTransactionOptions,
32+
type ParseOutputsOptions,
3133
} from "./BitGoPsbt.js";
3234

3335
// Zcash-specific PSBT subclass

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3979,7 +3979,7 @@ mod tests {
39793979
psbt.unsigned_tx.output.push(miniscript::bitcoin::TxOut {
39803980
value: miniscript::bitcoin::Amount::from_sat(10000),
39813981
script_pubkey: miniscript::bitcoin::ScriptBuf::from_hex(
3982-
"76a91479b000887626b294a914501a4cd226b58b23598388ac",
3982+
"76a9147f90f63fed017815f1da8bea299da27945a17bda88ac",
39833983
)
39843984
.unwrap(),
39853985
});
@@ -3998,7 +3998,7 @@ mod tests {
39983998
assert!(result.is_ok(), "Should add attestation successfully");
39993999

40004000
// Extract and verify
4001-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c";
4001+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c";
40024002
let psbt = bitgo_psbt.psbt();
40034003

40044004
// Verify it was added (with address, no verification)
@@ -4091,9 +4091,9 @@ mod tests {
40914091
psbt.unsigned_tx.output.push(miniscript::bitcoin::TxOut {
40924092
value: miniscript::bitcoin::Amount::from_sat(10000),
40934093
script_pubkey: miniscript::bitcoin::ScriptBuf::from_hex(
4094-
"76a91479b000887626b294a914501a4cd226b58b23598388ac",
4094+
"76a9147f90f63fed017815f1da8bea299da27945a17bda88ac",
40954095
)
4096-
.unwrap(), // Address: 1CdWUVacSQQJ617HuNWByGiisEGXGNx2c
4096+
.unwrap(), // Address: 1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c
40974097
});
40984098

40994099
// Add PayGo attestation
@@ -4122,15 +4122,12 @@ mod tests {
41224122
.unwrap();
41234123
let pubkey = secp256k1::PublicKey::from_slice(&pubkey_bytes).unwrap();
41244124

4125-
// Note: Signature verification with bitcoinjs-message format is not fully working yet
4126-
// So parsing with pubkey will fail validation
4127-
let parsed_result = bitgo_psbt.parse_outputs_with_wallet_keys(&wallet_keys, &[pubkey]);
4125+
let parsed_result = bitgo_psbt
4126+
.parse_outputs_with_wallet_keys(&wallet_keys, &[pubkey])
4127+
.unwrap();
41284128

4129-
// We expect this to fail validation for now
4130-
assert!(
4131-
parsed_result.is_err(),
4132-
"Expected verification to fail with current signature format"
4133-
);
4129+
// The PayGo output should have paygo: true (verified)
4130+
assert!(parsed_result[output_index].paygo);
41344131
}
41354132

41364133
crate::test_psbt_fixtures!(test_parse_transaction_with_wallet_keys, network, format, {

packages/wasm-utxo/src/paygo/attestation.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ mod tests {
6565
fn test_new_valid_entropy() {
6666
let entropy = vec![0u8; 64];
6767
let signature = vec![1u8; 65];
68-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c".to_string();
68+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c".to_string();
6969

7070
let attestation =
7171
PayGoAttestation::new(entropy.clone(), signature.clone(), address.clone());
@@ -81,7 +81,7 @@ mod tests {
8181
fn test_new_invalid_entropy_length() {
8282
let entropy = vec![0u8; 32]; // Wrong length
8383
let signature = vec![1u8; 65];
84-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c".to_string();
84+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c".to_string();
8585

8686
let result = PayGoAttestation::new(entropy, signature, address);
8787
assert!(result.is_err());
@@ -99,23 +99,23 @@ mod tests {
9999
b722b6d0d9adbab782d2d0d66402794b6bd6449dc26f634035ee388a2b5e7b53f6",
100100
)
101101
.unwrap();
102-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c".to_string();
102+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c".to_string();
103103

104104
let attestation = PayGoAttestation::new(entropy, signature, address.clone()).unwrap();
105105
let message = attestation.to_message();
106106

107-
// Message should be: 64 bytes entropy + 33 bytes address + 36 bytes UUID = 133 bytes
108-
assert_eq!(message.len(), 133);
107+
// Message should be: 64 bytes entropy + 34 bytes address + 36 bytes UUID = 134 bytes
108+
assert_eq!(message.len(), 134);
109109

110110
// Verify components
111111
let entropy_part = &message[0..64];
112-
let address_part = &message[64..97];
113-
let uuid_part = &message[97..133];
112+
let address_part = &message[64..98];
113+
let uuid_part = &message[98..134];
114114

115115
assert_eq!(entropy_part, &vec![0u8; 64][..]);
116116
assert_eq!(
117117
std::str::from_utf8(address_part).unwrap(),
118-
"1CdWUVacSQQJ617HuNWByGiisEGXGNx2c"
118+
"1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c"
119119
);
120120
assert_eq!(
121121
std::str::from_utf8(uuid_part).unwrap(),

packages/wasm-utxo/src/paygo/psbt.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ mod tests {
196196
#[test]
197197
fn test_extract_paygo_attestation_success() {
198198
let output = create_test_output_with_attestation();
199-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c";
199+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c";
200200

201201
let result = extract_paygo_attestation(&output, address);
202202
assert!(result.is_ok());
@@ -210,7 +210,7 @@ mod tests {
210210
#[test]
211211
fn test_extract_paygo_attestation_not_found() {
212212
let output = Output::default();
213-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c";
213+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c";
214214

215215
let result = extract_paygo_attestation(&output, address);
216216
assert!(result.is_err());
@@ -236,7 +236,7 @@ mod tests {
236236
output.proprietary.insert(key, signature);
237237
}
238238

239-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c";
239+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c";
240240
let result = extract_paygo_attestation(&output, address);
241241
assert!(result.is_err());
242242
assert!(result
@@ -320,7 +320,7 @@ mod tests {
320320
b722b6d0d9adbab782d2d0d66402794b6bd6449dc26f634035ee388a2b5e7b53f6",
321321
)
322322
.unwrap();
323-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c";
323+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c";
324324
let pubkey_bytes =
325325
hex::decode("02456f4f788b6af55eb9c54d88692cadef4babdbc34cde75218cc1d6b6de3dea2d")
326326
.unwrap();
@@ -337,10 +337,8 @@ mod tests {
337337
assert_eq!(attestation.address, address);
338338

339339
// Verify with pubkeys
340-
// Note: Signature verification is not fully working yet with bitcoinjs-message format
341-
// For now, we just verify the function runs without panic
342340
let result = has_paygo_attestation_verify(&output, Some(address), &[pubkey]);
343-
// The verification may fail, but should not panic
344-
let _ = result;
341+
assert!(result.is_ok());
342+
assert!(result.unwrap(), "Signature should be valid");
345343
}
346344
}

packages/wasm-utxo/src/paygo/verify.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,7 @@ mod tests {
106106
use super::*;
107107
use crate::paygo::PayGoAttestation;
108108

109-
// TODO: Fix signature verification test - the recovery algorithm needs adjustment
110-
// to match bitcoinjs-message format
111109
#[test]
112-
#[ignore]
113110
fn test_verify_valid_signature() {
114111
use secp256k1::PublicKey;
115112

@@ -120,7 +117,7 @@ mod tests {
120117
b722b6d0d9adbab782d2d0d66402794b6bd6449dc26f634035ee388a2b5e7b53f6",
121118
)
122119
.unwrap();
123-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c".to_string();
120+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c".to_string();
124121
let pubkey_bytes =
125122
hex::decode("02456f4f788b6af55eb9c54d88692cadef4babdbc34cde75218cc1d6b6de3dea2d")
126123
.unwrap();
@@ -144,7 +141,7 @@ mod tests {
144141
b722b6d0d9adbab782d2d0d66402794b6bd6449dc26f634035ee388a2b5e7b53f6",
145142
)
146143
.unwrap();
147-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c".to_string();
144+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c".to_string();
148145

149146
// Different public key
150147
let wrong_pubkey_bytes =
@@ -165,7 +162,7 @@ mod tests {
165162

166163
let entropy = vec![0u8; 64];
167164
let signature = vec![1u8; 32]; // Too short
168-
let address = "1CdWUVacSQQJ617HuNWByGiisEGXGNx2c".to_string();
165+
let address = "1CdWUVacSQQJ617HfuNWByGiisEGXGNx2c".to_string();
169166
let pubkey_bytes =
170167
hex::decode("02456f4f788b6af55eb9c54d88692cadef4babdbc34cde75218cc1d6b6de3dea2d")
171168
.unwrap();

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ impl<T: TryIntoJsValue> TryIntoJsValue for Option<T> {
8080
}
8181
}
8282

83+
impl TryIntoJsValue for bool {
84+
fn try_to_js_value(&self) -> Result<JsValue, WasmUtxoError> {
85+
Ok(JsValue::from_bool(*self))
86+
}
87+
}
88+
8389
impl TryIntoJsValue for XOnlyPublicKey {
8490
fn try_to_js_value(&self) -> Result<JsValue, WasmUtxoError> {
8591
Ok(JsValue::from_str(&self.to_string()))
@@ -367,7 +373,8 @@ impl TryIntoJsValue for crate::fixed_script_wallet::bitgo_psbt::ParsedOutput {
367373
"address" => self.address.clone(),
368374
"script" => self.script.clone(),
369375
"value" => self.value,
370-
"scriptId" => self.script_id
376+
"scriptId" => self.script_id,
377+
"paygo" => self.paygo
371378
)
372379
}
373380
}

packages/wasm-utxo/test/acid-test/acidTest.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe("AcidTest", function () {
9595
// Verify no signatures present
9696
const rpKey = test.getReplayProtectionKey();
9797
const replayProtection = { publicKeys: [rpKey.publicKey] };
98-
const parsed = psbt.parseTransactionWithWalletKeys(test.rootWalletKeys, replayProtection);
98+
const parsed = psbt.parseTransactionWithWalletKeys(test.rootWalletKeys, { replayProtection });
9999
const user = test.rootWalletKeys.userKey();
100100
const backup = test.rootWalletKeys.backupKey();
101101
const bitgo = test.rootWalletKeys.bitgoKey();
@@ -128,7 +128,7 @@ describe("AcidTest", function () {
128128
// Verify one signature per input (user only)
129129
const rpKey = test.getReplayProtectionKey();
130130
const replayProtection = { publicKeys: [rpKey.publicKey] };
131-
const parsed = psbt.parseTransactionWithWalletKeys(test.rootWalletKeys, replayProtection);
131+
const parsed = psbt.parseTransactionWithWalletKeys(test.rootWalletKeys, { replayProtection });
132132
const user = test.rootWalletKeys.userKey();
133133
const backup = test.rootWalletKeys.backupKey();
134134
const bitgo = test.rootWalletKeys.bitgoKey();
@@ -175,7 +175,7 @@ describe("AcidTest", function () {
175175
// Verify two signatures per input (user + bitgo or user + backup)
176176
const rpKey = test.getReplayProtectionKey();
177177
const replayProtection = { publicKeys: [rpKey.publicKey] };
178-
const parsed = psbt.parseTransactionWithWalletKeys(test.rootWalletKeys, replayProtection);
178+
const parsed = psbt.parseTransactionWithWalletKeys(test.rootWalletKeys, { replayProtection });
179179
const user = test.rootWalletKeys.userKey();
180180
const backup = test.rootWalletKeys.backupKey();
181181
const bitgo = test.rootWalletKeys.bitgoKey();

packages/wasm-utxo/test/benchmark/signing.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,9 @@ function benchmarkPerInputSign(
173173
// Clone PSBT for this benchmark
174174
const testPsbt = BitGoPsbt.fromBytes(psbt.serialize(), "bitcoin");
175175

176-
const parsed = testPsbt.parseTransactionWithWalletKeys(walletKeys, { publicKeys: [] });
176+
const parsed = testPsbt.parseTransactionWithWalletKeys(walletKeys, {
177+
replayProtection: { publicKeys: [] },
178+
});
177179

178180
// For MuSig2, generate nonces first (not timed)
179181
if (scriptType.isMuSig2KeyPath) {

packages/wasm-utxo/test/fixedScript/dogecoinLOLAmount.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ describe("Dogecoin large output limit amount (LOL amounts) (1-in/1-out)", functi
3333
psbt.addWalletInput({ txid, vout: 0, value }, walletKeys, { scriptId: { chain: 0, index: 0 } });
3434
psbt.addWalletOutput(walletKeys, { chain: 0, index: 0, value });
3535

36-
const parsed = psbt.parseTransactionWithWalletKeys(walletKeys, { publicKeys: [] });
36+
const parsed = psbt.parseTransactionWithWalletKeys(walletKeys, {
37+
replayProtection: { publicKeys: [] },
38+
});
3739
assert.strictEqual(parsed.inputs.length, 1);
3840
assert.strictEqual(parsed.outputs.length, 1);
3941
assert.strictEqual(parsed.inputs[0].value, value);

0 commit comments

Comments
 (0)