Skip to content

Commit

Permalink
fix(bitcoin): fix bech32 regex to not include taproot addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
kodemon committed Jul 10, 2023
1 parent 6faaef8 commit 3876b1b
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 40 deletions.
85 changes: 54 additions & 31 deletions src/Methods/Order/CreateSignablePsbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ export const createSignablePsbt = method({
pubkey: string.optional(),
}),
handler: async ({ network, location, maker, pubkey }) => {
const lookup = new Lookup(network);

const psbt = new Psbt({ network: lookup.btcnetwork });
const [hash, index] = utils.parse.location(location);
const tapInternalKey = pubkey ? Buffer.from(pubkey, "hex") : undefined;
const btcNetwork = utils.bitcoin.getBitcoinNetwork(network);

const address = utils.bitcoin.getBitcoinAddress(maker);
if (address === undefined) {
throw new BadRequestError("Provided maker address is invalid");
}
// ### Location
// Ensure that the location the signature is being created for exists and
// belongs to the maker.

const tx = await new Lookup(network).getTransaction(hash);
const tx = await lookup.getTransaction(hash);
if (tx === undefined) {
throw new BadRequestError("Could not find transaction");
}
Expand All @@ -33,35 +33,58 @@ export const createSignablePsbt = method({
throw new BadRequestError("Provided maker address does not match location output");
}

const psbt = new Psbt({ network: btcNetwork });
// ### Input
// Add transaction input to the PSBT. Determine the input structure based
// on the address type of the provided maker.

const type = utils.bitcoin.getAddressType(maker);

if (type === "bech32") {
psbt.addInput({
hash,
index,
witnessUtxo: {
script: Buffer.from(vout.scriptPubKey.hex, "hex"),
value: 0,
},
});
} else if (type === "taproot") {
if (tapInternalKey === undefined) {
throw new BadRequestError("Taproot address requires a pubkey");
if (type === undefined) {
throw new BadRequestError("Provided maker address does not match supported address types.");
}

switch (type) {
case "taproot": {
if (pubkey === undefined) {
throw new BadRequestError("Taproot address requires a pubkey");
}
const tapInternalKey = Buffer.from(pubkey, "hex");
if (tapInternalKey.length !== 32) {
throw new BadRequestError("Taproot pubkey must be 32 bytes");
}
psbt.addInput({
hash,
index,
witnessUtxo: {
script: utils.taproot.getPaymentOutput(tapInternalKey, lookup.btcnetwork),
value: 0,
},
tapInternalKey,
});
break;
}

case "bech32": {
psbt.addInput({
hash,
index,
witnessUtxo: {
script: Buffer.from(vout.scriptPubKey.hex, "hex"),
value: 0,
},
});
break;
}

default: {
psbt.addInput({ hash, index });
}
psbt.addInput({
hash,
index,
witnessUtxo: {
script: utils.taproot.getPaymentOutput(tapInternalKey, btcNetwork),
value: 0,
},
tapInternalKey,
});
} else {
psbt.addInput({ hash, index });
}

// ### Output
// Add transaction output to the PSBT. The output is a empty OP_RETURN output
// with the maker address as the value.

psbt.addOutput({
script: payments.embed({ data: [Buffer.from(maker, "utf8")] }).output!,
value: 0,
Expand Down
15 changes: 12 additions & 3 deletions src/Methods/Taproot/Bip84/GetBip84Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ export const getBip84Account = method({
network: validate.schema.network,
mnemonic: string,
account: number,
path: string.optional(),
}),
handler: async ({ mnemonic, account, network }) => {
handler: async ({ network, mnemonic, account, path }) => {
const masterNode = utils.taproot.getMasterNode(mnemonic, network);
const wallet = Wallet.fromBase58(utils.taproot.getBip84Account(masterNode, network, account).toBase58(), network);

let wallet = Wallet.fromBase58(utils.taproot.getBip84Account(masterNode, network, account).toBase58(), network);
if (path !== undefined) {
wallet = Wallet.fromPrivateKey(wallet.privateKey!.toString("hex"), network).derive(path);
}

return {
key: wallet.privateKey?.toString("hex"),
privateKey: wallet.privateKey?.toString("hex"),
publicKey: wallet.publicKey.toString("hex"),
xPublicKey: wallet.internalPubkey.toString("hex"),
address: wallet.address,
};
},
});
20 changes: 16 additions & 4 deletions src/Methods/Taproot/Bip84/GetBip84Address.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { method } from "@valkyr/api";
import { BadRequestError, method } from "@valkyr/api";
import Schema, { number, string } from "computed-types";

import { Wallet } from "../../../Libraries/Wallet";
Expand All @@ -7,12 +7,24 @@ import { validate } from "../../../Validators";
export const getBip84Address = method({
params: Schema({
network: validate.schema.network,
from: string,
key: string,
type: validate.schema.bip84.type,
index: number,
}),
handler: async ({ key, type, index, network }) => {
const wallet = Wallet.fromPrivateKey(key, network)[type](index);
return wallet.address;
handler: async ({ from, key, type, index, network }) => {
switch (from) {
case "privateKey": {
const wallet = Wallet.fromPrivateKey(key, network)[type](index);
return wallet.address;
}
case "publicKey": {
const wallet = Wallet.fromPublicKey(key, network)[type](index);
return wallet.address;
}
default: {
throw new BadRequestError("Unknown wallet parser type provided");
}
}
},
});
24 changes: 24 additions & 0 deletions src/Methods/Taproot/SignPSBT.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { method } from "@valkyr/api";
import { Psbt } from "bitcoinjs-lib";
import Schema, { string } from "computed-types";

import { Wallet } from "../../Libraries/Wallet";
import { utils } from "../../Utilities";
import { validate } from "../../Validators";

export const signPsbt = method({
params: Schema({
network: validate.schema.network,
psbt: string,
key: string,
}),
handler: async (params) => {
const wallet = Wallet.fromPrivateKey(params.key, params.network);

const psbt = Psbt.fromBase64(params.psbt, {
network: utils.bitcoin.getBitcoinNetwork(params.network),
}).signAllInputs(wallet.signer);

return { psbt: psbt.toBase64() };
},
});
2 changes: 2 additions & 0 deletions src/Methods/Taproot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { getBip84PrivateKey } from "./Bip84/GetBip84PrivateKey";
import { getBip84PublicKey } from "./Bip84/GetBip84PublicKey";
import { createTransaction } from "./CreateTransaction";
import { createWallet } from "./CreateWallet";
import { signPsbt } from "./SignPSBT";

api.register("CreateWallet", createWallet);
api.register("CreateTransaction", createTransaction);
api.register("GetBip84Account", getBip84Account);
api.register("GetBip84PublicKey", getBip84PublicKey);
api.register("GetBip84PrivateKey", getBip84PrivateKey);
api.register("GetBip84Address", getBip84Address);
api.register("SignPsbt", signPsbt);
4 changes: 2 additions & 2 deletions src/Utilities/Bitcoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ const addressFormats = {
mainnet: {
p2pkh: /^[1][a-km-zA-HJ-NP-Z1-9]{25,34}$/, // legacy
p2sh: /^[3][a-km-zA-HJ-NP-Z1-9]{25,34}$/, // segwit
bech32: /^(bc1[qp])[a-zA-HJ-NP-Z0-9]{14,74}$/, // bech32
bech32: /^(bc1q)[a-zA-HJ-NP-Z0-9]{14,74}$/, // bech32
taproot: /^(bc1p)[a-zA-HJ-NP-Z0-9]{14,74}$/, // taproot
},
other: {
p2pkh: /^[mn][a-km-zA-HJ-NP-Z1-9]{25,34}$/,
p2sh: /^[2][a-km-zA-HJ-NP-Z1-9]{25,34}$/,
bech32: /^(tb1[qp]|bcrt1[qp])[a-zA-HJ-NP-Z0-9]{14,74}$/,
bech32: /^(tb1q|bcrt1q)[a-zA-HJ-NP-Z0-9]{14,74}$/,
taproot: /^(tb1p|bcrt1p)[a-zA-HJ-NP-Z0-9]{14,74}$/,
},
} as const;
Expand Down

0 comments on commit 3876b1b

Please sign in to comment.