Skip to main content
Built by the community - Special thanks to Ruben Somsen and Josie Bake for their groundbreaking work on BIP-352.

Installation

npm i @shakesco/silent
Import the SDK:
const shakesco = require("@shakesco/silent");
const {
  KeyGeneration,
  SilentPaymentDestination,
  SilentPaymentBuilder,
  ECPrivateInfo,
  Network,
  BitcoinScriptOutput,
  bip32,
  bip39,
} = shakesco;

Integration Workflow

1

Generate silent payment address

Create a reusable address for receiving private payments
2

Create destination address

Generate a one-time taproot address for each payment
3

Scan for incoming funds

Detect payments without exposing spending keys
4

Spend received funds

Derive private keys and move Bitcoin

Step 1: Generate Silent Payment Address

Choose your key generation method based on your use case:
If not using the signature-derived method, ensure you’re using a cryptographically secure random number generator for b_scan and b_spend.

Create a Change Address

Critical for privacy: Never send change to a public address after making silent payments.
const keys = KeyGeneration.fromPrivateKeys({
  b_scan: b_scan,
  b_spend: b_spend,
  network: "testnet",
});

// Always use label 0 for change (per BIP-352 spec)
const changeSilentPaymentAddress = keys.toLabeledSilentPaymentAddress(0);
console.log(changeSilentPaymentAddress.toAddress());
Scenario: You send 10 silent payments to friends, then send change to your public address.Result: You’ve exposed:
  • ❌ Your own private transaction history
  • ❌ Your friends’ payment patterns
  • ❌ Links between all 10 transactions
Solution: Always use a labeled silent payment address for change.Reference: BIP-352 Labels for Change

Step 2: Create Destination Address

Generate a unique taproot address for the payment:
// Parse recipient's silent payment address
const addressPubKeys = KeyGeneration.fromAddress(silentPaymentAddress);

// Your UTXO details
const vinOutpoints = [
  {
    txid: "367e24cac43a7d77621ceb1cbc1cf4a7719fc81b05b07b38f99b043f4e8b95dc",
    index: 1,
  },
];

const pubkeys = [
  "025c471f0e7d30d6f9095058bbaedaf13e1de67dbfcbe8328e6378d2a3bfb5cfd0",
];

const UTXOPrivatekey = ""; // Your UTXO private key

// Build the destination
const builder = new SilentPaymentBuilder({
  vinOutpoints: vinOutpoints,
  pubkeys: pubkeys,
}).createOutputs(
  [
    new ECPrivateInfo(
      UTXOPrivatekey,
      false // Set true if output is from taproot
    ),
  ],
  [
    new SilentPaymentDestination({
      amount: 1000, // Satoshis (1 BTC = 100,000,000 sats)
      network: Network.Testnet,
      version: 0,
      scanPubkey: addressPubKeys.B_scan,
      spendPubkey: addressPubKeys.B_spend,
    }),
  ]
);

// Get the destination taproot address
const destinationAddress = builder[silentPaymentAddress][0];
console.log("Send 1000 sats to:", destinationAddress);
What you need: - UTXO transaction ID and output index - UTXO private key - Amount in satoshis - Recipient’s scan and spend public keys (B_scan, B_spend)

Step 3: Scan for Incoming Funds

Scanning trade-off: This is the main drawback of silent payments - you must scan the blockchain to detect incoming transactions.
Check if a transaction belongs to you:
const vinOutpoints = [
  {
    txid: "367e24cac43a7d77621ceb1cbc1cf4a7719fc81b05b07b38f99b043f4e8b95dc",
    index: 1,
  },
];

const pubkeys = [
  "025c471f0e7d30d6f9095058bbaedaf13e1de67dbfcbe8328e6378d2a3bfb5cfd0",
];

const search = new SilentPaymentBuilder({
  vinOutpoints: vinOutpoints,
  pubkeys: pubkeys,
  network: Network.Testnet,
}).scanOutputs(
  keys.b_scan, // Your scan private key
  keys.B_spend, // Your spend public key
  [
    new BitcoinScriptOutput(
      "5120fdcb28bcea339a5d36d0c00a3e110b837bf1151be9e7ac9a8544e18b2f63307d",
      BigInt(1000)
    ),
  ]
);

const foundOutput =
  search[builder[keys.toAddress()][0].address.pubkey.toString("hex")].output;
console.log(foundOutput);
If the output matches the taproot address → it’s yours! 🎉
  • Transaction input’s txid and output_index
  • Public key from the output
  • Script and amount from the taproot address
Learn more: BIP-352 Scanning

Step 4: Spend the Funds

Once you’ve confirmed funds belong to you, derive the private key:
const vinOutpoints = [
  {
    txid: "367e24cac43a7d77621ceb1cbc1cf4a7719fc81b05b07b38f99b043f4e8b95dc",
    index: 1,
  },
];

const pubkeys = [
  "025c471f0e7d30d6f9095058bbaedaf13e1de67dbfcbe8328e6378d2a3bfb5cfd0",
];

const private_key = new SilentPaymentBuilder({
  vinOutpoints: vinOutpoints,
  pubkeys: pubkeys,
}).spendOutputs(keys.b_scan, keys.b_spend);

console.log("Private key:", private_key);
Use this private key with bitcoinjs-lib to build and sign your taproot transaction.

That’s It

You’ve successfully implemented Bitcoin silent payments. Your users can now:
  • ✅ Share a single address for all payments
  • ✅ Receive Bitcoin privately
  • ✅ Maintain transaction unlinkability
  • ✅ Avoid notification transaction fees

Additional Resources