Skip to content

Commit

Permalink
taproot
Browse files Browse the repository at this point in the history
  • Loading branch information
hank-schrader committed Jul 15, 2024
1 parent 36ad5b0 commit 6cdaf87
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 32 deletions.
26 changes: 13 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bellhdw",
"version": "0.5.37",
"version": "0.5.38",
"description": "Client-side Bitcoin JavaScript library",
"main": "./src/index.js",
"types": "./src/index.d.ts",
Expand Down Expand Up @@ -37,23 +37,23 @@
],
"dependencies": {
"@noble/hashes": "1.3.3",
"belcoinjs-lib": "0.0.9",
"belcoinjs-lib": "0.1.1",
"bells-secp256k1": "0.1.0",
"belpair": "0.1.0",
"belpair": "0.1.2",
"nintondo-bip39": "1.0.0",
"bn.js": "5.2.1",
"browser-hdkey": "0.1.7"
"browser-hdkey": "0.1.8"
},
"devDependencies": {
"@types/bn.js": "5.1.1",
"@types/bs58": "4.0.0",
"@types/node": "20.5.9",
"@typescript-eslint/eslint-plugin": "5.45.0",
"@typescript-eslint/parser": "5.45.0",
"better-npm-audit": "3.7.3",
"prettier": "2.8.0",
"rimraf": "2.6.3",
"typescript": "4.4.4"
"@types/bn.js": "^5.1.1",
"@types/bs58": "^4.0.0",
"@types/node": "^20.5.9",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"better-npm-audit": "^3.7.3",
"prettier": "^2.8.0",
"rimraf": "^2.6.3",
"typescript": "^4.4.4"
},
"license": "MIT"
}
91 changes: 82 additions & 9 deletions ts_src/hd/private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,64 @@ import {
hexToBytes as fromHex,
} from "@noble/hashes/utils";
import { ZERO_KEY, ZERO_PRIVKEY } from "./common";
import type {
import {
Keyring,
PrivateKeyOptions,
SerializedHDKey,
Hex,
ToSignInput,
FromSeedOpts,
FromMnemonicOpts,
AddressType,
} from "./types";
import { BaseWallet } from "./base";
import * as tinysecp from "bells-secp256k1";
import { mnemonicToSeed } from "nintondo-bip39";
import ECPairFactory, { ECPairInterface } from "belpair";
import { Psbt } from "belcoinjs-lib";
import { Network, networks, Psbt, Signer } from "belcoinjs-lib";
import HDKey from "browser-hdkey";
import { sha256 } from "@noble/hashes/sha256";
import { crypto as belCrypto } from "belcoinjs-lib";
import { toXOnly } from "../utils/util";

const ECPair = ECPairFactory(tinysecp);

const DEFAULT_HD_PATH = "m/44'/0'/0'/0";

function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
return belCrypto.taggedHash(
"TapTweak",
Buffer.concat(h ? [pubKey, h] : [pubKey])
);
}

function tweakSigner(
signer: Signer,
opts: { network: Network; tweakHash?: Buffer }
): Signer {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
let privateKey: Uint8Array | undefined = signer.privateKey!;
if (!privateKey) {
throw new Error("Private key is required for tweaking signer!");
}
if (signer.publicKey[0] === 3) {
privateKey = tinysecp.privateNegate(privateKey);
}

const tweakedPrivateKey = tinysecp.privateAdd(
privateKey,
tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)
);
if (!tweakedPrivateKey) {
throw new Error("Invalid tweaked private key!");
}

return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), {
network: opts.network,
});
}

class HDPrivateKey extends BaseWallet implements Keyring<SerializedHDKey> {
hideRoot?: boolean;

Expand Down Expand Up @@ -123,17 +160,45 @@ class HDPrivateKey extends BaseWallet implements Keyring<SerializedHDKey> {
signPsbt(psbt: Psbt, inputs: ToSignInput[]) {
let account: ECPairInterface | undefined;

inputs.map((i) => {
account = this.findAccountByPk(i.publicKey);
psbt.signInput(i.index, account, i.sighashTypes);
inputs.forEach((input) => {
account = this.findAccountByPk(input.publicKey);
if (this.addressType === AddressType.P2TR) {
const signer = tweakSigner(account, {
network: this.network ?? networks.bellcoin,
});
psbt.signInput(input.index, signer, input.sighashTypes);
} else {
const signer = account;
psbt.signInput(input.index, signer, input.sighashTypes);
}
});

psbt.finalizeAllInputs();
}

signAllInputsInPsbt(psbt: Psbt, accountAddress: string) {
const account = this.findAccount(accountAddress);
psbt.signAllInputs(account);

psbt.data.inputs.forEach((input, idx) => {
if (this.addressType === AddressType.P2TR) {
const signer = tweakSigner(account, {
network: this.network ?? networks.bellcoin,
});
psbt.signInput(
idx,
signer,
input.sighashType !== undefined ? [input.sighashType] : undefined
);
} else {
const signer = account;
psbt.signInput(
idx,
signer,
input.sighashType !== undefined ? [input.sighashType] : undefined
);
}
});

return {
signatures: psbt.data.inputs.map((i) => {
if (
Expand All @@ -156,9 +221,17 @@ class HDPrivateKey extends BaseWallet implements Keyring<SerializedHDKey> {
}[] {
let account: ECPairInterface | undefined;

inputs.map((i) => {
account = this.findAccountByPk(i.publicKey);
psbt.signInput(i.index, account, i.sighashTypes);
inputs.forEach((input) => {
account = this.findAccountByPk(input.publicKey);
if (this.addressType === AddressType.P2TR) {
const signer = tweakSigner(account, {
network: this.network ?? networks.bellcoin,
});
psbt.signInput(input.index, signer, input.sighashTypes);
} else {
const signer = account;
psbt.signInput(input.index, signer, input.sighashTypes);
}
});

return psbt.data.inputs.map((f, i) => ({
Expand Down
95 changes: 86 additions & 9 deletions ts_src/hd/simple.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,55 @@
import { Keyring, SerializedSimpleKey, ToSignInput } from "./types";
import {
AddressType,
Keyring,
SerializedSimpleKey,
ToSignInput,
} from "./types";
import { ZERO_KEY, ZERO_PRIVKEY } from "./common";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
import { BaseWallet } from "./base";
import * as tinysecp from "bells-secp256k1";
import ECPairFactory, { ECPairInterface } from "belpair";
import { Psbt } from "belcoinjs-lib";
import { Network, networks, Psbt, Signer } from "belcoinjs-lib";
import { sha256 } from "@noble/hashes/sha256";
import { crypto as belCrypto } from "belcoinjs-lib";
import { toXOnly } from "../utils/util";

const ECPair = ECPairFactory(tinysecp);

function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
return belCrypto.taggedHash(
"TapTweak",
Buffer.concat(h ? [pubKey, h] : [pubKey])
);
}

function tweakSigner(
signer: Signer,
opts: { network: Network; tweakHash?: Buffer }
): Signer {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
let privateKey: Uint8Array | undefined = signer.privateKey!;
if (!privateKey) {
throw new Error("Private key is required for tweaking signer!");
}
if (signer.publicKey[0] === 3) {
privateKey = tinysecp.privateNegate(privateKey);
}

const tweakedPrivateKey = tinysecp.privateAdd(
privateKey,
tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)
);
if (!tweakedPrivateKey) {
throw new Error("Invalid tweaked private key!");
}

return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), {
network: opts.network,
});
}

class HDSimpleKey extends BaseWallet implements Keyring<SerializedSimpleKey> {
privateKey: Uint8Array = ZERO_PRIVKEY;
publicKey = ZERO_KEY;
Expand Down Expand Up @@ -106,9 +147,18 @@ class HDSimpleKey extends BaseWallet implements Keyring<SerializedSimpleKey> {
signPsbt(psbt: Psbt, inputs: ToSignInput[]) {
this.initPair();

for (let i of inputs) {
psbt.signInput(i.index, this.pair!, i.sighashTypes);
}
inputs.forEach((input) => {
const account = this.pair!;
if (this.addressType === AddressType.P2TR) {
const signer = tweakSigner(account, {
network: this.network ?? networks.bellcoin,
});
psbt.signInput(input.index, signer, input.sighashTypes);
} else {
const signer = account;
psbt.signInput(input.index, signer, input.sighashTypes);
}
});
psbt.finalizeAllInputs();
}

Expand All @@ -120,7 +170,26 @@ class HDSimpleKey extends BaseWallet implements Keyring<SerializedSimpleKey> {
throw new Error(
"Provided account address does not match the wallet's address"
);
psbt.signAllInputs(this.pair!);

psbt.data.inputs.forEach((input, idx) => {
if (this.addressType === AddressType.P2TR) {
const signer = tweakSigner(this.pair!, {
network: this.network ?? networks.bellcoin,
});
psbt.signInput(
idx,
signer,
input.sighashType !== undefined ? [input.sighashType] : undefined
);
} else {
psbt.signInput(
idx,
this.pair!,
input.sighashType !== undefined ? [input.sighashType] : undefined
);
}
});

return {
signatures: psbt.data.inputs.map((i) => {
if (
Expand All @@ -144,9 +213,17 @@ class HDSimpleKey extends BaseWallet implements Keyring<SerializedSimpleKey> {
this.initPair();
if (this.pair === undefined)
throw new Error("Cannot sign inputs since pair is undefined");
for (let i of inputs) {
psbt.signInput(i.index, this.pair!, i.sighashTypes);
}
inputs.forEach((input) => {
if (this.addressType === AddressType.P2TR) {
const signer = tweakSigner(this.pair!, {
network: this.network ?? networks.bellcoin,
});
psbt.signInput(input.index, signer, input.sighashTypes);
} else {
const signer = this.pair!;
psbt.signInput(input.index, signer, input.sighashTypes);
}
});
return psbt.data.inputs.map((f, i) => ({
inputIndex: i,
partialSig: f.partialSig?.flatMap((p) => p) ?? [],
Expand Down
2 changes: 1 addition & 1 deletion ts_src/utils/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,4 @@ export function parsePath(path: string): number[] {
}

export const toXOnly = (pubKey: Buffer) =>
pubKey.length === 32 ? pubKey : pubKey.slice(1, 33);
pubKey.length === 32 ? pubKey : pubKey.slice(1, 33);

0 comments on commit 6cdaf87

Please sign in to comment.