@samouraiwallet/bip47
A set of utilities for working with BIP47 and bitcoinjs-lib.
This library uses ES Modules. Node.js v16 or later is required.
This library does not use any Node.js built-ins and thus is browser compatible.
Source code was written in Typescript. Type definitions are included in the published bundle.
Contents
Installation
npm install @samouraiwallet/bip47
or
pnpm add @samouraiwallet/bip47
or
yarn add @samouraiwallet/bip47
API documentation
Generated API documentation is available in the git repository. To view latest API docs locally, you can run these commands.
mkdir bip47-docs
cd bip47-docs
curl -fsSL https://code.samourai.io/dojo/bip47-js/-/archive/master/bip47-js-master.tar.gz\?path\=docs | tar -xzv --strip-components=2
npx serve .
Usage
ECC library
You need to provide an implementation of secp256k1
elliptic curve.
Supported libraries:
- tiny-secp256k1 - Rust implementation compiled to Webassembly, work in Node.js and browsers but might require reconfiguring your bundler
- @bitcoinerlab/secp256k1 - Javascript implementation which works everywhere but has a lower performance
Examples
Create an instance of bip47
import BIP47Factory from "@samouraiwallet/bip47";
import * as ecc from "tiny-secp256k1";
const bip47 = BIP47Factory(ecc);
Create a PaymetCodePrivate instance from wallet master seed
on mainnet
import type {PaymentCodePrivate} from "@samouraiwallet/bip47";
let walletSeed: Uint8Array;
const alice: PaymentCodePrivate = bip47.fromSeed(walletSeed);
const alice2: PaymentCodePrivate = bip47.fromSeed(walletSeed, true);
on testnet
import type {PaymentCodePrivate} from "@samouraiwallet/bip47";
import {networks} from "@samouraiwallet/bip47/utils";
let walletSeed: Uint8Array;
const alice: PaymentCodePrivate = bip47.fromSeed(walletSeed, false, networks['testnet']);
const alice2: PaymentCodePrivate = bip47.fromSeed(walletSeed, true, networks['testnet']);
Create a PaymetCodePublic instance from payment code string
on mainnet
import type {PaymentCodePublic} from "@samouraiwallet/bip47";
const pcode = "PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97";
const bob: PaymentCodePublic = bip47.fromBase58(pcode);
on testnet
import type {PaymentCodePublic} from "@samouraiwallet/bip47";
import {networks} from "@samouraiwallet/bip47/utils";
const pcode = "PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97";
const bob: PaymentCodePublic = bip47.fromBase58(pcode, networks['testnet']);
Generate a base58 encoded payment code
const alicePcode: string = alice.toBase58();
Get notification address
const aliceNotificationAddress: string = alice.getNotificationAddress();
Get notification address public key
const aliceNotifPubKey: Uint8Array = alice.getNotificationPublicKey();
Get notification address private key
const aliceNotifPrivKey: Uint8Array = alice.getNotificationPrivateKey();
Derive addresses from Alice to Bob
Alice's side
const bobAddress: string = bob.getPaymentAddress(alice, 0, 'p2pkh');
if (bob.segwit) {
const bobSegwitAddress = bob.getPaymentAddress(alice, 1, 'p2wpkh');
}
Bob's side
import type {PaymentCodePrivate, PaymentCodePublic} from "@samouraiwallet/bip47";
let bobSeed: Uint8Array;
let alicePcode: string;
const bob: PaymentCodePrivate = bip47.fromSeed(bobSeed);
const alice: PaymentCodePublic = bip47.fromBase58(alicePcode);
const bobAddress: string = bob.getPaymentAddress(alice, 0, 'p2pkh');
Derive payment keys from Alice to Bob
Alice's side
const bobPubKey: Uint8Array = bob.derivePaymentPublicKey(alice, 0);
Bob's side
import type {PaymentCodePrivate, PaymentCodePublic} from "@samouraiwallet/bip47";
let bobSeed: Uint8Array;
let alicePcode: string;
const bob: PaymentCodePrivate = bip47.fromSeed(bobSeed);
const alice: PaymentCodePublic = bip47.fromBase58(alicePcode);
const bobPubKey: Uint8Array = bob.derivePaymentPublicKey(alice, 0);
const bobPrivKey: Uint8Array = bob.derivePaymentPrivateKey(alice, 0);
import type {PaymentCodePrivate, PaymentCodePublic} from "@samouraiwallet/bip47";
let bobSeed: Uint8Array;
const bob: PaymentCodePrivate = bip47.fromSeed(bob.seed);
let scriptPubKey: Uint8Array;
let outpoint: Uint8Array;
let pubKey: Uint8Array;
const alice: PaymentCodePublic = bob.getPaymentCodeFromNotificationTransactionData(scriptPubKey, outpoint, pubKey);
const alicePcode: string = alice.toBase58();
In order to extract payment code from a notification transaction, the scriptPubKey, outpoint and pubKey must be provided.
You can use bitcoinjs-lib
to extract these values from a transaction.
import * as bitcoin from 'bitcoinjs-lib';
let notificationTxHex: string;
const tx: bitcoin.Transaction = bitcoin.Transaction.fromHex(notificationTxHex);
const opReturnOutput = tx.outs.find((o) =>
o.script[0] === 0x6a && o.script[1] === 0x4c && o.script[2] === 0x50
);
if (!opReturnOutput) throw new error("Transaction doesn't contain OP_RETURN output");
const scriptPubKey: Uint8Array = opReturnOutput.script;
const input = tx.ins[0];
const outpoint: Uint8Array = new Uint8Array(input.hash.length + 4);
outpoint.set(input.hash);
outpoint.set(new Uint32Array([input.index]), input.hash.length)
let pubKey: Uint8Array;
if (input.witness.length) {
pubKey = input.witness[1];
} else if (bitcoin.script.toASM(input.script).split(' ').length === 2) {
pubKey = Buffer.from(bitcoin.script.toASM(input.script).split(' ')[1], 'hex',);
} else throw new Error('Unknown Transaction type');
Get blinded payment code for notification transaction
let outpoint: Uint8Array;
let privKey: Uint8Array;
const blindedAlicePcode: string = alicePcode.getBlindedPaymentCode(bob, outpoint, privKey);
Interfaces
export declare const BIP47Factory: (ecc: TinySecp256k1Interface) => {
fromSeed: (bSeed: Uint8Array, segwit?: boolean, network?: Network) => PaymentCodePrivate;
fromBase58: (inString: string, network?: Network) => PaymentCodePublic;
fromBuffer: (buf: Uint8Array, network?: Network) => PaymentCodePublic;
};
export declare class PaymentCodePublic {
protected readonly ecc: TinySecp256k1Interface;
protected readonly bip32: BIP32API;
protected readonly buf: Uint8Array;
protected readonly network: Network;
root: BIP32Interface;
hasPrivKeys: boolean;
segwit: boolean;
constructor(ecc: TinySecp256k1Interface, bip32: BIP32API, buf: Uint8Array, network?: Network);
get features(): Uint8Array;
get pubKey(): Uint8Array;
get chainCode(): Uint8Array;
get paymentCode(): Uint8Array;
clone(): PaymentCodePublic;
toBase58(): string;
derive(index: number): BIP32Interface;
getNotificationPublicKey(): Uint8Array;
getNotificationAddress(): string;
protected derivePublicKeyFromSharedSecret(B: Uint8Array, S: Uint8Array | null): Uint8Array;
derivePaymentPublicKey(paymentCode: PaymentCodePrivate, idx: number): Uint8Array;
protected getAddressFromPubkey(pubKey: Uint8Array, type: AddressType): string;
getPaymentAddress(paymentCode: PaymentCodePrivate, idx: number, type?: AddressType): string;
getBlindedPaymentCode(destinationPaymentCode: PaymentCodePublic, outpoint: Uint8Array, privateKey: Uint8Array): string;
}
export declare class PaymentCodePrivate extends PaymentCodePublic {
constructor(root: BIP32Interface, ecc: TinySecp256k1Interface, bip32: BIP32API, buf: Uint8Array, network?: Network);
toPaymentCodePublic(): PaymentCodePublic;
clone(): PaymentCodePrivate;
deriveHardened(index: number): BIP32Interface;
derivePaymentPublicKey(paymentCode: PaymentCodePublic, idx: number): Uint8Array;
getPaymentAddress(paymentCode: PaymentCodePublic, idx: number, type?: AddressType): string;
derivePaymentPrivateKey(paymentCodePublic: PaymentCodePublic, idx: number): Uint8Array;
getNotificationPrivateKey(): Uint8Array;
getPaymentCodeFromNotificationTransactionData(scriptPubKey: Uint8Array, outpoint: Uint8Array, pubKey: Uint8Array): PaymentCodePublic;
}