Commit 43395a53 authored by kenshin-samourai's avatar kenshin-samourai
Browse files

Merge branch 'feature/typescript' into 'develop'

Feature/typescript

See merge request !11
parents 1f7f361f c41f6c71
node_modules/ node_modules/
private-tests/ private-tests/
dist/
src/
test/
bin/
.vscode/
.idea/
tsconfig*
#!/bin/sh
cat >$(pwd)/dist/cjs/package.json <<!EOF
{
"type": "commonjs"
}
!EOF
cat >$(pwd)/dist/esm/package.json <<!EOF
{
"type": "module"
}
!EOF
This diff is collapsed.
{ {
"name": "bip47-js", "name": "bip47-js",
"version": "0.4.0", "version": "0.4.0",
"engines": {
"node": ">=12.0.0"
},
"description": "A set of utilities for working with BIP47 and bitcoinjs-lib", "description": "A set of utilities for working with BIP47 and bitcoinjs-lib",
"main": "src/index.js", "main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
}
},
"scripts": { "scripts": {
"test": "mocha --recursive --reporter spec" "test": "mocha -r ts-node/register test/**/*.ts",
"typescript": "tsc --noEmit",
"build:clean": "rm -rf dist",
"build:cjs": "tsc -p tsconfig.build.cjs.json",
"build:esm": "tsc -p tsconfig.build.esm.json",
"build": "npm run build:clean && npm run build:cjs && npm run build:esm && bin/fixup.sh",
"prepack": "npm run typescript && npm run test",
"prepare": "npm run build"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+ssh://git@code.samourai.io:dojo/bip47-js.git" "url": "https://code.samourai.io/dojo/bip47-js.git"
},
"bugs": {
"url": "https://code.samourai.io/dojo/bip47-js/-/issues"
}, },
"author": "Katana Cryptographic Ltd.", "author": "Katana Cryptographic Ltd.",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
...@@ -17,10 +37,16 @@ ...@@ -17,10 +37,16 @@
"bip32": "2.0.6", "bip32": "2.0.6",
"bs58check": "2.1.2", "bs58check": "2.1.2",
"create-hash": "1.2.0", "create-hash": "1.2.0",
"safe-buffer": "5.2.0",
"tiny-secp256k1": "1.1.6" "tiny-secp256k1": "1.1.6"
}, },
"devDependencies": { "devDependencies": {
"mocha": "^7.1.1" "@types/bs58check": "^2.1.0",
"@types/create-hash": "^1.2.2",
"@types/mocha": "^8.2.2",
"@types/node": "^14.14.41",
"@types/tiny-secp256k1": "^1.0.0",
"mocha": "^8.3.2",
"ts-node": "^9.1.1",
"typescript": "^4.2.4"
} }
} }
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var bip47 = require("./payment-code");
exports.fromBuffer = bip47.fromBuffer;
exports.fromBase58 = bip47.fromBase58;
exports.fromWalletSeed = bip47.fromWalletSeed;
exports.utils = require('./utils');
\ No newline at end of file
export { fromBuffer, fromBase58, fromWalletSeed } from "./payment-code";
export * as utils from "./utils";
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ecc = require('tiny-secp256k1');
const { networks, getP2pkhAddress, sha256 } = require('./utils');
const { fromPublicKey, fromSeed } = require('bip32');
const { encode, decode } = require('bs58check');
const PC_VERSION = 0x47;
class PaymentCode {
constructor(buf, network) {
network = network || 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 = fromPublicKey(this.pubKey, this.chainCode, this.network);
}
static fromSeed(bSeed, id, network) {
network = network || networks.bitcoin;
const reserved = Buffer.alloc(13, 0);
const root = fromSeed(bSeed);
const coinType = (network.pubKeyHash == networks.bitcoin.pubKeyHash) ? '0' : '1';
const root_bip47 = root.derivePath(`m/47'/${coinType}'/${id}'`);
let pc = Buffer.from('0100', 'hex'); // version + options
pc = Buffer.concat([pc, root_bip47.publicKey]);
pc = Buffer.concat([pc, root_bip47.chainCode]);
if (pc.length !== 67)
throw new TypeError('Missing or wrong publicKey or chainCode');
pc = Buffer.concat([pc, reserved]); // reserved bytes
const pcode = new PaymentCode(pc, network);
pcode.root = root_bip47; // store the privkey
return pcode;
}
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 getP2pkhAddress(child.publicKey, this.network);
}
derivePaymentPrivateKey(A, idx) {
if (!ecc.isPoint(A))
throw new TypeError('Argument is not a valid public key');
const b_node = this.derive(idx);
const b = b_node.privateKey;
const S = ecc.pointMultiply(A, b);
const Sx = S.slice(1, 33);
const s = sha256(Sx);
if (!ecc.isPrivate(s))
throw new TypeError('Invalid shared secret');
return ecc.privateAdd(b, s);
}
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)) {
// a is a private key
B = this.derive(idx).publicKey;
S = ecc.pointMultiply(B, a);
} else if (ecc.isPoint(a)) {
if (!this._hasPrivKeys())
throw new Error('Unable to compute the derivation with a public key provided as argument');
// a is a public key
const A = a;
const b_node = this.derive(idx);
const b = b_node.privateKey;
B = b_node.publicKey;
S = ecc.pointMultiply(A, b);
}
if (!ecc.isPoint(B))
throw new TypeError('Invalid derived public key');
const Sx = S.slice(1, 33);
const s = sha256(Sx);
if (!ecc.isPrivate(s))
throw new TypeError('Invalid shared secret');
return ecc.pointAdd(B, ecc.pointFromScalar(s));
}
getPaymentAddress(a, idx) {
const pubkey = this.derivePaymentPublicKey(a, idx);
return getP2pkhAddress(pubkey, this.network);
}
}
function 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);
}
exports.fromBase58 = fromBase58;
function fromBuffer(buf, network) {
return new PaymentCode(buf, network);
}
exports.fromBuffer = fromBuffer;
function fromWalletSeed(bSeed, id, network) {
return PaymentCode.fromSeed(bSeed, id, network);
}
exports.fromWalletSeed = fromWalletSeed;
import ecc from 'tiny-secp256k1';
import {networks, getP2pkhAddress, sha256, Network} from './utils';
import {BIP32Interface, fromPublicKey, fromSeed} from 'bip32';
import {encode, decode} from 'bs58check';
const PC_VERSION = 0x47;
class PaymentCode {
version: Buffer;
buf: Buffer;
network: Network;
root: BIP32Interface;
constructor(buf: Buffer, network: Network = 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 = fromPublicKey(this.pubKey, this.chainCode, this.network);
}
static fromSeed(bSeed: Buffer, id: number | string, network: Network = networks.bitcoin): PaymentCode {
const reserved = Buffer.alloc(13, 0);
const root = fromSeed(bSeed);
const coinType = (network.pubKeyHash === networks.bitcoin.pubKeyHash) ? '0' : '1';
const root_bip47 = root.derivePath(`m/47'/${coinType}'/${id}'`);
let pc = Buffer.from('0100', 'hex'); // version + options
pc = Buffer.concat([pc, root_bip47.publicKey]);
pc = Buffer.concat([pc, root_bip47.chainCode]);
if (pc.length !== 67)
throw new TypeError('Missing or wrong publicKey or chainCode');
pc = Buffer.concat([pc, reserved]); // reserved bytes
const pcode = new PaymentCode(pc, network);
pcode.root = root_bip47; // store the privkey
return pcode;
}
get features(): Buffer {
return this.buf.slice(1, 1);
}
get pubKey(): Buffer {
return this.buf.slice(2, 2 + 33);
}
get chainCode(): Buffer {
return this.buf.slice(35, 35 + 32);
}
get paymentCode(): Buffer {
return this.buf;
}
toBase58(): string {
const version = Buffer.from([PC_VERSION]);
const buf = Buffer.concat([version, this.buf]);
return encode(buf);
}
_hasPrivKeys(): boolean {
return this.root.privateKey != null;
}
derive(index: number): BIP32Interface {
return this.root.derive(index);
}
deriveHardened(index: number): BIP32Interface {
return this.root.deriveHardened(index);
}
getNotificationAddress(): string {
const child = this.derive(0);
return getP2pkhAddress(child.publicKey, this.network);
}
derivePaymentPrivateKey(A: Buffer, idx: number): Buffer {
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 = sha256(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 paymentPrivateKey;
}
derivePaymentPublicKey(a: Buffer, idx: number): Buffer {
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)) {
// a is a private key
B = this.derive(idx).publicKey;
S = ecc.pointMultiply(B, a);
} else if (ecc.isPoint(a)) {
if (!this._hasPrivKeys())
throw new Error('Unable to compute the derivation with a public key provided as argument');
// a is a public key
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);
}
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 = sha256(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 paymentPublicKey;
}
getPaymentAddress(a: Buffer, idx: number): string {
const pubkey = this.derivePaymentPublicKey(a, idx);
if (!pubkey)
throw new TypeError('Unable to derive public key')
return getP2pkhAddress(pubkey, this.network);
}
}
export function fromBase58(inString: string, network?: Network): PaymentCode {
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);
}
export function fromBuffer(buf: Buffer, network?: Network) {
return new PaymentCode(buf, network);
}
export function fromWalletSeed(bSeed: Buffer, id: number | string, network?: Network) {
return PaymentCode.fromSeed(bSeed, id, network);
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Buffer = require('safe-buffer').Buffer;
const createHash = require('create-hash');
const bs58check = require('bs58check');
exports.networks = {};
exports.networks.bitcoin = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bc',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x00,
scriptHash: 0x05,
wif: 0x80,
};
exports.networks.regtest = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bcrt',
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
};
exports.networks.testnet = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'tb',
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
};
function ripemd160(buffer) {
return createHash('rmd160').update(buffer).digest();
}
exports.ripemd160 = ripemd160;
function sha256(buffer) {
return createHash('sha256').update(buffer).digest();
}
exports.sha256 = sha256;
function hash160(buffer) {
return ripemd160(sha256(buffer));
}
exports.hash160 = hash160;
function toBase58Check(hash, version) {
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(version, 0);
hash.copy(payload, 1);
return bs58check.encode(payload);
}
exports.toBase58Check = toBase58Check;
function getP2pkhAddress(pubkey, network) {
return toBase58Check(hash160(pubkey), network.pubKeyHash)
}
exports.getP2pkhAddress = getP2pkhAddress;
import createHash from 'create-hash';
import bs58check from 'bs58check';
export const networks = {
bitcoin: {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bc',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x00,
scriptHash: 0x05,
wif: 0x80,
},
regtest: {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bcrt',
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
},
testnet: {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'tb',
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
}
} as const;
export type Network = typeof networks[keyof typeof networks];
export function ripemd160(buffer: Buffer) {
return createHash('rmd160').update(buffer).digest();
}
export function sha256(buffer: Buffer) {
return createHash('sha256').update(buffer).digest();
}
export function hash160(buffer: Buffer) {
return ripemd160(sha256(buffer));
}
export function toBase58Check(hash: Buffer, version: number) {
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(version, 0);
hash.copy(payload, 1);
return bs58check.encode(payload);
}