diff --git a/src/lib/account.ts b/src/lib/account.ts index a5a7323..ad5c2d0 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -230,10 +230,12 @@ export async function getSigner(chain: Chain = 'mvc', treehash?: string) { const mnemonic = await getCurrentAccount().then((account) => account!.mnemonic) const path = await getAccountProperty(chain, 'path') const node = deriveBtcPrivateKey(mnemonic, path, network) - let nodeXOnlyPubkey = node.publicKey.subarray(1) + const nodeXOnlyPubkey = node.publicKey.subarray(1) if (treehash) { - nodeXOnlyPubkey = Buffer.concat([nodeXOnlyPubkey, Buffer.from(treehash)]) + const withTreehash = Buffer.concat([nodeXOnlyPubkey, Buffer.from(treehash, 'hex')]) + return node.tweak(crypto.taggedHash('TapTweak', withTreehash)) } + return node.tweak(crypto.taggedHash('TapTweak', nodeXOnlyPubkey)) } const privateKey = await getPrivateKey(chain) diff --git a/src/lib/actions/btc/sign-psbt.ts b/src/lib/actions/btc/sign-psbt.ts index 0eea056..02dade1 100644 --- a/src/lib/actions/btc/sign-psbt.ts +++ b/src/lib/actions/btc/sign-psbt.ts @@ -17,9 +17,9 @@ export interface ToSignInput { } interface BaseUserToSignInput { - index: number; - sighashTypes: number[] | undefined; - disableTweakSigner?: boolean; + index: number + sighashTypes: number[] | undefined + disableTweakSigner?: boolean } export interface AddressUserToSignInput extends BaseUserToSignInput { @@ -37,16 +37,20 @@ export interface SignPsbtOptions { toSignInputs?: UserToSignInput[] } -export async function process({ psbtHex, options }: { - psbtHex: string, +export async function process({ + psbtHex, + options, +}: { + psbtHex: string options?: { toSignInputs?: ToSignInput[]; autoFinalized: boolean } }): Promise { const psbtNetwork = await getBtcNetwork() const addressType = await getAddressType('btc') + const pubkey = await getPublicKey('btc') - if (!options) [ + if (!options) { options = { toSignInputs: undefined, autoFinalized: true } - ] + } if (!options?.toSignInputs) { // Compatibility with legacy code. options.toSignInputs = await formatOptionsToSignInputs(psbtHex) @@ -56,26 +60,31 @@ export async function process({ psbtHex, options }: { const psbt = Psbt.fromHex(psbtHex, { network: psbtNetwork }) psbt.data.inputs.forEach((v, index) => { - const isNotSigned = !(v.finalScriptSig || v.finalScriptWitness); + const isNotSigned = !(v.finalScriptSig || v.finalScriptWitness) const isP2TR = addressType === 'P2TR' - const lostInternalPubkey = !v.tapInternalKey; + const lostInternalPubkey = !v.tapInternalKey // Special measures taken for compatibility with certain applications. if (isNotSigned && isP2TR && lostInternalPubkey) { - const tapInternalKey = toXOnly(Buffer.from(options!.toSignInputs![index].publicKey, 'hex')); + const tapInternalKey = toXOnly(Buffer.from(pubkey, 'hex')) const { output } = payments.p2tr({ internalPubkey: tapInternalKey, - network: psbtNetwork - }); + network: psbtNetwork, + }) if (v.witnessUtxo?.script.toString('hex') == output?.toString('hex')) { - v.tapInternalKey = tapInternalKey; + v.tapInternalKey = tapInternalKey } } }) - options.toSignInputs.forEach(async (v, index) => { - const keyPair = await getSigner('btc', options!.toSignInputs![index].treehash) + // options.toSignInputs.forEach(async (v, index) => { + // const keyPair = await getSigner('btc', options!.toSignInputs![index].treehash) + // psbt.signInput(v.index, keyPair, v.sighashTypes) + // }) + for (let i = 0; i < options.toSignInputs.length; i++) { + const keyPair = await getSigner('btc', options!.toSignInputs![i].treehash) + const v = options.toSignInputs[i] psbt.signInput(v.index, keyPair, v.sighashTypes) - }) + } if (options.autoFinalized) { options.toSignInputs.forEach((v) => { @@ -91,72 +100,69 @@ const formatOptionsToSignInputs = async (_psbt: string | Psbt, options?: SignPsb const btcAddress = await getAddress('btc') const account = { pubkey, address: btcAddress } - let toSignInputs: ToSignInput[] = []; + let toSignInputs: ToSignInput[] = [] if (options && options.toSignInputs) { // We expect userToSignInputs objects to be similar to ToSignInput interface, // but we allow address to be specified in addition to publicKey for convenience. toSignInputs = options.toSignInputs.map((input) => { - const index = Number(input.index); - if (isNaN(index)) throw new Error('invalid index in toSignInput'); + const index = Number(input.index) + if (isNaN(index)) throw new Error('invalid index in toSignInput') if (!(input as AddressUserToSignInput).address && !(input as PublicKeyUserToSignInput).publicKey) { - throw new Error('no address or public key in toSignInput'); + throw new Error('no address or public key in toSignInput') } if ((input as AddressUserToSignInput).address && (input as AddressUserToSignInput).address != account.address) { - throw new Error('invalid address in toSignInput'); + throw new Error('invalid address in toSignInput') } if ( (input as PublicKeyUserToSignInput).publicKey && (input as PublicKeyUserToSignInput).publicKey != account.pubkey ) { - throw new Error('invalid public key in toSignInput'); + throw new Error('invalid public key in toSignInput') } - const sighashTypes = input.sighashTypes?.map(Number); + const sighashTypes = input.sighashTypes?.map(Number) - if (sighashTypes?.some(isNaN)) throw new Error('invalid sighash type in toSignInput'); + if (sighashTypes?.some(isNaN)) throw new Error('invalid sighash type in toSignInput') return { index, publicKey: account.pubkey, sighashTypes, - disableTweakSigner: input.disableTweakSigner - }; - }); + disableTweakSigner: input.disableTweakSigner, + } + }) } else { const networkType = await getNetwork() const psbtNetwork = networkType === 'mainnet' ? networks.bitcoin : networks.testnet - const psbt = - typeof _psbt === 'string' - ? Psbt.fromHex(_psbt as string, { network: psbtNetwork }) - : (_psbt as Psbt); + const psbt = typeof _psbt === 'string' ? Psbt.fromHex(_psbt as string, { network: psbtNetwork }) : (_psbt as Psbt) psbt.data.inputs.forEach((v, index) => { - let script: any = null; - let value = 0; + let script: any = null + let value = 0 if (v.witnessUtxo) { - script = v.witnessUtxo.script; - value = v.witnessUtxo.value; + script = v.witnessUtxo.script + value = v.witnessUtxo.value } else if (v.nonWitnessUtxo) { - const tx = Transaction.fromBuffer(v.nonWitnessUtxo); - const output = tx.outs[psbt.txInputs[index].index]; - script = output.script; - value = output.value; + const tx = Transaction.fromBuffer(v.nonWitnessUtxo) + const output = tx.outs[psbt.txInputs[index].index] + script = output.script + value = output.value } - const isSigned = v.finalScriptSig || v.finalScriptWitness; + const isSigned = v.finalScriptSig || v.finalScriptWitness if (script && !isSigned) { - const address = btcjs.address.fromOutputScript(script, psbtNetwork); + const address = btcjs.address.fromOutputScript(script, psbtNetwork) if (account.address === address) { toSignInputs.push({ index, publicKey: account.pubkey, - sighashTypes: v.sighashType ? [v.sighashType] : undefined - }); + sighashTypes: v.sighashType ? [v.sighashType] : undefined, + }) } } - }); + }) } - return toSignInputs; -}; + return toSignInputs +}