Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@samouraiwallet/bip47

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@samouraiwallet/bip47 - npm Package Compare versions

Comparing version 0.8.0 to 1.0.0

9

CHANGELOG.md
# Changelog
## v1.0.0
### Breaking major release
- full browser compatibility (no use of Node.js internals)
- switched to @samouraiwallet/bip32
- more tests
- more straightforward API
- added methods to create notification payload and extract payment code from payload
- added more comprehensive usage examples
## v0.8.0

@@ -4,0 +13,0 @@ ### Breaking

5

dist/index.d.ts

@@ -1,3 +0,4 @@

export { BIP47Factory } from './payment-code.js';
export * as utils from './utils.js';
export { BIP47Factory as default, BIP47Factory } from './payment-code.js';
export type { TinySecp256k1Interface, Network, AddressType } from './types.js';
export type { PaymentCodePublic, PaymentCodePrivate } from './payment-code.js';
//# sourceMappingURL=index.d.ts.map

3

dist/index.js

@@ -1,3 +0,2 @@

export { BIP47Factory } from './payment-code.js';
export * as utils from './utils.js';
export { BIP47Factory as default, BIP47Factory } from './payment-code.js';
//# sourceMappingURL=index.js.map

@@ -1,62 +0,43 @@

import { BIP32Interface } from 'bip32';
import { Network, TinySecp256k1Interface, AddressType } from './types';
import { BIP32API, BIP32Interface } from '@samouraiwallet/bip32';
import type { Network, TinySecp256k1Interface, AddressType } from './types.js';
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;
}
export declare const BIP47Factory: (ecc: TinySecp256k1Interface) => {
fromSeed: (bSeed: Uint8Array, id: number | string, network?: Network) => {
version: Uint8Array;
buf: Uint8Array;
network: Network;
root: BIP32Interface;
readonly features: Uint8Array;
readonly pubKey: Uint8Array;
readonly chainCode: Uint8Array;
readonly paymentCode: Uint8Array;
toBase58(): string;
_hasPrivKeys(): boolean;
derive(index: number): BIP32Interface;
deriveHardened(index: number): BIP32Interface;
getNotificationAddress(): string;
getNotificationPrivateKey(): Buffer;
derivePaymentPrivateKey(A: Buffer, idx: number): Buffer;
derivePaymentPublicKey(a: Buffer, idx: number): Buffer;
getPaymentAddress(a: Buffer, idx: number, type?: AddressType): string;
};
fromBase58: (inString: string, network?: Network) => {
version: Uint8Array;
buf: Uint8Array;
network: Network;
root: BIP32Interface;
readonly features: Uint8Array;
readonly pubKey: Uint8Array;
readonly chainCode: Uint8Array;
readonly paymentCode: Uint8Array;
toBase58(): string;
_hasPrivKeys(): boolean;
derive(index: number): BIP32Interface;
deriveHardened(index: number): BIP32Interface;
getNotificationAddress(): string;
getNotificationPrivateKey(): Buffer;
derivePaymentPrivateKey(A: Buffer, idx: number): Buffer;
derivePaymentPublicKey(a: Buffer, idx: number): Buffer;
getPaymentAddress(a: Buffer, idx: number, type?: AddressType): string;
};
fromBuffer: (buf: Uint8Array, network?: Network) => {
version: Uint8Array;
buf: Uint8Array;
network: Network;
root: BIP32Interface;
readonly features: Uint8Array;
readonly pubKey: Uint8Array;
readonly chainCode: Uint8Array;
readonly paymentCode: Uint8Array;
toBase58(): string;
_hasPrivKeys(): boolean;
derive(index: number): BIP32Interface;
deriveHardened(index: number): BIP32Interface;
getNotificationAddress(): string;
getNotificationPrivateKey(): Buffer;
derivePaymentPrivateKey(A: Buffer, idx: number): Buffer;
derivePaymentPublicKey(a: Buffer, idx: number): Buffer;
getPaymentAddress(a: Buffer, idx: number, type?: AddressType): string;
};
fromSeed: (bSeed: Uint8Array, segwit?: boolean, network?: Network) => PaymentCodePrivate;
fromBase58: (inString: string, network?: Network) => PaymentCodePublic;
fromBuffer: (buf: Uint8Array, network?: Network) => PaymentCodePublic;
};
//# sourceMappingURL=payment-code.d.ts.map

@@ -1,155 +0,213 @@

import { BIP32Factory } from 'bip32';
import bs58check from 'bs58check';
import { BIP32Factory } from '@samouraiwallet/bip32';
import { bs58check, hmacSHA512 } from '@samouraiwallet/bip32/crypto';
import { sha256 } from '@noble/hashes/sha256';
import * as utils from './utils.js';
const { encode, decode } = bs58check;
const PC_VERSION = 0x47;
export const BIP47Factory = (ecc) => {
const bip32 = BIP32Factory(ecc);
class PaymentCode {
constructor(buf, network = utils.networks.bitcoin) {
if (buf.length !== 80)
throw new TypeError('Invalid buffer length');
this.version = buf.slice(0, 1);
if (this.version[0] !== 1)
throw new TypeError('Only payment codes version 1 are supported');
this.buf = buf;
this.network = network;
this.root = bip32.fromPublicKey(Buffer.from(this.pubKey), Buffer.from(this.chainCode), this.network);
}
get features() {
return this.buf.slice(1, 1);
}
get pubKey() {
return this.buf.slice(2, 2 + 33);
}
get chainCode() {
return this.buf.slice(35, 35 + 32);
}
get paymentCode() {
return this.buf;
}
toBase58() {
const version = Buffer.from([PC_VERSION]);
const buf = Buffer.concat([version, this.buf]);
return encode(buf);
}
_hasPrivKeys() {
return this.root.privateKey != null;
}
derive(index) {
return this.root.derive(index);
}
deriveHardened(index) {
return this.root.deriveHardened(index);
}
getNotificationAddress() {
const child = this.derive(0);
return utils.getP2pkhAddress(child.publicKey, this.network);
}
getNotificationPrivateKey() {
if (!this._hasPrivKeys())
throw new Error('This payment code does not have private keys');
const child = this.derive(0);
return child.privateKey;
}
derivePaymentPrivateKey(A, idx) {
if (!ecc.isPoint(A))
throw new TypeError('Argument is not a valid public key');
const b_node = this.derive(idx);
if (!b_node.privateKey)
throw new Error('Unable to derive node with private key');
const b = b_node.privateKey;
const S = ecc.pointMultiply(A, b);
if (!S)
throw new Error('Unable to compute resulting point');
const Sx = S.slice(1, 33);
const s = utils.sha256(Buffer.from(Sx));
if (!ecc.isPrivate(s))
throw new TypeError('Invalid shared secret');
const paymentPrivateKey = ecc.privateAdd(b, s);
if (!paymentPrivateKey)
throw new TypeError('Unable to compute payment private key');
return Buffer.from(paymentPrivateKey);
}
derivePaymentPublicKey(a, idx) {
if (!ecc.isPrivate(a) && !ecc.isPoint(a))
throw new TypeError('Argument is neither a valid private key or public key');
let B = null;
let S = null;
if (ecc.isPrivate(a)) {
B = this.derive(idx).publicKey;
S = ecc.pointMultiply(B, a);
const SAMOURAI_FEATURE_BYTE = 79;
export class PaymentCodePublic {
constructor(ecc, bip32, buf, network = utils.networks.bitcoin) {
this.ecc = ecc;
this.bip32 = bip32;
this.hasPrivKeys = false;
if (buf.length !== 80)
throw new Error('Invalid buffer length');
if (buf[0] !== 1)
throw new Error('Only payment codes version 1 are supported');
this.buf = buf;
this.network = network;
this.segwit = this.buf[SAMOURAI_FEATURE_BYTE] === 1;
this.root = bip32.fromPublicKey(this.pubKey, this.chainCode, this.network);
}
get features() {
return this.buf.subarray(1, 2);
}
get pubKey() {
return this.buf.subarray(2, 2 + 33);
}
get chainCode() {
return this.buf.subarray(35, 35 + 32);
}
get paymentCode() {
return this.buf;
}
clone() {
return new PaymentCodePublic(this.ecc, this.bip32, this.buf.slice(0), this.network);
}
toBase58() {
const version = new Uint8Array([PC_VERSION]);
const buf = new Uint8Array(version.length + this.buf.length);
buf.set(version);
buf.set(this.buf, version.length);
return bs58check.encode(buf);
}
derive(index) {
return this.root.derive(index);
}
getNotificationPublicKey() {
return this.derive(0).publicKey;
}
getNotificationAddress() {
return utils.getP2pkhAddress(this.getNotificationPublicKey(), this.network);
}
derivePublicKeyFromSharedSecret(B, S) {
if (!this.ecc.isPoint(B))
throw new Error('Invalid derived public key');
if (!S)
throw new Error('Unable to compute secret point');
const Sx = S.subarray(1, 33);
const s = sha256(Sx);
if (!this.ecc.isPrivate(s))
throw new Error('Invalid shared secret');
const EccPoint = this.ecc.pointFromScalar(s);
if (!EccPoint)
throw new Error('Unable to compute point');
const paymentPublicKey = this.ecc.pointAdd(B, EccPoint);
if (!paymentPublicKey)
throw new Error('Unable to compute payment public key');
return paymentPublicKey;
}
derivePaymentPublicKey(paymentCode, idx) {
const a = paymentCode.getNotificationPrivateKey();
if (!this.ecc.isPrivate(a))
throw new Error('Received invalid private key');
const B = this.derive(idx).publicKey;
const S = this.ecc.pointMultiply(B, a);
return this.derivePublicKeyFromSharedSecret(B, S);
}
getAddressFromPubkey(pubKey, type) {
switch (type) {
case 'p2pkh': {
return utils.getP2pkhAddress(pubKey, this.network);
}
else if (ecc.isPoint(a)) {
if (!this._hasPrivKeys())
throw new Error('Unable to compute the derivation with a public key provided as argument');
const A = a;
const b_node = this.derive(idx);
if (!b_node.privateKey)
throw new Error('Unable to derive node with private key');
const b = b_node.privateKey;
B = b_node.publicKey;
S = ecc.pointMultiply(A, b);
case 'p2sh': {
return utils.getP2shAddress(pubKey, this.network);
}
if (!B || !ecc.isPoint(B))
throw new TypeError('Invalid derived public key');
if (!S)
throw new Error('Unable to compute resulting point');
const Sx = S.slice(1, 33);
const s = utils.sha256(Buffer.from(Sx));
if (!ecc.isPrivate(s))
throw new TypeError('Invalid shared secret');
const EccPoint = ecc.pointFromScalar(s);
if (!EccPoint)
throw new Error('Unable to compute point');
const paymentPublicKey = ecc.pointAdd(B, EccPoint);
if (!paymentPublicKey)
throw new TypeError('Unable to compute payment public key');
return Buffer.from(paymentPublicKey);
}
getPaymentAddress(a, idx, type = 'p2pkh') {
const pubkey = this.derivePaymentPublicKey(a, idx);
if (!pubkey)
throw new TypeError('Unable to derive public key');
switch (type) {
case 'p2pkh': {
return utils.getP2pkhAddress(pubkey, this.network);
}
case 'p2sh': {
return utils.getP2shAddress(pubkey, this.network);
}
case 'p2wpkh': {
return utils.getP2wpkhAddress(pubkey, this.network);
}
default: {
throw new Error('Unknown address type. Expected: p2pkh | p2sh | p2wpkh');
}
case 'p2wpkh': {
return utils.getP2wpkhAddress(pubKey, this.network);
}
default: {
throw new Error(`Unknown address type. Expected: p2pkh | p2sh | p2wpkh, got ${type}`);
}
}
}
const fromSeed = (bSeed, id, network = utils.networks.bitcoin) => {
const root = bip32.fromSeed(Buffer.from(bSeed));
getPaymentAddress(paymentCode, idx, type = 'p2pkh') {
const pubKey = this.derivePaymentPublicKey(paymentCode, idx);
return this.getAddressFromPubkey(pubKey, type);
}
getBlindedPaymentCode(destinationPaymentCode, outpoint, privateKey) {
const a = privateKey;
const B = destinationPaymentCode.getNotificationPublicKey();
const S = this.ecc.pointMultiply(B, a);
if (!S)
throw new Error('Unable to compute secret point');
const x = S.subarray(1, 33);
const o = outpoint;
const s = hmacSHA512(o, x);
const paymentCodeBuffer = this.paymentCode;
const blindedPaymentCode = paymentCodeBuffer.slice(0);
blindedPaymentCode.set(utils.xorUint8Arrays(s.subarray(0, 32), paymentCodeBuffer.subarray(3, 35)), 3);
blindedPaymentCode.set(utils.xorUint8Arrays(s.subarray(32, 64), paymentCodeBuffer.subarray(35, 67)), 35);
return utils.bytesToHex(blindedPaymentCode);
}
}
export class PaymentCodePrivate extends PaymentCodePublic {
constructor(root, ecc, bip32, buf, network = utils.networks.bitcoin) {
super(ecc, bip32, buf, network);
this.root = root;
this.hasPrivKeys = true;
}
toPaymentCodePublic() {
return new PaymentCodePublic(this.ecc, this.bip32, this.buf.slice(0), this.network);
}
clone() {
return new PaymentCodePrivate(this.root, this.ecc, this.bip32, this.buf.slice(0), this.network);
}
deriveHardened(index) {
return this.root.deriveHardened(index);
}
derivePaymentPublicKey(paymentCode, idx) {
const A = paymentCode.getNotificationPublicKey();
if (!this.ecc.isPoint(A))
throw new Error('Received invalid public key');
const b_node = this.derive(idx);
if (!b_node.privateKey)
throw new Error('Unable to derive node with private key');
const b = b_node.privateKey;
const B = b_node.publicKey;
const S = this.ecc.pointMultiply(A, b);
return this.derivePublicKeyFromSharedSecret(B, S);
}
getPaymentAddress(paymentCode, idx, type = 'p2pkh') {
const pubKey = this.derivePaymentPublicKey(paymentCode, idx);
return this.getAddressFromPubkey(pubKey, type);
}
derivePaymentPrivateKey(paymentCodePublic, idx) {
const A = paymentCodePublic.getNotificationPublicKey();
if (!this.ecc.isPoint(A))
throw new Error('Argument is not a valid public key');
const b_node = this.derive(idx);
if (!b_node.privateKey)
throw new Error('Unable to derive node without private key');
const b = b_node.privateKey;
const S = this.ecc.pointMultiply(A, b);
if (!S)
throw new Error('Unable to compute resulting point');
const Sx = S.subarray(1, 33);
const s = sha256(Sx);
if (!this.ecc.isPrivate(s))
throw new Error('Invalid shared secret');
const paymentPrivateKey = this.ecc.privateAdd(b, s);
if (!paymentPrivateKey)
throw new Error('Unable to compute payment private key');
return paymentPrivateKey;
}
getNotificationPrivateKey() {
const child = this.derive(0);
return child.privateKey;
}
getPaymentCodeFromNotificationTransactionData(scriptPubKey, outpoint, pubKey) {
if (!(scriptPubKey.length === 83 && scriptPubKey[0] === 0x6a && scriptPubKey[1] === 0x4c && scriptPubKey[2] === 0x50))
throw new Error('Invalid OP_RETURN payload');
const A = pubKey;
const b = this.getNotificationPrivateKey();
const S = this.ecc.pointMultiply(A, b);
if (!S)
throw new Error('Unable to compute secret point');
const x = S.subarray(1, 33);
const s = hmacSHA512(outpoint, x);
const blindedPaymentCode = scriptPubKey.subarray(3);
const paymentCodeBuffer = blindedPaymentCode.slice(0);
paymentCodeBuffer.set(utils.xorUint8Arrays(s.subarray(0, 32), blindedPaymentCode.subarray(3, 35)), 3);
paymentCodeBuffer.set(utils.xorUint8Arrays(s.subarray(32, 64), blindedPaymentCode.subarray(35, 67)), 35);
return new PaymentCodePublic(this.ecc, this.bip32, paymentCodeBuffer, this.network);
}
}
export const BIP47Factory = (ecc) => {
const bip32 = BIP32Factory(ecc);
const fromSeed = (bSeed, segwit = false, network = utils.networks.bitcoin) => {
const root = bip32.fromSeed(bSeed);
const coinType = (network.pubKeyHash === utils.networks.bitcoin.pubKeyHash) ? '0' : '1';
const root_bip47 = root.derivePath(`m/47'/${coinType}'/${id}'`);
const root_bip47 = root.derivePath(`m/47'/${coinType}'/0'`);
const pc = new Uint8Array(80);
pc.set([1, 0]);
if (root_bip47.publicKey.length !== 33)
throw new TypeError('Missing or wrong publicKey');
throw new Error('Missing or wrong publicKey');
pc.set(root_bip47.publicKey, 2);
if (root_bip47.chainCode.length !== 32)
throw new TypeError('Missing or wrong chainCode');
throw new Error('Missing or wrong chainCode');
pc.set(root_bip47.chainCode, 35);
const pcode = new PaymentCode(pc, network);
pcode.root = root_bip47;
return pcode;
if (segwit) {
pc[SAMOURAI_FEATURE_BYTE] = 1;
}
return new PaymentCodePrivate(root_bip47, ecc, bip32, pc, network);
};
const fromBase58 = (inString, network) => {
const buf = decode(inString);
const version = buf.slice(0, 1);
if (version[0] !== PC_VERSION)
throw new TypeError('Invalid version');
return new PaymentCode(buf.slice(1), network);
const buf = bs58check.decode(inString);
const version = buf[0];
if (version !== PC_VERSION)
throw new Error('Invalid version');
return new PaymentCodePublic(ecc, bip32, buf.slice(1), network);
};
const fromBuffer = (buf, network) => {
return new PaymentCode(buf, network);
return new PaymentCodePublic(ecc, bip32, buf, network);
};

@@ -156,0 +214,0 @@ return {

@@ -1,11 +0,10 @@

import { TinySecp256k1Interface as TinySecp256k1InterfaceBIP32 } from 'bip32';
import type { TinySecp256k1Interface as TinySecp256k1InterfaceBIP32 } from '@samouraiwallet/bip32';
export type AddressType = 'p2pkh' | 'p2sh' | 'p2wpkh';
interface Bip32 {
public: number;
private: number;
}
export interface Network {
messagePrefix: string;
bech32: string;
bip32: Bip32;
bip32: {
public: number;
private: number;
};
pubKeyHash: number;

@@ -16,3 +15,2 @@ scriptHash: number;

export interface TinySecp256k1Interface extends TinySecp256k1InterfaceBIP32 {
privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null;
pointMultiply(p: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null;

@@ -22,3 +20,2 @@ pointAdd(pA: Uint8Array, pB: Uint8Array, compressed?: boolean): Uint8Array | null;

}
export {};
//# sourceMappingURL=types.d.ts.map

@@ -1,11 +0,8 @@

/// <reference types="node" />
import { Network } from './types';
import type { Network } from './types.js';
export { hexToBytes, bytesToHex } from '@noble/hashes/utils';
export declare const networks: Record<'bitcoin' | 'regtest' | 'testnet', Network>;
export declare function ripemd160(buffer: Uint8Array): Uint8Array;
export declare function sha256(buffer: Uint8Array): Uint8Array;
export declare function hash160(buffer: Uint8Array): Uint8Array;
export declare function toBase58Check(hash: Uint8Array, version: number): string;
export declare function xorUint8Arrays(a: Uint8Array, b: Uint8Array): Uint8Array;
export declare function getP2pkhAddress(pubkey: Uint8Array, network: Network): string;
export declare function getP2shAddress(pubkey: Buffer, network: Network): string;
export declare function getP2shAddress(pubkey: Uint8Array, network: Network): string;
export declare function getP2wpkhAddress(pubkey: Uint8Array, network: Network): string;
//# sourceMappingURL=utils.d.ts.map

@@ -1,5 +0,3 @@

import { sha256 as noble_sha256 } from '@noble/hashes/sha256';
import { ripemd160 as noble_ripemd160 } from '@noble/hashes/ripemd160';
import bs58check from 'bs58check';
import { bech32 } from 'bech32';
import { bech32, bs58check, hash160 } from '@samouraiwallet/bip32/crypto';
export { hexToBytes, bytesToHex } from '@noble/hashes/utils';
export const networks = {

@@ -40,12 +38,13 @@ bitcoin: {

};
export function ripemd160(buffer) {
return noble_ripemd160(buffer);
export function xorUint8Arrays(a, b) {
if (a.length !== b.length) {
throw new Error('Uint8Arrays are not of the same length');
}
const result = new Uint8Array(a.length);
for (let i = 0; i < a.length; i++) {
result[i] = a[i] ^ b[i];
}
return result;
}
export function sha256(buffer) {
return noble_sha256(buffer);
}
export function hash160(buffer) {
return ripemd160(sha256(buffer));
}
export function toBase58Check(hash, version) {
function toBase58Check(hash, version) {
const payload = new Uint8Array(21);

@@ -52,0 +51,0 @@ payload.set([version], 0);

{
"name": "@samouraiwallet/bip47",
"version": "0.8.0",
"version": "1.0.0",
"engines": {

@@ -17,18 +17,13 @@ "node": ">=16.6.0"

"type": "module",
"main": "./dist/index.js",
"exports": {
".": "./dist/index.js"
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.js",
"types": "./dist/utils.d.ts"
}
},
"types": "./dist/index.d.ts",
"scripts": {
"test": "vitest run",
"test:watch": "vitest watch",
"typescript": "tsc --noEmit",
"lint": "eslint --ext .ts src/ test/",
"build:clean": "rm -rf dist",
"build:esm": "tsc -p tsconfig.build.json",
"build": "npm run build:clean && npm run build:esm",
"prepack": "npm run lint && npm run typescript && npm run test",
"prepare": "npm run build"
},
"sideEffects": false,
"repository": {

@@ -45,23 +40,30 @@ "type": "git",

"dependencies": {
"@noble/hashes": "1.3.0",
"bech32": "2.0.0",
"bip32": "4.0.0",
"bs58check": "3.0.1"
"@noble/hashes": "1.4.0",
"@samouraiwallet/bip32": "5.1.0"
},
"devDependencies": {
"@types/bs58check": "^2.1.0",
"@types/create-hash": "^1.2.2",
"@types/node": "^16.18.30",
"@types/tiny-secp256k1": "^2.0.1",
"@typescript-eslint/eslint-plugin": "^5.59.6",
"@typescript-eslint/parser": "^5.59.6",
"@vitest/coverage-c8": "^0.31.0",
"eslint": "^8.40.0",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-unicorn": "^47.0.0",
"tiny-secp256k1": "^2.2.1",
"typescript": "^5.0.4",
"vitest": "^0.31.0"
"@types/node": "^18.19.31",
"@typescript-eslint/eslint-plugin": "^7.6.0",
"@typescript-eslint/parser": "^7.6.0",
"@vitest/coverage-v8": "^1.5.0",
"eslint": "^8.57.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-unicorn": "^52.0.0",
"tiny-secp256k1": "^2.2.3",
"typedoc": "^0.25.13",
"typescript": "^5.4.5",
"vitest": "^1.5.0"
},
"scripts": {
"test": "vitest run",
"test:watch": "vitest watch",
"test:coverage": "vitest run --coverage",
"typescript": "tsc --noEmit",
"lint": "eslint --ext .ts src/ test/",
"docs": "typedoc src/*.ts",
"build:clean": "rm -rf dist",
"build:esm": "tsc -p tsconfig.build.json",
"build": "pnpm run build:clean && pnpm run build:esm"
}
}
}

@@ -5,58 +5,320 @@ # @samouraiwallet/bip47

This library uses ES Modules. Node.js v14 or later is required.
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.
## Usage
## Contents
- [Installation](#installation)
- [API documentation](#api-documentation)
- Usage
- [ECC library](#ecc-library)
- [Examples](#examples)
- [Interfaces](#interfaces)
You need to provide your own implementation of `secp256k1` elliptic curve.
[tiny-secp256k1](https://github.com/bitcoinjs/tiny-secp256k1) is recommended but you can use any other which has the same API.
## Installation
```bash
npm install @samouraiwallet/bip47
```
or
All necessary documentation of usage is provided via test files.
```bash
pnpm add @samouraiwallet/bip47
```
or
### Using in browser
```bash
yarn add @samouraiwallet/bip47
```
You will need webpack/browserify to bundle this library and it's dependencies to be able to use it in browser
environment.
Since `tiny-secp256k1` is a WASM library, it will require such bundler to be able to load WASM code.
Please consult the docs of a bundler that you use for further information.
## API documentation
Generated API documentation is available in the git repository. To view latest API docs locally, you can run these commands.
For example current webpack will require you to set `{ experiments: { asyncWebAssembly: true }}` in your config.
```bash
# create a temp folder
mkdir bip47-docs
cd bip47-docs
## Examples
# download and extract docs directory
curl -fsSL https://code.samourai.io/dojo/bip47-js/-/archive/master/bip47-js-master.tar.gz\?path\=docs | tar -xzv --strip-components=2
### Mainnet
# run simple HTTP file server
npx serve .
```
```js
import * as ecc from 'tiny-secp256k1';
import {BIP47Factory} from '@samouraiwallet/bip47';
## 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 PaymetCodePrivate instance from wallet master seed](#create-a-paymetcodeprivate-instance-from-wallet-master-seed)
- [Create a PaymetCodePublic instance from payment code string](#create-a-paymetcodepublic-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);
```
const aliceB58PCode = 'PM8TJTLJbPRGxSbc8EJi42Wrr6QbNSaSSVJ5Y3E4pbCYiTHUskHg13935Ubb7q8tx9GVbh2UuRnBc3WSyJHhUrw8KhprKnn9eDznYGieTzFcwQRya4GA';
const bobSeed = '87eaaac5a539ab028df44d9110defbef3797ddb805ca309f61a69ff96dbaa7ab5b24038cf029edec5235d933110f0aea8aeecf939ed14fc20730bba71e4b1110';
#### Create a PaymetCodePrivate instance from wallet master seed
const alicePcode = bip47.fromBase58(aliceB58PCode);
const bobPcode = bip47.fromSeed(Buffer.from(bobSeed, 'hex'), 0);
**on mainnet**
```ts
import type {PaymentCodePrivate} from "@samouraiwallet/bip47";
const bobNotifPrivKey = bobPcode.getNotificationPrivateKey();
let walletSeed: Uint8Array;
const alicePaymentAddr1 = alicePcode.getPaymentAddress(bobNotifPrivKey, 0, 'p2pkh'); // derive P2PKH payment address
const alicePaymentAddr2 = alicePcode.getPaymentAddress(bobNotifPrivKey, 1, 'p2wpkh'); // derive P2WPKH payment address
const alice: PaymentCodePrivate = bip47.fromSeed(walletSeed);
// with segwit support
const alice2: PaymentCodePrivate = bip47.fromSeed(walletSeed, true);
```
### Testnet
**on testnet**
```ts
import type {PaymentCodePrivate} from "@samouraiwallet/bip47";
import {networks} from "@samouraiwallet/bip47/utils";
```js
import * as ecc from 'tiny-secp256k1';
import {BIP47Factory, utils} from '@samouraiwallet/bip47';
let walletSeed: Uint8Array;
const networks = utils.networks;
// 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']);
```
const bip47 = BIP47Factory(ecc);
#### Create a PaymetCodePublic instance from payment code string
const b58PCode = 'PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97';
**on mainnet**
```ts
import type {PaymentCodePublic} from "@samouraiwallet/bip47";
const alicePcode = bip47.fromBase58(b58PCode, networks.testnet);
const aliceNotifAddr = alicePcode.getNotificationAddress();
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;
}
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc