From d69b9ea3d85f364bd5f9c04fb3c610a3b5bb9e3c Mon Sep 17 00:00:00 2001 From: chad Date: Mon, 13 Jan 2025 12:44:00 -0600 Subject: [PATCH] implement fingerprint extraction from BIP380 descriptors --- .changeset/sharp-cows-promise.md | 6 +++++ src/descriptors.test.ts | 39 ++++++++++++++++++++++++++++++++ src/descriptors.ts | 17 ++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 .changeset/sharp-cows-promise.md diff --git a/.changeset/sharp-cows-promise.md b/.changeset/sharp-cows-promise.md new file mode 100644 index 0000000..962d242 --- /dev/null +++ b/.changeset/sharp-cows-promise.md @@ -0,0 +1,6 @@ +--- +"@caravan/descriptors": patch +--- + +Add getFingerprintFromBip380Descriptor utility to extract fingerprints from BIP380 descriptors. + diff --git a/src/descriptors.test.ts b/src/descriptors.test.ts index e806c60..e7c039d 100644 --- a/src/descriptors.test.ts +++ b/src/descriptors.test.ts @@ -6,6 +6,7 @@ import { encodeDescriptorWithMultipath, getChecksum, getWalletFromDescriptor, + getFingerprintFromBip380Descriptor, } from "./descriptors"; import { EXTERNAL_BRAID, @@ -324,3 +325,41 @@ describe("Multipath Notation Support (<0;1>)", () => { }); }); }); + +describe("getFingerprintFromBip380Descriptor", () => { + it("should extract fingerprint from BIP380 descriptor with key origin", () => { + // Test vector from BIP380: wpkh with key origin + const descriptor = + "wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)"; + const result = getFingerprintFromBip380Descriptor(descriptor); + expect(result).toBe("d34db33f"); + }); + + it("should extract fingerprint from BIP380 descriptor with sh(wpkh) and key origin", () => { + // Test vector from BIP380: sh(wpkh) with key origin + const descriptor = + "sh(wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*))"; + const result = getFingerprintFromBip380Descriptor(descriptor); + expect(result).toBe("d34db33f"); + }); + + it("should return null for BIP380 descriptor without key origin", () => { + // Test vector from BIP380: wpkh without key origin + const descriptor = + "wpkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0)"; + const result = getFingerprintFromBip380Descriptor(descriptor); + expect(result).toBeNull(); + }); + + it("should return null for invalid descriptor", () => { + const descriptor = "invalid"; + const result = getFingerprintFromBip380Descriptor(descriptor); + expect(result).toBeNull(); + }); + + it("should handle uppercase fingerprint and return lowercase", () => { + const descriptor = "wpkh([D34DB33F/44'/0'/0']xpub...)"; + const result = getFingerprintFromBip380Descriptor(descriptor); + expect(result).toBe("d34db33f"); + }); +}); diff --git a/src/descriptors.ts b/src/descriptors.ts index dbc2c7e..a5ae0fa 100644 --- a/src/descriptors.ts +++ b/src/descriptors.ts @@ -11,6 +11,8 @@ import { CHECKSUM_REGEX, } from "./multipath"; +const FINGERPRINT_REGEX = /\[([a-f0-9]+)\//i; + // should be a 32 byte hex string export type PolicyHmac = string; // should be an 8 byte hex string @@ -226,8 +228,23 @@ export const getWalletFromDescriptor = async ( return await decodeDescriptors(internal, external, network); }; +/** + * Extracts the key fingerprint from a BIP380 descriptor string. + * The fingerprint is part of the key origin information, enclosed in square brackets + * followed by a derivation path and a slash, as defined in BIP380. + * + * @param descriptor - The BIP380 descriptor string to parse. + * @returns The extracted fingerprint as a lowercase hex string, or null if not found. + */ +export const getFingerprintFromBip380Descriptor = ( + descriptor: string, +): string | null => { + return descriptor.match(FINGERPRINT_REGEX)?.[1]?.toLowerCase() || null; +}; + export default { encodeDescriptors, encodeDescriptorWithMultipath, decodeDescriptors, + getFingerprintFromBip380Descriptor, };