Skip to content

Instantly share code, notes, and snippets.

@Neggia
Last active August 11, 2023 21:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Neggia/1b0485997d490040c613d4bd9ddc1cc3 to your computer and use it in GitHub Desktop.
Save Neggia/1b0485997d490040c613d4bd9ddc1cc3 to your computer and use it in GitHub Desktop.
TAALSigner
import React from 'react';
import { sendMessageAndWaitForResponse } from '../utils/globals';
import { connect, setNetwork } from '../store/portSlice';
import store, { RootState } from '../store/store';
import { TAALSigner } from '../signers/TAALSigner';
import {
bsv,
DefaultProvider,
DefaultProviderOption,
} from 'scrypt-ts'
type ConnectTAALWalletButtonProps = {
onConnected: (signer: TAALSigner) => void;
};
const ConnectTAALWalletButton: React.FC<ConnectTAALWalletButtonProps> = (props) => {
const connectWallet = async () => {
store.dispatch(connect());
const state: RootState = store.getState();
const port: RootState['port']['value'] = state.port.value;
if(!port){
console.error('Port is not defined');
} else
try {
const response = await sendMessageAndWaitForResponse(port, { action: 'connect' });
// console.log('connectWallet() response:', response);
if(response.action === 'error'){
alert(response.payload.reason);
throw new Error(`TAAL wallet connection error: ${response.payload.reason}`);
}
//Test code start
// const responseBalance = await sendMessageAndWaitForResponse(port, { action: 'getBalance' });
// console.log('connectWallet() getBalance:', responseBalance);
// const responseUnspent = await sendMessageAndWaitForResponse(port, { action: 'getUnspent' });
// console.log('connectWallet() getUnspent:', responseUnspent);
//Test code end
// https://github.com/sCrypt-Inc/tic-tac-toe/blob/main/src/App.tsx
const getNetworkResponse = await sendMessageAndWaitForResponse(port, { action: 'getNetwork' });
store.dispatch(setNetwork(getNetworkResponse.payload));
// const network = getNetworkResponse.payload;
const options: DefaultProviderOption = {
// taal: 'your-taal-api-key',
// gorillapool: 'your-gorillapool-api-key',
// sensible: 'your-sensible-api-key',
// scrypt: scryptConfig,
network: getNetworkResponse.payload, // You can use bsv.Networks.mainnet or bsv.Networks.testnet, or any other custom network
};
const provider = new DefaultProvider(options);
const signer = new TAALSigner(provider);
// Call the onConnected callback and pass the initialized signer
props.onConnected(signer);
} catch (error) {
if (error instanceof Error) {
console.error('Error:', error.message);
} else {
console.error('Error:', error);
}
}
};
return (
<button onClick={connectWallet}>
Connect TAAL Wallet
</button>
);
};
export default ConnectTAALWalletButton;
import { connect } from '../store/portSlice';
import store, { RootState } from '../store/store';
// import { sendMessageAndWaitForResponse } from '../utils/globals';
// src/utils.ts
// import { Store } from 'redux'; // Assuming you are using Redux for state management
interface Port {
postMessage: (message: any) => void;
onMessage: {
addListener: (listener: (response: any) => void) => void;
removeListener: (listener: (response: any) => void) => void;
};
}
interface QueueItem {
request: () => Promise<any>;
resolve: (response: any) => void;
reject: (error: any) => void;
}
const requestQueue: QueueItem[] = [];
const delayBetweenCalls = 400; //https://docs.taal.com/core-products/whatsonchain#rate-limits
const processQueue = async () => {
if (requestQueue.length === 0) {
return;
}
const currentItem = requestQueue[0];
try {
const response = await currentItem.request();
currentItem.resolve(response);
} catch (error) {
currentItem.reject(error);
} finally {
requestQueue.shift();
processQueue();
}
};
let currentRequestId = 0;
export function sendMessageAndWaitForResponse(port: Port, message: any): Promise<any> {
return new Promise((resolve, reject) => {
const requestId = currentRequestId++;
message.requestId = requestId;
const request = async () => {
return new Promise((resolve, reject) => {
// Send the message
port.postMessage(message);
// Set up the response listener
function listener(response: any) {
if (response.requestId !== requestId) {
// Ignore the response if it doesn't match the request ID
return;
}
// Remove the listener after receiving the response
port.onMessage.removeListener(listener);
// Resolve the Promise with the response
resolve(response);
}
port.onMessage.addListener(listener);
// Set up a timeout to reject the Promise if there's no response after a specified time
setTimeout(() => {
// Remove the listener if it's still active
port.onMessage.removeListener(listener);
// Reject the Promise with an error
reject(new Error('Timeout waiting for response'));
}, 10000); // 10 seconds timeout
});
};
requestQueue.push({ request, resolve, reject });
if (requestQueue.length === 1) {
processQueue();
}
});
}
// export async function sendAction(store: Store, action: string, payload: any): Promise<any> {
export async function sendAction(action: string, payload?: any): Promise<any> {
const state: RootState = store.getState();
const port: RootState['port']['value'] = state.port.value;
if(!port){
console.error('Port is not defined');
} else
try{
const message = payload ? { action, payload } : { action };
const response = await sendMessageAndWaitForResponse(port, message);
if (response.action === 'error') {
throw new Error(response.payload.reason);
}
return response;
} catch (error) {
if (error instanceof Error) {
console.error('Error:', error.message);
} else {
console.error('Error:', error);
}
}
}
export function isTAALChromeWalletConnected(): boolean {
const state: RootState = store.getState();
const port: RootState['port']['value'] = state.port.value;
if(port === undefined) {
return false;
} else {
return true;
}
}
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
interface FetchTransactionOptions {
callQueue?: boolean;
raw?: boolean;
}
export async function fetchTransactionByHash(txid: string, options?: FetchTransactionOptions) {
const callQueue = options?.callQueue;
const raw = options?.raw;
let localStorageItemId: string;
if(raw){
localStorageItemId = `txraw-${txid}`;
} else {
localStorageItemId = `tx-${txid}`;
}
// Check if the transaction is in the cache
const cachedTransaction = localStorage.getItem(localStorageItemId);
if (cachedTransaction) {
console.log('Tx fetched from cache: ', txid);
// Parse and return the cached transaction
if(raw){
return cachedTransaction;
} else {
return JSON.parse(cachedTransaction);
}
}
if(callQueue){
// Wait for the specified delay duration before making the API call
await sleep(delayBetweenCalls);
}
const state: RootState = store.getState();
const network: RootState['port']['network'] = state.port.network;
let whatsonchainUrlNetwork: string = 'main';
if(network === 'testnet'){
whatsonchainUrlNetwork = 'test';
}
let txUrl: string;
if(raw){
txUrl = `https://api.whatsonchain.com/v1/bsv/${whatsonchainUrlNetwork}/tx/${txid}/hex`;
} else {
txUrl = `https://api.whatsonchain.com/v1/bsv/${whatsonchainUrlNetwork}/tx/hash/${txid}`;
}
const response = await fetch(txUrl);
console.log('fetchTransactionByHash() response', response);
// Cache the fetched transaction in localStorage
let transactionData;
if(raw){
transactionData = await response.text();
localStorage.setItem(localStorageItemId, transactionData);
} else {
transactionData = await response.json();
localStorage.setItem(localStorageItemId, JSON.stringify(transactionData));
}
return transactionData;
};
export function isValidDate(date: Date) {
return !isNaN(date.getTime());
}
export function isDefined<T>(value: T | undefined | null): value is T {
return value !== undefined && value !== null;
}
export function matchWithPlaceholder(strWithPlaceholder: string, strToMatch: string) {
// Replace placeholders with regex that matches any characters
let placeholderRegex = strWithPlaceholder.replace(/<.*?>/g, '.*');
// Add '.*' to the end of the regex to allow for any characters at the end of strToMatch
placeholderRegex += '.*';
// console.log('matchWithPlaceholder() placeholderRegex', placeholderRegex);
// console.log('matchWithPlaceholder() strToMatch', strToMatch);
// Create a new RegExp with the placeholder regex
let regex = new RegExp(`^${placeholderRegex}$`);
// Test if the string matches the regex
const matchWithPlaceholder = regex.test(strToMatch);
// console.log('matchWithPlaceholder() matchWithPlaceholder', matchWithPlaceholder);
return matchWithPlaceholder;
}
import { createSlice } from '@reduxjs/toolkit';
// Define the initial state and its type
interface PortState {
value: chrome.runtime.Port | undefined;
network: string;
}
const initialState: PortState = {
value: undefined,
network: 'main',
};
// Create the slice
const portSlice = createSlice({
name: 'port',
initialState,
reducers: {
connect: (state) => {
state.value = chrome.runtime.connect(
process.env.REACT_APP_TAAL_EXTENSION_ID!,
{ name: process.env.REACT_APP_NAME! },
);
},
setNetwork: (state, action) => {
state.network = action.payload;
},
},
});
// Export the actions and reducer
export const { connect, setNetwork } = portSlice.actions;
export default portSlice.reducer;
import { configureStore } from '@reduxjs/toolkit';
import portReducer from './portSlice';
const store = configureStore({
reducer: {
port: portReducer,
},
});
// Define the RootState type
export type RootState = ReturnType<typeof store.getState>;
export default store;
import {
bsv,
Signer,
SignTransactionOptions,
SignatureRequest,
SignatureResponse,
Provider
} from 'scrypt-ts';
import { parseAddresses } from 'scrypt-ts/dist/bsv/utils';
import AddressOption = bsv.Address //Z:\Projects\VSCodium\auoz\node_modules\scrypt-ts\dist\bsv\types.d.ts
import { Transaction, PublicKey, Script } from 'bsv';
import { isTAALChromeWalletConnected, sendAction, fetchTransactionByHash, isDefined } from '../utils/globals';
interface InputInfo {
inputIndex: number;
satoshis: number;
scriptHex: string;
}
interface signPreimagePayload{
tx: string;
sighash: number;
script: string;
i: number;
satoshis: number;
}
interface inputSignature{
inputIndex: number;
sigtype: number;
publicKey: bsv.PublicKey;
signature: bsv.crypto.Signature;
}
const DEFAULT_SIGHASH_TYPE = bsv.crypto.Signature.ALL; //Z:\Projects\VSCodium\auoz\node_modules\scrypt-ts\dist\bsv\signers\sensilet-signer.js
export class TAALSigner extends Signer {
override async isAuthenticated(): Promise<boolean>{
const isTAALConnected: boolean = isTAALChromeWalletConnected();
return isTAALConnected;
}
override async requestAuth(): Promise<{
isAuthenticated: boolean;
error: string;
}> {
const isTAALConnected: boolean = isTAALChromeWalletConnected();
const errorString: string = 'TAAL chrome wallet is not connected!';
return {
isAuthenticated: isTAALConnected,
error: errorString
};
}
override async connect(provider: Provider): Promise<this> {
// we should make sure TAAL chrome wallet is connected before we connect a provider.
const isTAALConnected: boolean = isTAALChromeWalletConnected();
if(!isTAALConnected) {
Promise.reject(new Error('TAAL chrome wallet is not connected!'))
}
if(!provider.isConnected()) {
// connect the provider
await provider.connect();
}
this.provider = provider;
return this;
}
override async getDefaultAddress(): Promise<bsv.Address> {
const response = await sendAction('getAddress');
return bsv.Address.fromString(response.payload);
}
override async getDefaultPubKey(): Promise<PublicKey> {
// Real code start
const response = await sendAction('getRootPublicKey');
const rootPublicKey = new bsv.PublicKey(response.payload);
// Real code end
// Test code start
/* const address = await this.getDefaultAddress();
const rootPublicKey = await this.getPubKey(address); */
// Test code end
console.log("getDefaultPubKey().toString(): ", rootPublicKey.toString());
return Promise.resolve(rootPublicKey);
}
override async getPubKey(address: AddressOption): Promise<PublicKey> {
const response = await sendAction('getPublicKey');
const publicKey = new bsv.PublicKey(response.payload);
console.log("getPubKey().toString(): ", publicKey.toString());
return Promise.resolve(publicKey);
}
override async signRawTransaction(rawTxHex: string, options: SignTransactionOptions): Promise<string> {
// convert `rawTxHex` to a transation object
const sigReqsByInputIndex: Map<number, SignatureRequest> = (options?.sigRequests || []).reduce((m, sigReq) => { m.set(sigReq.inputIndex, sigReq); return m; }, new Map());
const tx = new bsv.Transaction(rawTxHex);
tx.inputs.forEach((_, inputIndex) => {
const sigReq = sigReqsByInputIndex.get(inputIndex);
if (!sigReq) {
throw new Error(`\`SignatureRequest\` info should be provided for the input ${inputIndex} to call #signRawTransaction`)
}
const script = sigReq.scriptHex ? new bsv.Script(sigReq.scriptHex) : bsv.Script.buildPublicKeyHashOut(sigReq.address.toString());
// set ref output of the input
tx.inputs[inputIndex].output = new bsv.Transaction.Output({
script,
satoshis: sigReq.satoshis
})
});
const signedTx = await this.signTransaction(tx, options);
return signedTx.toString();
}
public async getNetwork() {
const response = await sendAction('getNetwork');
const network = response.payload;
console.log('network:', network);
return network;
}
updateInputsWithInfo(tx: Transaction, inputInfos: InputInfo[]): Transaction {
tx.inputs.forEach((input, index) => {
// Find the corresponding inputInfo based on the inputIndex
const inputInfo = inputInfos.find((info) => info.inputIndex === index);
if (inputInfo) {
// Update the input properties using the inputInfo data
input.output = new bsv.Transaction.Output({
satoshis: inputInfo.satoshis,
script: Script.fromHex(inputInfo.scriptHex),
});
}
});
return tx;
}
override async signTransaction(tx: Transaction, options?: SignTransactionOptions): Promise<Transaction> {
// console.log("signTransaction() tx: ", tx);
// console.log("signTransaction() options: ", options);
const network = await this.getNetwork();
const address = await this.getDefaultAddress();
// console.log("signTransaction() address.toString(): ",address.toString());
// Generate default `sigRequests` if not passed by user
const sigRequests: SignatureRequest[] = options?.sigRequests?.length ? options.sigRequests :
tx.inputs.map((input, inputIndex) => {
const useAddressToSign = options && options.address ? options.address :
input.output?.script.isPublicKeyHashOut()
? input.output.script.toAddress(network)
: address;
// : this._address; //this._address = scryptlib_1.bsv.Address.fromString(addr); in getConnectedTarget()
/* console.log("signTransaction() input.output?.script.isPublicKeyHashOut(): ",input.output?.script.isPublicKeyHashOut());
console.log("signTransaction() input.output.script.toAddress(network).toString(): ",input.output?.script.toAddress(network).toString());
console.log("signTransaction() address.toString(): ",address.toString());
console.log("signTransaction() useAddressToSign.toString(): ",useAddressToSign.toString()); */
return {
prevTxId: input.prevTxId.toString(),
outputIndex: input.outputIndex,
inputIndex,
satoshis: input.output?.satoshis ?? 0, //satoshis: input.output?.satoshis,
address: useAddressToSign,
scriptHex: input.output?.script?.toHex() ?? '', //scriptHex: input.output?.script?.toHex(),
sigHashType: DEFAULT_SIGHASH_TYPE,
}
})
// console.log("signTransaction() sigRequests: ",sigRequests);
// Test code start
/* const rootPublicKey = await this.getDefaultPubKey();
const addressFromRootPublicKey = bsv.Address.fromPublicKey(rootPublicKey, network);
console.log("signTransaction() rootPublicKey address.toString(): ",addressFromRootPublicKey.toString());
const publicKey = await this.getPubKey(address);
const addressFromPublicKey = bsv.Address.fromPublicKey(publicKey, network);
console.log("signTransaction() publicKey address.toString(): ",addressFromPublicKey.toString());
const isPublicKeyCorrect = tx.inputs.some(input => {
const inputScript = input.output?.script;
if (inputScript?.isPublicKeyHashOut()) {
const inputAddress = inputScript.toAddress(network);
console.log('signTransaction() inputAddress.toString(): ', inputAddress.toString());
console.log('signTransaction() address.toString(): ', address.toString());
return inputAddress.toString() === address.toString();
}
return false;
});
console.log('signTransaction() isPublicKeyCorrect: ', isPublicKeyCorrect);
const privateKeyWIF: string = process.env.REACT_APP_TAAL_PRIVATE_KEY_WIF as string;
const privateKey = bsv.PrivateKey.fromWIF(privateKeyWIF);
const addressFromPrivateKeyWIF = privateKey.toAddress(network).toString();
console.log('signTransaction() addressFromPrivateKeyWIF:', addressFromPrivateKeyWIF); */
// Test code end
const rawTxHex = tx.toString();
const inputInfos = sigRequests.flatMap((sigReq) => {
const addresses = parseAddresses(sigReq.address, network);
return addresses.map(address => {
return {
txHex: rawTxHex,
inputIndex: sigReq.inputIndex,
scriptHex: sigReq.scriptHex || bsv.Script.buildPublicKeyHashOut(address).toHex(),
satoshis: sigReq.satoshis,
sigtype: sigReq.sigHashType || DEFAULT_SIGHASH_TYPE,
address: address.toString()
}
});
});
// console.log("signTransaction() inputInfos: ",inputInfos);
// Modify the transaction object using the inputInfos data
const updatedTx = this.updateInputsWithInfo(tx, inputInfos);
// console.log("signTransaction() updatedTx: ",updatedTx);
const response = await sendAction('signTx', updatedTx);
const signedTxHex = response.payload;
// console.log("signTransaction() signedTxHex: ",signedTxHex);
// Create a new Transaction object from the signed transaction hex
const signedTx = new bsv.Transaction(signedTxHex);
// console.log("signTransaction() signedTx: ",signedTx);
// Modify the transaction object using the inputInfos data
const updatedSignedTx = this.updateInputsWithInfo(signedTx, inputInfos);
console.log("signTransaction() updatedSignedTx.hash: ",updatedSignedTx.hash);
return updatedSignedTx;
}
async processPrevTxs(rawTxHex: string): Promise<Transaction> {
let rawTx = new bsv.Transaction(rawTxHex);
// Extract prevTxId for each input and fetch the transactions
const prevTxIds = rawTx.inputs.map(input => input.prevTxId.toString('hex'));
// Fetch all previous transactions using Promise.all
const prevTxs = await Promise.all(prevTxIds.map(txId => fetchTransactionByHash(txId, { callQueue: true })));
for (const [index, prevTx] of prevTxs.entries()) {
const outputIndex = rawTx.inputs[index].outputIndex;
const script = Script.fromBuffer(Buffer.from(prevTx.vout[outputIndex].scriptPubKey.hex, 'hex'));
// const scriptHex = script.toHex();
const satoshis: number = prevTx.vout[outputIndex].value * 100000000;
const newOutput = new Transaction.Output({ script: script, satoshis: satoshis });
rawTx.inputs[index].output = newOutput;
/* if (script.isPublicKeyHashOut()){
// t = new m.PublicKeyHash(e);
} else if (script.isScriptHashOut()) // && e.publicKeys && e.threshold)
{
// t = new m.MultiSigScriptHash(e,e.publicKeys,e.threshold,e.signatures);
} else if (!script.isPublicKeyOut()) {
const sigtype = bsv.crypto.Signature.SIGHASH_ALL; //Check
const preimage = rawTx.getPreimage(index);
console.log("processPrevTxs() preimage: ", preimage);
const signPreimagePayload: signPreimagePayload = {
tx: rawTxHex,
sighash: sigtype,
script: scriptHex,
i: index,
satoshis: satoshis
};
const signPreimageResponse = await sendAction('signPreimage', signPreimagePayload);
console.log("processPrevTxs() signPreimageResponse: ", signPreimageResponse);
} */
}
return rawTx;
}
hasCustomScript(rawTx: Transaction): boolean{
for (const [index, input] of rawTx.inputs.entries()) {
if (input.output?.script.isPublicKeyHashOut()){
// t = new m.PublicKeyHash(e);
} else if (input.output?.script.isScriptHashOut()) // && e.publicKeys && e.threshold)
{
// t = new m.MultiSigScriptHash(e,e.publicKeys,e.threshold,e.signatures);
} else if (!input.output?.script.isPublicKeyOut()) {
return true;
}
}
return false;
}
async signCustomInput(inputTx: Transaction, inputIndex: number): Promise<inputSignature> {
const rawTxHex = inputTx.toString();
const sigtype = bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID;
// const outputIndex = inputTx.inputs[inputIndex].outputIndex;
const script = inputTx?.inputs[inputIndex]?.output?.script; //Script.fromBuffer(Buffer.from(prevTx.vout[outputIndex].scriptPubKey.hex, 'hex'));
const scriptHex: string = (script ?? new bsv.Script('')).toHex();
const satoshis: number = inputTx?.inputs[inputIndex]?.output?.satoshis ?? 0;
const rootPublicKey = await this.getDefaultPubKey();
const preimage = inputTx.getPreimage(inputIndex);
console.log("signCustomInput() preimage: ", preimage);
const signPreimagePayload: signPreimagePayload = {
tx: rawTxHex,
sighash: sigtype,
script: scriptHex,
i: inputIndex,
satoshis: satoshis
};
const signPreimageResponse = await sendAction('signPreimage', signPreimagePayload);
console.log("signCustomInput() signPreimageResponse: ", signPreimageResponse);
const signatureHex = signPreimageResponse.payload;
const signature = bsv.crypto.Signature.fromTxFormat(Buffer.from(signatureHex, 'hex'));
return {
inputIndex: inputIndex,
sigtype: sigtype,
publicKey: rootPublicKey,
signature: signature
}
}
async signStandardInput(inputTx: Transaction, inputIndex: number): Promise<inputSignature> {
const sigtype = bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID;
const rootPublicKey = await this.getDefaultPubKey();
const signedTx = await this.signTransaction(inputTx);
const script = signedTx.inputs[inputIndex].script;
const signature = script.chunks[0].buf;
console.log("signStandardInput() signature: ", signature);
const sig = signedTx.getSignature(inputIndex) as string;
console.log("signStandardInput() sig: ", sig);
// const signatureHex = signature.toString('hex');
return {
inputIndex: inputIndex,
sigtype: sigtype,
publicKey: rootPublicKey,
signature: bsv.crypto.Signature.fromTxFormat(signature),
}
}
// For test purposes only
// Sign a transaction with inputs, one of which is a custom output script (Scrypt contract) and other are standard scripts (P2PKH or P2SH)
async signMixedTransaction(rawTx: Transaction): Promise<Transaction> {
// Filter the inputs for the custom and standard input transactions
let customInput;
let customInputIndex: number = -1;
const address = await this.getDefaultAddress();
for (const [index, input] of rawTx.inputs.entries()) {
if (input.output?.script.isPublicKeyHashOut()){
} else if (input.output?.script.isScriptHashOut()) // && e.publicKeys && e.threshold)
{
} else if (!input.output?.script.isPublicKeyOut()) {
customInput = rawTx.inputs[index];
customInputIndex = index;
}
}
const standardInputs = rawTx.inputs.filter((_, index) => index !== customInputIndex);
// Convert custom input to a UTXO object
if (
isDefined(customInput) &&
isDefined(customInput.prevTxId) &&
isDefined(customInput.outputIndex) &&
isDefined(customInput.script) &&
isDefined(customInput.output) &&
isDefined(customInput.output.script) &&
isDefined(customInput.output.satoshis)
) {
const customInputUtxo: Transaction.IUnspentOutput = {
txId: customInput.prevTxId.toString('hex'),
outputIndex: customInput.outputIndex,
address: address.toString(), //customInput.script.toAddress().toString(),
script: customInput.output.script.toHex(),
satoshis: customInput.output.satoshis,
};
// Create a transaction with the custom input
const customInputTx = new bsv.Transaction().from([customInputUtxo]);
// Sign the custom input using the function that signs the preimage of an individual input
const customInputSignature: inputSignature = await this.signCustomInput(customInputTx, customInputIndex);
// Apply the custom input signature to the original transaction
rawTx.applySignature(customInputSignature);
}
// Sign each standard input separately and apply the signature to the original transaction
for (const [index, standardInput] of standardInputs.entries()) {
if (
isDefined(standardInput.output) &&
isDefined(standardInput.output.script) &&
isDefined(standardInput.output.satoshis)
) {
const standardInputUtxo = {
txId: standardInput.prevTxId.toString('hex'),
outputIndex: standardInput.outputIndex,
address: address.toString(), //standardInput.script.toAddress().toString(),
script: standardInput.output.script.toHex(),
satoshis: standardInput.output.satoshis,
};
const standardInputTx = new bsv.Transaction().from([standardInputUtxo]);
const standardInputSignature = await this.signStandardInput(standardInputTx, index);
standardInputSignature.inputIndex = 1 + index;
rawTx.applySignature(standardInputSignature);
}
}
return rawTx;
}
// Sign a transaction with inputs, one of which is a custom output script (Scrypt contract) and other are standard scripts (P2PKH or P2SH)
async signMixedTransaction2(rawTx: Transaction): Promise<Transaction> {
// const address = await this.getDefaultAddress();
// Sign each standard input separately and apply the signature to the original transaction
for (const [index, input] of rawTx.inputs.entries()) {
/* const inputUtxo = {
txId: input.prevTxId.toString('hex'),
outputIndex: input.outputIndex,
address: address.toString(),
script: input?.output?.script.toHex() ?? '',
satoshis: input?.output?.satoshis ?? 0,
};
// Create a transaction with the custom input
const inputTx = new bsv.Transaction().from([inputUtxo]); */
// Sign the custom input using the function that signs the preimage of an individual input
const inputSignature: inputSignature = await this.signCustomInput(rawTx, index);
// Apply the custom input signature to the original transaction
// rawTx.applySignature(inputSignature);
// Apply the signature to the input
const sigScript = bsv.Script.empty()
.add(inputSignature.signature.toTxFormat())
.add(Buffer.from((inputSignature.sigtype).toString(16), 'hex'))
.add(inputSignature.publicKey.toBuffer());
input.setScript(sigScript);
console.log(`signMixedTransaction2() rawTx.inputs[${index}].script.chunks[0].buf`, rawTx.inputs[index].script.chunks[0].buf.toJSON());
console.log(`signMixedTransaction2() rawTx.inputs[${index}].script.chunks[1].buf`, rawTx.inputs[index].script.chunks[1].buf.toJSON());
}
return rawTx;
}
// For test purposes only, sign with private key
async signMixedTransaction3(rawTx: Transaction): Promise<Transaction> {
// const address = await this.getDefaultAddress();
const privateKeyWIF: string = process.env.REACT_APP_TAAL_PRIVATE_KEY_WIF as string;
const privateKey = bsv.PrivateKey.fromWIF(privateKeyWIF);
// const addressFromPrivateKeyWIF = privateKey.toAddress(network).toString();
const sigtype = bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID;
// const rootPublicKey = await this.getDefaultPubKey();
// Sign each standard input separately and apply the signature to the original transaction
for (const [index, input] of rawTx.inputs.entries()) {
if(input.output){
const satoshis = bsv.crypto.BN.fromNumber(input.output.satoshis);
const signature = bsv.Transaction.Sighash.sign(rawTx, privateKey, sigtype, index, input.output.script, satoshis);
console.log(`signMixedTransaction3() signature${index}`, signature.toString());
const publicKey = bsv.PublicKey.fromPrivateKey(privateKey); //rootPublicKey
console.log(`signMixedTransaction3() publicKey${index}`, publicKey.toString());
// Apply the signature to the input
const sigScript = bsv.Script.empty()
.add(signature.toTxFormat())
.add(Buffer.from((sigtype).toString(16), 'hex'))
.add(publicKey.toBuffer());
input.setScript(sigScript);
}
// console.log(`signMixedTransaction2() rawTx.inputs[${index}].script.chunks[0].buf`, rawTx.inputs[index].script.chunks[0].buf.toJSON());
// console.log(`signMixedTransaction2() rawTx.inputs[${index}].script.chunks[1].buf`, rawTx.inputs[index].script.chunks[1].buf.toJSON());
}
// Check if transaction is fully signed
if (rawTx.verify()) {
return rawTx;
} else {
throw new Error(`Transcation signing fail`);
}
}
/**
* Get signatures with api
* @param rawTxHex a transation raw hex
* @param sigRequests a `SignatureRequest` array for the some inputs of the transaction.
* @returns a `SignatureResponse` array
*/
async getSignatures(rawTxHex: string, sigRequests: SignatureRequest[]): Promise<SignatureResponse[]> {
const rawTx = await this.processPrevTxs(rawTxHex);
console.log("getSignatures() rawTx: ", rawTx);
const hasCustomScript = this.hasCustomScript(rawTx);
let signedTx: Transaction;
if(hasCustomScript){
signedTx = await this.signMixedTransaction2(rawTx);
} else {
signedTx = await this.signTransaction(rawTx);
}
// const rootPublicKey = await this.getDefaultPubKey();
const address = await this.getDefaultAddress();
const publicKey = await this.getPubKey(address);
const sigResponses = signedTx.inputs.map((input, idx) => {
const script = input.script;
const signature = script.chunks[0].buf;
// const sig = signedTx.getSignature(idx) as string;
const signatureHex = signature.toString('hex');
// const publicKeyHex = script.chunks[1].buf.toString('hex');
const publicKeyHex = publicKey.toString();
const sigHashType = signature.readUInt8(signature.length - 1);
return {
inputIndex: idx,
sig: signatureHex,
publicKey: publicKeyHex,
sigHashType: sigHashType || DEFAULT_SIGHASH_TYPE
}
})
console.log("getSignatures() sigResponses: ", sigResponses);
return sigResponses;
}
override async signMessage(message: string, address?: AddressOption): Promise<string> {
if (address) {
throw new Error(`${this.constructor.name}#signMessge with \`address\` param is not supported!`);
}
const response = await sendAction('signMessage', message);
return response.payload; //TODO
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment