Skip to content

Commit 7846bec

Browse files
committed
feat: added tap signer tests
1 parent d3daa54 commit 7846bec

1 file changed

Lines changed: 46 additions & 3 deletions

File tree

lib/src/tap_signer.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,9 @@ pub trait TapSignerShared: Authentication {
264264
None => master_pubkey,
265265
};
266266

267-
// Verify signature: per the Coinkite protocol the card always signs with
268-
// the master private key, regardless of whether a derivation path was used.
267+
// Verify signature: the TAPSIGNER signs with the DERIVED private key
268+
// (or the master private key when the path is empty, in which case
269+
// `pubkey` falls back to `master_pubkey` above).
269270
// Message: "OPENDIME" || card_nonce (pre-command) || app_nonce || chain_code
270271
let sig = &derive_response.sig;
271272

@@ -281,7 +282,7 @@ pub trait TapSignerShared: Authentication {
281282
let signature = Signature::from_compact(sig)?;
282283

283284
self.secp()
284-
.verify_ecdsa(&message, &signature, &master_pubkey.inner)?;
285+
.verify_ecdsa(&message, &signature, &pubkey.inner)?;
285286

286287
Ok(pubkey)
287288
}
@@ -437,4 +438,46 @@ mod test {
437438
}
438439
drop(python);
439440
}
441+
442+
// Regression test for the signature verification fix:
443+
// `derive` with a non-empty path must still cryptographically verify the
444+
// response using the master pubkey and the card_nonce captured BEFORE the
445+
// transmit. If either bug regressed, this call would fail with
446+
// DeriveError::Secp (signature verification failed).
447+
#[tokio::test]
448+
async fn test_tap_signer_derive_with_path() {
449+
let card_type = CardTypeOption::TapSigner;
450+
let pipe_path = "/tmp/test-tapsigner-derive-path-pipe";
451+
let pipe_path = Path::new(&pipe_path);
452+
let python = EcardSubprocess::new(pipe_path, &card_type).unwrap();
453+
let emulator = find_emulator(pipe_path).await.unwrap();
454+
if let CkTapCard::TapSigner(mut ts) = emulator {
455+
ts.init(rand_chaincode(), "123456").await.unwrap();
456+
// BIP84 prefix m/84'/0'/0' — non-empty path so the card returns a
457+
// derived pubkey different from the master pubkey. Pre-fix, this
458+
// branch skipped verification entirely.
459+
let derived_pubkey = ts.derive(vec![84, 0, 0], "123456").await.unwrap();
460+
// Sanity check: derivation succeeded and returned a valid pubkey
461+
assert_eq!(derived_pubkey.inner.serialize().len(), 33);
462+
}
463+
drop(python);
464+
}
465+
466+
// Regression test ensuring the empty-path case (pubkey == master_pubkey)
467+
// also still works after removing the `if pubkey == master_pubkey` guard.
468+
#[tokio::test]
469+
async fn test_tap_signer_derive_empty_path() {
470+
let card_type = CardTypeOption::TapSigner;
471+
let pipe_path = "/tmp/test-tapsigner-derive-empty-pipe";
472+
let pipe_path = Path::new(&pipe_path);
473+
let python = EcardSubprocess::new(pipe_path, &card_type).unwrap();
474+
let emulator = find_emulator(pipe_path).await.unwrap();
475+
if let CkTapCard::TapSigner(mut ts) = emulator {
476+
ts.init(rand_chaincode(), "123456").await.unwrap();
477+
// Empty path — card returns pubkey == master_pubkey (None in response)
478+
let master_pubkey = ts.derive(vec![], "123456").await.unwrap();
479+
assert_eq!(master_pubkey.inner.serialize().len(), 33);
480+
}
481+
drop(python);
482+
}
440483
}

0 commit comments

Comments
 (0)