Skip to content

Latest commit

 

History

History
151 lines (117 loc) · 10.9 KB

0069.md

File metadata and controls

151 lines (117 loc) · 10.9 KB

BRC-69: Revealing Key Linkages

Ty Everett (ty@projectbabbage.com)

Abstract

This BRC defines two methods for revealing key linkages under the BRC-42 key derivation scheme and BRC-43. Method 1 reveals the root ECDH shared point between two counterparties, enabling linkage across their derivation universe and calculation of their BRC-2 encryption keys for known derivation labels. Method 2 reveals a specific child-key offset rather than the root shared point. These methods support BRC-3 signature verification and transaction auditing, with different confidentiality boundaries. Neither method is a blanket guarantee that encrypted data remains confidential after a disclosure. Correctness of linkage claims is a separate concern addressed by BRC-93 and, for Method 1, BRC-94.

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 point between a user's identity key and a specified counterparty's key. Its compressed point encoding is the HMAC key used for child-key derivation. A recipient can compute offsets and child public keys for known or subsequently learned BRC-43 security levels, protocol IDs and key IDs for that pair, linking past and future activity that uses the same identity-key pair. The disclosure does not itself enumerate unknown labels or identify every transaction.

Method 1 also removes the confidentiality of BRC-2 data from the recipient of the disclosure when the identity public keys, derivation labels and ciphertext are available. BRC-42 adds the same derivation offset to both parties' keys for a given invoice number. Consequently the disclosed root shared point, offset and identity public keys suffice to calculate the child ECDH shared point used by BRC-2; recovering either party's private key is not necessary. This applies across protocols and key IDs for that pair, rather than only to a protocol named in an application permission or audit request. It does not provide the root shared points for unrelated counterparty pairs, ciphertext not available to the recipient, or separately generated content-encryption keys merely because they belong to the same application.

Implementations MUST NOT describe Method 1 as preserving BRC-2 ciphertext confidentiality from the recipient of the root shared point, or as a cryptographically limited grant for a single protocol or time range. Wallets SHOULD explain the pair-wide linkage and encryption-key consequences before authorizing a disclosure. A BRC-94 proof verifies the disclosed point's correctness; it does not reduce these consequences.

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.

For two distinct counterparties whose private keys remain secret, the offset alone does not disclose the root shared point or suffice to calculate their BRC-2 child ECDH point. Its narrower scope MUST NOT be interpreted as an unconditional confidentiality guarantee: it does not protect an encryption key already derivable from other disclosed material or from a publicly known counterparty private key, such as anyone. An offset is sensitive key-derivation material, not merely public metadata. Revelation correctness and authorization remain separate requirements.

Protection of Revelation Payloads

BRC-72 encrypts revelation payloads for a designated verifier, and BRC-100 exposes these operations as wallet methods. This protects delivery to that verifier under the encryption scheme's assumptions. Once decrypted, the verifier holds the disclosed point or offset and can retain or redistribute it. Transport encryption, a privileged flag, a permission decision or a revelation timestamp does not cryptographically constrain subsequent use, erase knowledge already received, or revoke the disclosure. Method 1 remains applicable to earlier and later ciphertext under the same identity-key pair when the necessary labels and ciphertext are available.

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.

Compatibility Clarification

The confidentiality clarification corrects the earlier claim that both methods always preserve BRC-2 encryption confidentiality. It does not change either revelation payload, the BRC-42 derivation, or BRC-2 ciphertext. Implementations that represented Method 1 as a protocol-limited audit operation SHOULD update their permission explanations and security documentation. Encryption and proof verification remain separate from the scope of the authority disclosed.

The reference KeyDeriver returns the compressed root shared point for Method 1 and the invoice-specific HMAC for Method 2. ProtoWallet encrypts these disclosures for the designated verifier; that wrapper leaves their derivation scope unchanged.

Revision History

  • 2026-09-30: Corrected Method 1's BRC-2 confidentiality claim and qualified Method 2's scope. Distinguished disclosure authority from delivery encryption, correctness proofs and permissions. Revelation bytes and key derivation remain unchanged.

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')
}