Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions src/MasterID.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
ECIES,
Hash,
type PrivateKey,
type PublicKey,
HD,
Utils as BSVUtils,
} from "@bsv/sdk";
Expand Down Expand Up @@ -199,6 +201,107 @@ class MasterID {
id: this.bapId,
};
}

/**
* Derive a friend-specific private key using Type42 (BRC-42) derivation.
*
* The seed (typically a friend's BAP ID) is used as the invoice number
* in the BRC-42 key derivation. The member's own public key serves as
* the counterparty public key, making this a self-derivation that
* produces deterministic, seed-specific child keys.
*
* In BIP32 mode, the seed is hashed to produce a derivation path.
*
* @param seed - The derivation seed (e.g., friend's BAP ID)
* @returns The derived private key
*/
#deriveKeyFromSeed(seed: string): PrivateKey {
if (this.#isType42) {
if (!this.#masterPrivateKey)
throw new Error("Master private key not initialized");
return this.#masterPrivateKey.deriveChild(
this.#masterPrivateKey.toPublicKey(),
seed
);
}
if (!this.#HDPrivateKey)
throw new Error("HD private key not initialized");
const seedHex = BSVUtils.toHex(Hash.sha256(seed, "utf8"));
const seedPath = Utils.getSigningPathFromHex(seedHex);
return this.#HDPrivateKey.derive(seedPath).privKey;
}

/**
* Get a friend-specific public key derived from this member's key
* and a seed (typically the friend's BAP ID).
*
* This public key should be shared with the friend (e.g., in a friend
* request transaction) so they can use it as the counterparty key
* when encrypting messages to this member.
*
* @param seed - The derivation seed (e.g., friend's BAP ID)
* @returns The derived public key
*/
getEncryptionPublicKeyWithSeed(seed: string): PublicKey {
return this.#deriveKeyFromSeed(seed).toPublicKey();
}

/**
* Encrypt data using a friend-specific derived key and ECIES.
*
* Derives a private key from this member's key + seed, then uses
* Electrum ECIES to encrypt the data to the counterparty's public key.
* The counterparty can decrypt using their corresponding private key
* and this member's derived public key.
*
* @param data - The data to encrypt (string or binary)
* @param seed - The derivation seed (e.g., friend's BAP ID)
* @param counterPartyPublicKey - The friend's derived public key
* @returns Base64-encoded encrypted payload
*/
encryptWithSeed(
data: string | number[],
seed: string,
counterPartyPublicKey: PublicKey
): string {
const derivedKey = this.#deriveKeyFromSeed(seed);
const messageBuf =
typeof data === "string" ? BSVUtils.toArray(data, "utf8") : data;
const encrypted = ECIES.electrumEncrypt(
messageBuf,
counterPartyPublicKey,
derivedKey,
true
);
return BSVUtils.toBase64(encrypted);
}

/**
* Decrypt data using a friend-specific derived key and ECIES.
*
* Derives a private key from this member's key + seed, then uses
* Electrum ECIES to decrypt data that was encrypted by the
* counterparty using this member's derived public key.
*
* @param ciphertext - Base64-encoded encrypted payload
* @param seed - The derivation seed (e.g., friend's BAP ID)
* @param counterPartyPublicKey - The friend's derived public key
* @returns The decrypted data as a UTF-8 string
*/
decryptWithSeed(
ciphertext: string,
seed: string,
counterPartyPublicKey: PublicKey
): string {
const derivedKey = this.#deriveKeyFromSeed(seed);
const encBuf = BSVUtils.toArray(ciphertext, "base64");
const decrypted = ECIES.electrumDecrypt(
encBuf,
derivedKey,
counterPartyPublicKey
);
return BSVUtils.toUTF8(decrypted);
}
}

export { MasterID };
Loading