Revealing Key Linkages

Ty Everett (ty@projectbabbage.com)

Abstract

This BRC proposes two methods for revealing key linkages under the BRC-42 key derivation scheme and BRC-43. One method reveals the root BRC-42 shared secret between two counterparties, allowing anyone to link all interactions between them. The other method reveal only a specific link (the key offset value) between a root identity key and one of its child public keys. This establishes proof of association with a particular BRC-42 derived key and provides an audit trail for a specific BRC-43 protocol ID, key ID, and counterparty. While these methods enable BRC-3 signature verification and transaction auditing, they do not allow anyone to decrypt data encrypted using BRC-2, preserving privacy.

Motivation

The BRC-42 key derivation architecture and BRC-43 allow for privacy-preserving transactions between parties. However, there are scenarios where users may wish to prove key associations and provide audit trails for their interactions. For example, a user may want to establish their ownership of a key used in a transaction, or authorities might need to audit transactions between specific parties. This technical standard aims to fulfill these requirements, allowing users to prove key associations and reveal transaction linkages between parties, while preserving the confidentiality of interactions with unrelated parties.

Specification

Method 1: Revealing Counterparty Shared Secrets

This method returns the BRC-42 root ECDH shared secret between a user's identity key and a specified counterparty's key. Revealing this key allows any party to link all past and future transactions between these two entities, since the shared secret is used as the HMAC key for computing all child key derivation offset values.

Although this enables transaction traceability, and the ability to verify signatures computed with BRC-3, it does not allow for decryption of any data encrypted using a BRC-2 encryption mechanism. This is because while the child public keys are derivable by the person who has been given the shared secret, the corresponding private keys are not, and the BRC-2 shared secret used for encryption is computed between the two child keys, and not the root keys.

This method must never be used where the counterparty is the user themself (counterparty=self), as that would reveal keys used internally by the user. The method is also ineffective where the counterparty is "anyone", as the anyone private key is already publicly known (it is the value 1).

Method 2: Revealing Specific Key Associations

This method reveals the offset value (the link) that connects a root identity key to one of its derived child public keys, under a specified BRC-43 protocol ID, key ID, and counterparty. By exposing this link, the user can prove their association with the specific derived key, allowing for auditing of all activity involving this key for the specified protocol and counterparty.

This method allows the user to create an audit trail for specific protocols, key identifiers, and counterparties without exposing all interactions with the particular counterparty. This is because, while the root shared secret is used to compute the HMAC, it is not revealed — only the resulting HMAC is shared.

Implementation

The two methods in this technical standard are essential building blocks for creating protocols that support accountability and provide an added layer of security to Bitcoin and MetaNet client software implementing BRC-42 and BRC-43 protocols. Implementations should ensure that these methods are only used in ways that maintain user privacy and do not reveal linkage information — especially counterparty root shared secrets — to the public.

Examples

Below is TypeScript code that implements the described methods:

Method 1

import bsv from 'babbage-bsv'

const BN = bsv.crypto.BN
const Hash = bsv.crypto.Hash
const G = bsv.crypto.Point.getG()

/**
 * Returns the root shared secret as described by Method 1
 *
 * @param params All parameters are provided in an object
 * @param params.senderPrivateKey The private key of the sender in WIF format
 * @param params.recipientPublicKey The public key of the recipient in hexadecimal DER format
 *
 * @returns The revealed shared secret, given as a hex string
 */
export function getRootKeyLinkage(params: {
  senderPrivateKey: string | bsv.crypto.BN | bsv.PrivateKey,
  recipientPublicKey: string | bsv.PublicKey,
  invoiceNumber: string
}): string {
  let publicKey: bsv.PublicKey, privateKey: bsv.PrivateKey
  if (typeof params.recipientPublicKey === 'string') {
    publicKey = bsv.PublicKey.fromString(params.recipientPublicKey)
  } else if (params.recipientPublicKey instanceof bsv.PublicKey) {
    publicKey = params.recipientPublicKey
  } else {
    throw new Error('Unrecognized format for recipientPublicKey')
  }
  if (typeof params.senderPrivateKey === 'string') {
    privateKey = BN.fromHex(params.senderPrivateKey)
  } else if (params.senderPrivateKey instanceof BN) {
    privateKey = params.senderPrivateKey
  } else if (params.senderPrivateKey instanceof bsv.PrivateKey) {
    privateKey = params.senderPrivateKey.bn
  } else {
    throw new Error('Unrecognized format for senderPrivateKey')
  }
  const sharedSecret = publicKey.point.mul(privateKey).toBuffer()
  return sharedSecret.toString('hex')
}

Method 2

import bsv from 'babbage-bsv'

const BN = bsv.crypto.BN
const Hash = bsv.crypto.Hash
const G = bsv.crypto.Point.getG()

/**
 * Returns the child key offset as described by Method 2
 *
 * @param params All parameters are provided in an object
 * @param params.senderPrivateKey The private key of the sender in WIF format
 * @param params.recipientPublicKey The public key of the recipient in hexadecimal DER format
 * @param params.securityLevel The BRC-43 security level
 * @param params.protocolID The BRC-43 protocol ID
 * @param params.keyID The BRC-43 key ID
 *
 * @returns The revealed key offset, given as a hex string
 */
export function getKeyLinkage(params: {
  senderPrivateKey: string | bsv.crypto.BN | bsv.PrivateKey,
  recipientPublicKey: string | bsv.PublicKey,
  invoiceNumber: string,
  securityLevel: number,
  protocolID: string,
  keyID: string
}): string {
  let publicKey: bsv.PublicKey, privateKey: bsv.PrivateKey
  if (typeof params.recipientPublicKey === 'string') {
    publicKey = bsv.PublicKey.fromString(params.recipientPublicKey)
  } else if (params.recipientPublicKey instanceof bsv.PublicKey) {
    publicKey = params.recipientPublicKey
  } else {
    throw new Error('Unrecognized format for recipientPublicKey')
  }
  if (typeof params.senderPrivateKey === 'string') {
    privateKey = BN.fromHex(params.senderPrivateKey)
  } else if (params.senderPrivateKey instanceof BN) {
    privateKey = params.senderPrivateKey
  } else if (params.senderPrivateKey instanceof bsv.PrivateKey) {
    privateKey = params.senderPrivateKey.bn
  } else {
    throw new Error('Unrecognized format for senderPrivateKey')
  }
  const sharedSecret = publicKey.point.mul(privateKey).toBuffer()
  const invoiceNumber = Buffer.from(`${params.securityLevel}-${params.protocolId}-${params.keyID}`, 'utf8')
  const hmac = Hash.sha256hmac(sharedSecret, invoiceNumber)
  return hmac.toString('hex')
}

Last updated