@samouraiwallet/bip47
Version:
A set of utilities for working with BIP47 and bitcoinjs-lib
324 lines (228 loc) • 11.3 kB
Markdown
# @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](#installation)
- [API documentation](#api-documentation)
- Usage
- [ECC library](#ecc-library)
- [Examples](#examples)
- [Interfaces](#interfaces)
## Installation
```bash
npm install @samouraiwallet/bip47
```
or
```bash
pnpm add @samouraiwallet/bip47
```
or
```bash
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.
```bash
# create a temp folder
mkdir bip47-docs
cd bip47-docs
# download and extract docs directory
curl -fsSL https://github.com/Dojo-Open-Source-Project/bip47/archive/refs/heads/master.tar.gz\?path\=docs | tar -xzv --strip-components=2
# run simple HTTP file server
npx serve .
```
## Usage
### ECC library
You need to provide an implementation of `secp256k1` elliptic curve.
Supported libraries:
- [tiny-secp256k1](https://github.com/bitcoinjs/tiny-secp256k1) - Rust implementation compiled to Webassembly, work in Node.js and browsers but might require reconfiguring your bundler
- [@bitcoinerlab/secp256k1](https://github.com/bitcoinerlab/secp256k1) - Javascript implementation which works everywhere but has a lower performance
### Examples
- [Create an instance of bip47](#create-an-instance-of-bip47)
- [Create a PaymentCodePrivate instance from wallet master seed](#create-a-paymentcodeprivate-instance-from-wallet-master-seed)
- [Create a PaymentCodePublic instance from payment code string](#create-a-paymentcodepublic-instance-from-payment-code-string)
- [Generate a base58 encoded payment code](#generate-a-base58-encoded-payment-code)
- [Get notification address](#get-notification-address)
- [Get notification address public key](#get-notification-address-public-key)
- [Get notification address private key](#get-notification-address-private-key)
- [Derive addresses from Alice to Bob](#derive-addresses-from-alice-to-bob)
- [Derive payment keys from Alice to Bob](#derive-payment-keys-from-alice-to-bob)
- [Extract payment code from notification transaction](#extract-payment-code-from-notification-transaction)
- [Get blinded payment code for notification transaction](#get-blinded-payment-code-for-notification-transaction)
#### Create an instance of bip47
```ts
import BIP47Factory from "@samouraiwallet/bip47";
import * as ecc from "tiny-secp256k1";
const bip47 = BIP47Factory(ecc);
```
#### Create a PaymentCodePrivate instance from wallet master seed
**on mainnet**
```ts
import type {PaymentCodePrivate} from "@samouraiwallet/bip47";
let walletSeed: Uint8Array;
const alice: PaymentCodePrivate = bip47.fromSeed(walletSeed);
// with segwit support
const alice2: PaymentCodePrivate = bip47.fromSeed(walletSeed, true);
```
**on testnet**
```ts
import type {PaymentCodePrivate} from "@samouraiwallet/bip47";
import {networks} from "@samouraiwallet/bip47/utils";
let walletSeed: Uint8Array;
// pass in a desired network object (bitcoin | testnet | regtest) from utils or directly from bitcoinjs-lib
const alice: PaymentCodePrivate = bip47.fromSeed(walletSeed, false, networks['testnet']);
// with segwit support
const alice2: PaymentCodePrivate = bip47.fromSeed(walletSeed, true, networks['testnet']);
```
#### Create a PaymentCodePublic instance from payment code string
**on mainnet**
```ts
import type {PaymentCodePublic} from "@samouraiwallet/bip47";
const pcode = "PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97";
const bob: PaymentCodePublic = bip47.fromBase58(pcode);
```
**on testnet**
```ts
import type {PaymentCodePublic} from "@samouraiwallet/bip47";
import {networks} from "@samouraiwallet/bip47/utils";
const pcode = "PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97";
// pass in a desired network object (bitcoin | testnet | regtest) from utils or directly from bitcoinjs-lib
const bob: PaymentCodePublic = bip47.fromBase58(pcode, networks['testnet']);
```
#### Generate a base58 encoded payment code
```ts
const alicePcode: string = alice.toBase58(); // PM8TJTLJbPRGxSbc8EJi42Wrr6QbNSaSSVJ5Y3E4pbCYiTHUskHg13935Ubb7q8tx9GVbh2UuRnBc3WSyJHhUrw8KhprKnn9eDznYGieTzFcwQRya4GA
```
#### Get notification address
```ts
const aliceNotificationAddress: string = alice.getNotificationAddress(); // 1JDdmqFLhpzcUwPeinhJbUPw4Co3aWLyzW
```
#### Get notification address public key
```ts
const aliceNotifPubKey: Uint8Array = alice.getNotificationPublicKey(); // 0353883a146a23f988e0f381a9507cbdb3e3130cd81b3ce26daf2af088724ce683
```
#### Get notification address private key
```ts
const aliceNotifPrivKey: Uint8Array = alice.getNotificationPrivateKey(); // 8d6a8ecd8ee5e0042ad0cb56e3a971c760b5145c3917a8e7beaf0ed92d7a520c
```
#### Derive addresses from Alice to Bob
**Alice's side**
```ts
// Bob's P2PKH address at index 0
const bobAddress: string = bob.getPaymentAddress(alice, 0, 'p2pkh'); // 141fi7TY3h936vRUKh1qfUZr8rSBuYbVBK
// check if Bob's payment code supports receiving to segwit addresses
if (bob.segwit) {
// Bob's P2WPKH address at index 1
const bobSegwitAddress = bob.getPaymentAddress(alice, 1, 'p2wpkh'); // bc1qzn8a8drxv6ln7rztjsw660gzf3hnrfwupzmsfh
}
```
**Bob's side**
```ts
import type {PaymentCodePrivate, PaymentCodePublic} from "@samouraiwallet/bip47";
let bobSeed: Uint8Array;
let alicePcode: string; // base58 encoded payment code
const bob: PaymentCodePrivate = bip47.fromSeed(bobSeed);
const alice: PaymentCodePublic = bip47.fromBase58(alicePcode);
const bobAddress: string = bob.getPaymentAddress(alice, 0, 'p2pkh'); // 141fi7TY3h936vRUKh1qfUZr8rSBuYbVBK
```
#### Derive payment keys from Alice to Bob
**Alice's side**
```ts
// Bob's payment pubkey at index 0
const bobPubKey: Uint8Array = bob.derivePaymentPublicKey(alice, 0); // 0344b4795e48df097bd87e6cf87a70e4f0c30b2d847b6e34cddde64af10296952d
```
**Bob's side**
```ts
import type {PaymentCodePrivate, PaymentCodePublic} from "@samouraiwallet/bip47";
let bobSeed: Uint8Array;
let alicePcode: string; // base58 encoded payment code
const bob: PaymentCodePrivate = bip47.fromSeed(bobSeed);
const alice: PaymentCodePublic = bip47.fromBase58(alicePcode);
// Bob's payment keys at index 0
const bobPubKey: Uint8Array = bob.derivePaymentPublicKey(alice, 0);
const bobPrivKey: Uint8Array = bob.derivePaymentPrivateKey(alice, 0);
```
#### Extract payment code from notification transaction
```ts
import type {PaymentCodePrivate, PaymentCodePublic} from "@samouraiwallet/bip47";
let bobSeed: Uint8Array;
const bob: PaymentCodePrivate = bip47.fromSeed(bob.seed);
let scriptPubKey: Uint8Array; // scriptPubKey of notification transaction OP_RETURN output
let outpoint: Uint8Array; // outpoint of first input of notification transaction
let pubKey: Uint8Array; // public key of first input of notification transaction
const alice: PaymentCodePublic = bob.getPaymentCodeFromNotificationTransactionData(scriptPubKey, outpoint, pubKey);
const alicePcode: string = alice.toBase58(); // PM8TJTLJbPRGxSbc8EJi42Wrr6QbNSaSSVJ5Y3E4pbCYiTHUskHg13935Ubb7q8tx9GVbh2UuRnBc3WSyJHhUrw8KhprKnn9eDznYGieTzFcwQRya4GA
```
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.
```ts
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
```ts
let outpoint: Uint8Array; // outpoint of the first input of the notification transaction
let privKey: Uint8Array; // private key of a first input of the notification transaction
const blindedAlicePcode: string = alicePcode.getBlindedPaymentCode(bob, outpoint, privKey);
```
### Interfaces
```ts
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;
}
```