From cd96125023aa01dcb3449eddd55e1debd91cc2a6 Mon Sep 17 00:00:00 2001 From: Christopher Fong Date: Mon, 19 May 2025 13:36:54 -0400 Subject: [PATCH 1/4] chore: wip paygo util functions TICKET: BTC-2047 chore: wip adding tests for paygo address proof util functions TICKET: BTC-2047 chore: added testing and functions to get proprietary key vals from output TICKET: BTC-2047 chore: wip added functions and tests TICKET: BTC-2047 --- modules/utxo-lib/src/bitgo/UtxoPsbt.ts | 73 ++++++++++++- .../src/bitgo/psbt/paygoAddressProof.ts | 97 +++++++++++++++++ modules/utxo-lib/src/testutil/psbt.ts | 23 ++++ .../test/bitgo/psbt/paygoAddressProof.ts | 100 ++++++++++++++++++ 4 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 modules/utxo-lib/src/bitgo/psbt/paygoAddressProof.ts create mode 100644 modules/utxo-lib/test/bitgo/psbt/paygoAddressProof.ts diff --git a/modules/utxo-lib/src/bitgo/UtxoPsbt.ts b/modules/utxo-lib/src/bitgo/UtxoPsbt.ts index 678bc17b2d..ae45d10dfc 100644 --- a/modules/utxo-lib/src/bitgo/UtxoPsbt.ts +++ b/modules/utxo-lib/src/bitgo/UtxoPsbt.ts @@ -7,7 +7,7 @@ import { Transaction as ITransaction, TransactionFromBuffer, } from 'bip174/src/lib/interfaces'; -import { checkForInput } from 'bip174/src/lib/utils'; +import { checkForInput, checkForOutput } from 'bip174/src/lib/utils'; import { BufferWriter, varuint } from 'bitcoinjs-lib/src/bufferutils'; import { SessionKey } from '@brandonblack/musig'; import { BIP32Factory, BIP32Interface } from 'bip32'; @@ -57,6 +57,8 @@ import { getTaprootOutputKey } from '../taproot'; import { getPsbtInputProprietaryKeyVals, getPsbtInputSignatureCount, + getPsbtOutputProprietaryKeyVals, + ProprietaryKeySearch, ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER, } from './PsbtUtil'; @@ -1152,6 +1154,75 @@ export class UtxoPsbt = UtxoTransaction ukv.key.equals(key)); + if (ukvIndex > -1) { + output.unknownKeyVals[ukvIndex] = { key, value }; + return this; + } + } + this.addUnknownKeyValToOutput(outputIndex, { + key, + value, + }); + return this; + } + + /** + * To delete any data from proprietary key value in PSBT output. + * Default identifierEncoding is utf-8 for identifier. + */ + deleteProprietaryKeyValsInOutput(outputIndex: number, keysToDelete?: ProprietaryKeySearch): this { + const output = checkForOutput(this.data.outputs, outputIndex); + if (!output.unknownKeyVals?.length) { + return this; + } + if (keysToDelete && keysToDelete.subtype === undefined && Buffer.isBuffer(keysToDelete.keydata)) { + throw new Error('invalid proprietary key search filter combination. subtype is required'); + } + output.unknownKeyVals = output.unknownKeyVals.filter((keyValue, i) => { + const key = decodeProprietaryKey(keyValue.key); + return !( + keysToDelete === undefined || + (keysToDelete.identifier === key.identifier && + (keysToDelete.subtype === undefined || + (keysToDelete.subtype === key.subtype && + (!Buffer.isBuffer(keysToDelete.keydata) || keysToDelete.keydata.equals(key.keydata))))) + ); + }); + return this; + } + private createMusig2NonceForInput( inputIndex: number, keyPair: BIP32Interface, diff --git a/modules/utxo-lib/src/bitgo/psbt/paygoAddressProof.ts b/modules/utxo-lib/src/bitgo/psbt/paygoAddressProof.ts new file mode 100644 index 0000000000..7e11fc13a2 --- /dev/null +++ b/modules/utxo-lib/src/bitgo/psbt/paygoAddressProof.ts @@ -0,0 +1,97 @@ +import * as assert from 'assert'; +import * as bitcoinMessage from 'bitcoinjs-message'; +import { crypto } from 'bitcoinjs-lib'; + +import { address } from '../..'; +import { networks } from '../../networks'; +import { toBase58Check } from '../../address'; +import { getPsbtOutputProprietaryKeyVals, ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER } from '../PsbtUtil'; +import { UtxoPsbt } from '../UtxoPsbt'; + +/** The function consumes the signature as a parameter and adds the PayGo address to the + * PSBT output at the output index where the signature is of the format: + * 0x18Bitcoin Signed Message:\n
signed by + * the HSM beforehand. + * + * @param psbt - PSBT that we need to encode our paygo address into + * @param outputIndex - the index of the address in our output + * @param sig - the signature that we want to encode + */ +export function addPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, sig: Buffer, pub: Buffer): void { + psbt.addProprietaryKeyValToOutput(outputIndex, { + key: { + identifier: PSBT_PROPRIETARY_IDENTIFIER, + subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, + keydata: Buffer.from(pub) + }, + value: sig, + }); +} + +/** Verify the paygo address signature is valid using BitGoJs statics + * + * @param psbt - PSBT we want to verify that the paygo address is in + * @param outputIndex - we have the output index that address is in + * @param pub - The public key that we want to verify the proof with + * @returns + */ +export function verifyPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, pub: Buffer): void { + const stored = psbt.getOutputProprietaryKeyVals(outputIndex, { + identifier: PSBT_PROPRIETARY_IDENTIFIER, + subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, + }); + if (!stored) { + throw new Error('No address proof'); + } + + // assert stored length is 0 or 1 + if (stored.length === 0) { + throw new Error(`There is no paygo address proof encoded in the PSBT at output ${outputIndex}.`); + } else if (stored.length > 1) { + throw new Error('There are multiple paygo address proofs encoded in the PSBT. Something went wrong.'); + } + + const signature = stored[0].value; + // It doesn't matter that this is bitcoin or not, we just need to convert the public key buffer into an address format + // for the verification + const messageToVerify = toBase58Check(crypto.hash160(pub), networks.bitcoin.pubKeyHash, networks.bitcoin); + + // TODO: need to figure out what the message is in this context + // Are we verifying the address of the PayGo? we can call getAddressFromScript given output index. + if (!bitcoinMessage.verify(message, messageToVerify, signature)) { + throw new Error('Cannot verify the paygo address signature with the provided pubkey.'); + } + + const out = psbt.txOutputs[outputIndex]; + assert(out); + const addressFromOutput = address.fromOutputScript(out.script, psbt.network); + const addressFromProof = extractAddressFromPayGoAttestationProof(message, addressFromOutput.length); + + if (addressFromProof !== addressFromOutput) { + throw new Error(`The address from the output (${addressFromOutput}) does not match the address that is in the proof (${addressFromProof}).`) + } +} + +/** Get the output index of the paygo output if there is one. It does this by + * checking if the metadata is on one of the outputs of the PSBT. If there is + * no paygo output, return undefined + * + * @param psbt + * @returns number - the index of the output address + */ +export function getPaygoAddressProofOutputIndex(psbt: UtxoPsbt): number | undefined { + const res = psbt.data.outputs.flatMap((output, outputIndex) => { + const proprietaryKeyVals = getPsbtOutputProprietaryKeyVals(output, { + identifier: PSBT_PROPRIETARY_IDENTIFIER, + subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, + }); + + return proprietaryKeyVals.length === 0 ? [] : [outputIndex]; + }); + + return res.length === 0 ? undefined : res[0]; +} + +export function psbtOutputIncludesPaygoAddressProof(psbt: UtxoPsbt): boolean { + return getPaygoAddressProofOutputIndex(psbt) !== undefined; +} diff --git a/modules/utxo-lib/src/testutil/psbt.ts b/modules/utxo-lib/src/testutil/psbt.ts index 7e849763f9..3848ac424e 100644 --- a/modules/utxo-lib/src/testutil/psbt.ts +++ b/modules/utxo-lib/src/testutil/psbt.ts @@ -261,3 +261,26 @@ export function verifyFullySignedSignatures( } }); } + +/** We generate the PayGo attestation proof based on the private key, UUID, and address of our PayGo. + * We create a random entropy of 64 bytes encoded to base58 and used to create the attestation proof + * in the format 0x18Bitcoin Signed Message:\n
+ * + * @param attestationPrvKey + * @param uuid + * @param address + */ +export function generatePayGoAttestationProof(attestationPrvKey: Buffer, uuid: string, address: Buffer): Buffer { + // This is our prefix to our bitcoin signed message + const prefixByte = Buffer.from([0x18]); + const signedMessagePrefix = Buffer.from('Bitcoin Signed Message:\n', 'utf8'); + // We always create a 32 byte buffer array but we can implement a random + // function to generate between two ranges of our length of entropy + const entropy = Buffer.allocUnsafe(32); + crypto.getRandomValues(entropy); + const uuidToBuffer = Buffer.from(uuid, 'hex'); + const signedMessageBufferRaw = Buffer.concat([prefixByte, signedMessagePrefix, entropy, address, uuidToBuffer]); + const varInt = Buffer.from([signedMessageBufferRaw.length]); + const fullResMessageBuffer = Buffer.concat([prefixByte, signedMessagePrefix, varInt, entropy, address, uuidToBuffer])l + +} \ No newline at end of file diff --git a/modules/utxo-lib/test/bitgo/psbt/paygoAddressProof.ts b/modules/utxo-lib/test/bitgo/psbt/paygoAddressProof.ts new file mode 100644 index 0000000000..ba1bb1d311 --- /dev/null +++ b/modules/utxo-lib/test/bitgo/psbt/paygoAddressProof.ts @@ -0,0 +1,100 @@ +import * as assert from 'assert' +import { decodeProprietaryKey } from 'bip174/src/lib/proprietaryKeyVal'; +import { KeyValue } from 'bip174/src/lib/interfaces'; +import { checkForOutput } from 'bip174/src/lib/utils'; + +import { bip32, networks, testutil } from '../../../src' +import { addPaygoAddressProof, verifyPaygoAddressProof, getPaygoAddressProofOutputIndex, psbtOutputIncludesPaygoAddressProof } from "../../../src/bitgo/psbt/paygoAddressProof"; +import { generatePayGoAttestationProof, inputScriptTypes, outputScriptTypes } from '../../../src/testutil'; +import { ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER, RootWalletKeys } from '../../../src/bitgo'; + + +const network = networks.bitcoin; +const keys = [1,2,3].map((v) => bip32.fromSeed(Buffer.alloc(16, `test/2/${v}`), network)) +const rootWalletKeys = new RootWalletKeys([keys[0], keys[1], keys[2]]) +// const dummyKey1 = rootWalletKeys.deriveForChainAndIndex(50, 200); +const dummyKey2 = rootWalletKeys.deriveForChainAndIndex(60, 201); + +const psbtInputs = inputScriptTypes.map((scriptType) => ({scriptType, value: BigInt(1000)})) +const psbtOutputs = outputScriptTypes.map((scriptType) => ({ scriptType, value: BigInt(900)})) +// const dummy1PubKey = dummyKey1.user.publicKey; +// This generatePayGoAttestationProof function should be returning the bitcoin signed message +const sig2 = dummyKey2.user.privateKey!; + +// wallet pub and priv key for tbtc +const attestationPubKey = "xpub661MyMwAqRbcFU2Qx7pvGmmiQpVj8NcR7dSVpgqNChMkQyobpVWWERcrTb47WicmXwkhAY2VrC3hb29s18FDQWJf5pLm3saN6uLXAXpw1GV"; +const attestationPrvKey = "red"; +const nilUUID = '00000000-0000-0000-0000-000000000000'; +const addressProofBuffer = generatePayGoAttestationProof(Buffer.from(attestationPrvKey), nilUUID, Buffer.from(address)) + + +function getTestPsbt() { + return testutil.constructPsbt( + psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned' + ) +} + +describe('addPaygoAddressProof and verifyPaygoAddressProof', () => { + function getPaygoProprietaryKey(proprietaryKeyVals: KeyValue[]) { + return proprietaryKeyVals.map(({key, value}) => { + return { key: decodeProprietaryKey(key), value }; + }).filter((keyValue) => { + return keyValue.key.identifier === PSBT_PROPRIETARY_IDENTIFIER && keyValue.key.subtype === ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF + }); + } + + it("should fail a proof verification if the proof isn't valid", () => { + const outputIndex = 0; + const psbt = getTestPsbt(); + addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey)); + const output = checkForOutput(psbt.data.outputs, outputIndex); + const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!); + assert(proofInPsbt.length === 1) + assert.throws(() => verifyPaygoAddressProof(psbt, 0, dummyKey2.user.publicKey), (e: any) => e.message === 'Cannot verify the paygo address signature with the provided pubkey.'); + }); + + it("should add and verify a valid paygo address proof on the PSBT", () => { + const outputIndex = 0; + const psbt = getTestPsbt(); + addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey)); + // should verify function return a boolean? that way we can assert + // if this is verified, throws an error otherwise or false + error msg as an object + verifyPaygoAddressProof(psbt, outputIndex, Buffer.from(attestationPubKey)); + }); + + it("should throw an error if there are multiple PayGo proprietary keys in the PSBT", () => { + const outputIndex = 0; + const psbt = getTestPsbt(); + addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey)); + addPaygoAddressProof(psbt, outputIndex, Buffer.from(sig2), Buffer.from(attestationPubKey)); + const output = checkForOutput(psbt.data.outputs, outputIndex); + const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!); + assert(proofInPsbt.length !== 0) + assert(proofInPsbt.length <= 1) + assert.throws(() => verifyPaygoAddressProof(psbt, outputIndex, Buffer.from(attestationPubKey)), (e: any) => e.message === 'There are multiple paygo address proofs encoded in the PSBT. Something went wrong.'); + }); +}); + + +describe('verifyPaygoAddressProof', () => { + it('should throw an error if there is no PayGo address in PSBT', () => { + const psbt = getTestPsbt(); + assert.throws(() => verifyPaygoAddressProof(psbt, 0, Buffer.from(attestationPubKey)), (e: any) => e.message === 'here is no paygo address proof encoded in the PSBT.'); + }); +}); + +describe('getPaygoAddressProofIndex', () => { + it('should get PayGo address proof index from PSBT if there is one', () => { + const psbt = getTestPsbt(); + const outputIndex = 0; + addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey)); + assert(psbtOutputIncludesPaygoAddressProof(psbt)); + assert(getPaygoAddressProofOutputIndex(psbt) === 0) + }); + + it("should return undefined if there is no PayGo address proof in PSBT", () => { + const psbt = getTestPsbt(); + assert(getPaygoAddressProofOutputIndex(psbt) === undefined) + assert(!psbtOutputIncludesPaygoAddressProof(psbt)) + }); +}); \ No newline at end of file From 636f8a9b546a7d3fb083fb0bd77c8d2d4a398861 Mon Sep 17 00:00:00 2001 From: Christopher Fong Date: Tue, 3 Jun 2025 10:46:48 -0400 Subject: [PATCH 2/4] chore: added test for paygo addr and moved generate paygo proof to other pr TICKET: BTC-2047 chore: determined msg to sign and verify Issue: BTC-2047 TICKET: BTC-2047 chore(utxo-lib): modified verify function and tests Issue: BTC-2047 TICKET: BTC-2047 chore(utxo-core): moved util functions to uxto-core TICKET: BTC-2047 chore: got tests to work BTC-2047 TICKET: BTC-2047 feat(utxo-core): cleaned up comments TICKET: BTC-2047 chore(utxo-core): added bitcoinjs-message to deps and fixed import TICKET: BTC-2047 chore(utxo-core): used new helper functions for prop kv func and removed old TICKET: BTC-2047 --- modules/utxo-core/src/paygo/index.ts | 1 + .../utxo-core/src/paygo/psbt/PayGoUtils.ts | 125 +++++++++++++++ modules/utxo-core/src/paygo/psbt/index.ts | 1 + .../utxo-core/test/paygo/psbt/PayGoUtils.ts | 142 ++++++++++++++++++ modules/utxo-lib/src/bitgo/PsbtUtil.ts | 1 + modules/utxo-lib/src/bitgo/UtxoPsbt.ts | 2 +- .../src/bitgo/psbt/paygoAddressProof.ts | 97 ------------ modules/utxo-lib/src/bitgo/zcash/address.ts | 4 +- modules/utxo-lib/src/testutil/psbt.ts | 23 --- .../test/bitgo/psbt/paygoAddressProof.ts | 100 ------------ 10 files changed, 273 insertions(+), 223 deletions(-) create mode 100644 modules/utxo-core/src/paygo/psbt/PayGoUtils.ts create mode 100644 modules/utxo-core/src/paygo/psbt/index.ts create mode 100644 modules/utxo-core/test/paygo/psbt/PayGoUtils.ts delete mode 100644 modules/utxo-lib/src/bitgo/psbt/paygoAddressProof.ts delete mode 100644 modules/utxo-lib/test/bitgo/psbt/paygoAddressProof.ts diff --git a/modules/utxo-core/src/paygo/index.ts b/modules/utxo-core/src/paygo/index.ts index 2dd3b9d8b9..1b130416cf 100644 --- a/modules/utxo-core/src/paygo/index.ts +++ b/modules/utxo-core/src/paygo/index.ts @@ -1 +1,2 @@ export * from './ExtractAddressPayGoAttestation'; +export * from './psbt'; diff --git a/modules/utxo-core/src/paygo/psbt/PayGoUtils.ts b/modules/utxo-core/src/paygo/psbt/PayGoUtils.ts new file mode 100644 index 0000000000..f200beee90 --- /dev/null +++ b/modules/utxo-core/src/paygo/psbt/PayGoUtils.ts @@ -0,0 +1,125 @@ +import * as utxolib from '@bitgo/utxo-lib'; +import * as bitcoinMessage from 'bitcoinjs-message'; +import { checkForOutput } from 'bip174/src/lib/utils'; + +import { extractAddressBufferFromPayGoAttestationProof } from '../ExtractAddressPayGoAttestation'; + +/** The function consumes the signature as a parameter and adds the PayGo address to the + * PSBT output at the output index where the signature is of the format: + * 0x18Bitcoin Signed Message:\n
signed by + * the HSM beforehand. + * + * @param psbt - PSBT that we need to encode our paygo address into + * @param outputIndex - the index of the address in our output + * @param sig - the signature that we want to encode + */ +export function addPaygoAddressProof( + psbt: utxolib.bitgo.UtxoPsbt, + outputIndex: number, + sig: Buffer, + pub: Buffer +): void { + utxolib.bitgo.addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'output', outputIndex, { + key: { + identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER, + subtype: utxolib.bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, + keydata: pub, + }, + value: sig, + }); +} + +/** Verify the paygo address signature is valid using BitGoJs statics + * + * @param psbt - PSBT we want to verify that the paygo address is in + * @param outputIndex - we have the output index that address is in + * @param pub - The public key that we want to verify the proof with + * @param message - The message we want to verify corresponding to sig + * @returns + */ +export function verifyPaygoAddressProof( + psbt: utxolib.bitgo.UtxoPsbt, + outputIndex: number, + message: Buffer, + attestationPubKey: Buffer +): void { + const psbtOutputs = checkForOutput(psbt.data.outputs, outputIndex); + const stored = utxolib.bitgo.getProprietaryKeyValuesFromUnknownKeyValues(psbtOutputs, { + identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER, + subtype: utxolib.bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, + }); + if (!stored) { + throw new Error(`No address proof.`); + } + + // assert stored length is 0 or 1 + if (stored.length === 0) { + throw new Error(`There is no paygo address proof encoded in the PSBT at output ${outputIndex}.`); + } else if (stored.length > 1) { + throw new Error('There are multiple paygo address proofs encoded in the PSBT. Something went wrong.'); + } + + const signature = stored[0].value; + const pub = stored[0].key.keydata; + + // Check that the keydata pubkey is the same as the one we are verifying against + if (Buffer.compare(pub, attestationPubKey) !== 0) { + throw new Error('The public key in the PSBT does not match the provided public key.'); + } + + // It doesn't matter that this is bitcoin or not, we just need to convert the public key buffer into an address format + // for the verification + const messageToVerify = utxolib.address.toBase58Check( + utxolib.crypto.hash160(pub), + utxolib.networks.bitcoin.pubKeyHash, + utxolib.networks.bitcoin + ); + + if (!bitcoinMessage.verify(message, messageToVerify, signature, utxolib.networks.bitcoin.messagePrefix)) { + throw new Error('Cannot verify the paygo address signature with the provided pubkey.'); + } + // We should be verifying the address that was encoded into our message. + const addressFromProof = extractAddressBufferFromPayGoAttestationProof(message).toString(); + + // Check that the address from the proof matches what is in the PSBT + const txOutputs = psbt.txOutputs; + if (outputIndex >= txOutputs.length) { + throw new Error(`Output index ${outputIndex} is out of bounds for PSBT outputs.`); + } + const output = txOutputs[outputIndex]; + const addressFromOutput = utxolib.address.fromOutputScript(output.script, psbt.network); + + if (addressFromProof !== addressFromOutput) { + throw new Error( + `The address from the output (${addressFromOutput}) does not match the address that is in the proof (${addressFromProof}).` + ); + } +} + +/** Get the output index of the paygo output if there is one. It does this by + * checking if the metadata is on one of the outputs of the PSBT. If there is + * no paygo output, return undefined + * + * @param psbt + * @returns number - the index of the output address + */ +export function getPaygoAddressProofOutputIndex(psbt: utxolib.bitgo.UtxoPsbt): number | undefined { + const res = psbt.data.outputs.flatMap((output, outputIndex) => { + const proprietaryKeyVals = utxolib.bitgo.getPsbtOutputProprietaryKeyVals(output, { + identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER, + subtype: utxolib.bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, + }); + + if (proprietaryKeyVals.length > 1) { + throw new Error(`There are multiple PayGo addresses in the PSBT output ${outputIndex}.`); + } + + return proprietaryKeyVals.length === 0 ? [] : [outputIndex]; + }); + + return res.length === 0 ? undefined : res[0]; +} + +export function psbtOutputIncludesPaygoAddressProof(psbt: utxolib.bitgo.UtxoPsbt): boolean { + return getPaygoAddressProofOutputIndex(psbt) !== undefined; +} diff --git a/modules/utxo-core/src/paygo/psbt/index.ts b/modules/utxo-core/src/paygo/psbt/index.ts new file mode 100644 index 0000000000..39af535315 --- /dev/null +++ b/modules/utxo-core/src/paygo/psbt/index.ts @@ -0,0 +1 @@ +export * from './PayGoUtils'; diff --git a/modules/utxo-core/test/paygo/psbt/PayGoUtils.ts b/modules/utxo-core/test/paygo/psbt/PayGoUtils.ts new file mode 100644 index 0000000000..3d745ea7f1 --- /dev/null +++ b/modules/utxo-core/test/paygo/psbt/PayGoUtils.ts @@ -0,0 +1,142 @@ +import assert from 'assert'; + +import * as utxolib from '@bitgo/utxo-lib'; +import * as bitcoinMessage from 'bitcoinjs-message'; +import { decodeProprietaryKey } from 'bip174/src/lib/proprietaryKeyVal'; +import { KeyValue } from 'bip174/src/lib/interfaces'; +import { checkForOutput } from 'bip174/src/lib/utils'; + +import { + addPaygoAddressProof, + getPaygoAddressProofOutputIndex, + psbtOutputIncludesPaygoAddressProof, + verifyPaygoAddressProof, +} from '../../../src/paygo/psbt/PayGoUtils'; +import { generatePayGoAttestationProof } from '../../../src/testutil/generatePayGoAttestationProof.utils'; + +// To construct our PSBTs +const network = utxolib.networks.bitcoin; +const keys = [1, 2, 3].map((v) => utxolib.bip32.fromSeed(Buffer.alloc(16, `test/2/${v}`), network)); +const rootWalletKeys = new utxolib.bitgo.RootWalletKeys([keys[0], keys[1], keys[2]]); + +// PSBT INPUTS AND OUTPUTS +const psbtInputs = utxolib.testutil.inputScriptTypes.map((scriptType) => ({ + scriptType, + value: BigInt(1000), +})); +const psbtOutputs = utxolib.testutil.outputScriptTypes.map((scriptType) => ({ + scriptType, + value: BigInt(900), +})); + +// wallet pub and priv key for tbtc +const dummyPub1 = rootWalletKeys.deriveForChainAndIndex(50, 200); +const attestationPubKey = dummyPub1.user.publicKey; +const attestationPrvKey = dummyPub1.user.privateKey!; + +// UUID structure +const nilUUID = '00000000-0000-0000-0000-000000000000'; + +// our xpub converted to base58 address +const addressToVerify = utxolib.address.toBase58Check( + utxolib.crypto.hash160(Buffer.from(dummyPub1.backup.publicKey)), + utxolib.networks.bitcoin.pubKeyHash, + utxolib.networks.bitcoin +); + +// this should be retuning a Buffer +const addressProofBuffer = generatePayGoAttestationProof(nilUUID, Buffer.from(addressToVerify)); +// signature with the given msg addressProofBuffer +const sig = bitcoinMessage.sign(addressProofBuffer, attestationPrvKey!, true, network.messagePrefix); + +function getTestPsbt() { + return utxolib.testutil.constructPsbt(psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned'); +} + +describe('addPaygoAddressProof and verifyPaygoAddressProof', () => { + function getPaygoProprietaryKey(proprietaryKeyVals: KeyValue[]) { + return proprietaryKeyVals + .map(({ key, value }) => { + return { key: decodeProprietaryKey(key), value }; + }) + .filter((keyValue) => { + return ( + keyValue.key.identifier === utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER && + keyValue.key.subtype === utxolib.bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF + ); + }); + } + + it("should fail a proof verification if the proof isn't valid", () => { + const outputIndex = 0; + const psbt = getTestPsbt(); + addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); + const output = checkForOutput(psbt.data.outputs, outputIndex); + const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!); + assert(proofInPsbt.length === 1); + assert.throws( + () => verifyPaygoAddressProof(psbt, 0, Buffer.from('Random Signed Message'), attestationPubKey), + (e: any) => e.message === 'Cannot verify the paygo address signature with the provided pubkey.' + ); + }); + + it('should add and verify a valid paygo address proof on the PSBT', () => { + const psbt = getTestPsbt(); + psbt.addOutput({ script: utxolib.address.toOutputScript(addressToVerify, network), value: BigInt(10000) }); + const outputIndex = psbt.data.outputs.length - 1; + addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); + verifyPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), attestationPubKey); + }); + + it('should throw an error if there are multiple PayGo proprietary keys in the PSBT', () => { + const outputIndex = 0; + const psbt = getTestPsbt(); + addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); + addPaygoAddressProof(psbt, outputIndex, Buffer.from('signature2'), Buffer.from('fakepubkey2s')); + const output = checkForOutput(psbt.data.outputs, outputIndex); + const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!); + assert(proofInPsbt.length !== 0); + assert(proofInPsbt.length > 1); + assert.throws( + () => verifyPaygoAddressProof(psbt, outputIndex, addressProofBuffer, attestationPubKey), + (e: any) => e.message === 'There are multiple paygo address proofs encoded in the PSBT. Something went wrong.' + ); + }); +}); + +describe('verifyPaygoAddressProof', () => { + it('should throw an error if there is no PayGo address in PSBT', () => { + const psbt = getTestPsbt(); + assert.throws( + () => verifyPaygoAddressProof(psbt, 0, addressProofBuffer, attestationPubKey), + (e: any) => e.message === 'There is no paygo address proof encoded in the PSBT at output 0.' + ); + }); +}); + +describe('getPaygoAddressProofIndex', () => { + it('should get PayGo address proof index from PSBT if there is one', () => { + const psbt = getTestPsbt(); + const outputIndex = 0; + addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); + assert(psbtOutputIncludesPaygoAddressProof(psbt)); + assert(getPaygoAddressProofOutputIndex(psbt) === 0); + }); + + it('should return undefined if there is no PayGo address proof in PSBT', () => { + const psbt = getTestPsbt(); + assert(getPaygoAddressProofOutputIndex(psbt) === undefined); + assert(!psbtOutputIncludesPaygoAddressProof(psbt)); + }); + + it('should return an error and fail if we have multiple PayGo address in the PSBT in the same output index', () => { + const psbt = getTestPsbt(); + const outputIndex = 0; + addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); + addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from('xpub12345abcdef29a028510d3b2d4')); + assert.throws( + () => getPaygoAddressProofOutputIndex(psbt), + (e: any) => e.message === 'There are multiple PayGo addresses in the PSBT output 0.' + ); + }); +}); diff --git a/modules/utxo-lib/src/bitgo/PsbtUtil.ts b/modules/utxo-lib/src/bitgo/PsbtUtil.ts index 2aa36733a9..fbbd187bc8 100644 --- a/modules/utxo-lib/src/bitgo/PsbtUtil.ts +++ b/modules/utxo-lib/src/bitgo/PsbtUtil.ts @@ -20,6 +20,7 @@ export enum ProprietaryKeySubtype { MUSIG2_PARTICIPANT_PUB_KEYS = 0x01, MUSIG2_PUB_NONCE = 0x02, MUSIG2_PARTIAL_SIG = 0x03, + PAYGO_ADDRESS_ATTESTATION_PROOF = 0x04, } /** diff --git a/modules/utxo-lib/src/bitgo/UtxoPsbt.ts b/modules/utxo-lib/src/bitgo/UtxoPsbt.ts index ae45d10dfc..4b28aead73 100644 --- a/modules/utxo-lib/src/bitgo/UtxoPsbt.ts +++ b/modules/utxo-lib/src/bitgo/UtxoPsbt.ts @@ -7,7 +7,7 @@ import { Transaction as ITransaction, TransactionFromBuffer, } from 'bip174/src/lib/interfaces'; -import { checkForInput, checkForOutput } from 'bip174/src/lib/utils'; +import { checkForInput } from 'bip174/src/lib/utils'; import { BufferWriter, varuint } from 'bitcoinjs-lib/src/bufferutils'; import { SessionKey } from '@brandonblack/musig'; import { BIP32Factory, BIP32Interface } from 'bip32'; diff --git a/modules/utxo-lib/src/bitgo/psbt/paygoAddressProof.ts b/modules/utxo-lib/src/bitgo/psbt/paygoAddressProof.ts deleted file mode 100644 index 7e11fc13a2..0000000000 --- a/modules/utxo-lib/src/bitgo/psbt/paygoAddressProof.ts +++ /dev/null @@ -1,97 +0,0 @@ -import * as assert from 'assert'; -import * as bitcoinMessage from 'bitcoinjs-message'; -import { crypto } from 'bitcoinjs-lib'; - -import { address } from '../..'; -import { networks } from '../../networks'; -import { toBase58Check } from '../../address'; -import { getPsbtOutputProprietaryKeyVals, ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER } from '../PsbtUtil'; -import { UtxoPsbt } from '../UtxoPsbt'; - -/** The function consumes the signature as a parameter and adds the PayGo address to the - * PSBT output at the output index where the signature is of the format: - * 0x18Bitcoin Signed Message:\n
signed by - * the HSM beforehand. - * - * @param psbt - PSBT that we need to encode our paygo address into - * @param outputIndex - the index of the address in our output - * @param sig - the signature that we want to encode - */ -export function addPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, sig: Buffer, pub: Buffer): void { - psbt.addProprietaryKeyValToOutput(outputIndex, { - key: { - identifier: PSBT_PROPRIETARY_IDENTIFIER, - subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, - keydata: Buffer.from(pub) - }, - value: sig, - }); -} - -/** Verify the paygo address signature is valid using BitGoJs statics - * - * @param psbt - PSBT we want to verify that the paygo address is in - * @param outputIndex - we have the output index that address is in - * @param pub - The public key that we want to verify the proof with - * @returns - */ -export function verifyPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, pub: Buffer): void { - const stored = psbt.getOutputProprietaryKeyVals(outputIndex, { - identifier: PSBT_PROPRIETARY_IDENTIFIER, - subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, - }); - if (!stored) { - throw new Error('No address proof'); - } - - // assert stored length is 0 or 1 - if (stored.length === 0) { - throw new Error(`There is no paygo address proof encoded in the PSBT at output ${outputIndex}.`); - } else if (stored.length > 1) { - throw new Error('There are multiple paygo address proofs encoded in the PSBT. Something went wrong.'); - } - - const signature = stored[0].value; - // It doesn't matter that this is bitcoin or not, we just need to convert the public key buffer into an address format - // for the verification - const messageToVerify = toBase58Check(crypto.hash160(pub), networks.bitcoin.pubKeyHash, networks.bitcoin); - - // TODO: need to figure out what the message is in this context - // Are we verifying the address of the PayGo? we can call getAddressFromScript given output index. - if (!bitcoinMessage.verify(message, messageToVerify, signature)) { - throw new Error('Cannot verify the paygo address signature with the provided pubkey.'); - } - - const out = psbt.txOutputs[outputIndex]; - assert(out); - const addressFromOutput = address.fromOutputScript(out.script, psbt.network); - const addressFromProof = extractAddressFromPayGoAttestationProof(message, addressFromOutput.length); - - if (addressFromProof !== addressFromOutput) { - throw new Error(`The address from the output (${addressFromOutput}) does not match the address that is in the proof (${addressFromProof}).`) - } -} - -/** Get the output index of the paygo output if there is one. It does this by - * checking if the metadata is on one of the outputs of the PSBT. If there is - * no paygo output, return undefined - * - * @param psbt - * @returns number - the index of the output address - */ -export function getPaygoAddressProofOutputIndex(psbt: UtxoPsbt): number | undefined { - const res = psbt.data.outputs.flatMap((output, outputIndex) => { - const proprietaryKeyVals = getPsbtOutputProprietaryKeyVals(output, { - identifier: PSBT_PROPRIETARY_IDENTIFIER, - subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, - }); - - return proprietaryKeyVals.length === 0 ? [] : [outputIndex]; - }); - - return res.length === 0 ? undefined : res[0]; -} - -export function psbtOutputIncludesPaygoAddressProof(psbt: UtxoPsbt): boolean { - return getPaygoAddressProofOutputIndex(psbt) !== undefined; -} diff --git a/modules/utxo-lib/src/bitgo/zcash/address.ts b/modules/utxo-lib/src/bitgo/zcash/address.ts index e03e00f22f..4943d4cf60 100644 --- a/modules/utxo-lib/src/bitgo/zcash/address.ts +++ b/modules/utxo-lib/src/bitgo/zcash/address.ts @@ -23,7 +23,7 @@ export function toBase58Check(hash: Buffer, version: number): string { } export function fromOutputScript(outputScript: Buffer, network: Network): string { - assert(isZcash(network)); + assert.ok(isZcash(network)); let o; let prefix; try { @@ -41,7 +41,7 @@ export function fromOutputScript(outputScript: Buffer, network: Network): string } export function toOutputScript(address: string, network: Network): Buffer { - assert(isZcash(network)); + assert.ok(isZcash(network)); const { version, hash } = fromBase58Check(address); if (version === network.pubKeyHash) { return payments.p2pkh({ hash }).output as Buffer; diff --git a/modules/utxo-lib/src/testutil/psbt.ts b/modules/utxo-lib/src/testutil/psbt.ts index 3848ac424e..7e849763f9 100644 --- a/modules/utxo-lib/src/testutil/psbt.ts +++ b/modules/utxo-lib/src/testutil/psbt.ts @@ -261,26 +261,3 @@ export function verifyFullySignedSignatures( } }); } - -/** We generate the PayGo attestation proof based on the private key, UUID, and address of our PayGo. - * We create a random entropy of 64 bytes encoded to base58 and used to create the attestation proof - * in the format 0x18Bitcoin Signed Message:\n
- * - * @param attestationPrvKey - * @param uuid - * @param address - */ -export function generatePayGoAttestationProof(attestationPrvKey: Buffer, uuid: string, address: Buffer): Buffer { - // This is our prefix to our bitcoin signed message - const prefixByte = Buffer.from([0x18]); - const signedMessagePrefix = Buffer.from('Bitcoin Signed Message:\n', 'utf8'); - // We always create a 32 byte buffer array but we can implement a random - // function to generate between two ranges of our length of entropy - const entropy = Buffer.allocUnsafe(32); - crypto.getRandomValues(entropy); - const uuidToBuffer = Buffer.from(uuid, 'hex'); - const signedMessageBufferRaw = Buffer.concat([prefixByte, signedMessagePrefix, entropy, address, uuidToBuffer]); - const varInt = Buffer.from([signedMessageBufferRaw.length]); - const fullResMessageBuffer = Buffer.concat([prefixByte, signedMessagePrefix, varInt, entropy, address, uuidToBuffer])l - -} \ No newline at end of file diff --git a/modules/utxo-lib/test/bitgo/psbt/paygoAddressProof.ts b/modules/utxo-lib/test/bitgo/psbt/paygoAddressProof.ts deleted file mode 100644 index ba1bb1d311..0000000000 --- a/modules/utxo-lib/test/bitgo/psbt/paygoAddressProof.ts +++ /dev/null @@ -1,100 +0,0 @@ -import * as assert from 'assert' -import { decodeProprietaryKey } from 'bip174/src/lib/proprietaryKeyVal'; -import { KeyValue } from 'bip174/src/lib/interfaces'; -import { checkForOutput } from 'bip174/src/lib/utils'; - -import { bip32, networks, testutil } from '../../../src' -import { addPaygoAddressProof, verifyPaygoAddressProof, getPaygoAddressProofOutputIndex, psbtOutputIncludesPaygoAddressProof } from "../../../src/bitgo/psbt/paygoAddressProof"; -import { generatePayGoAttestationProof, inputScriptTypes, outputScriptTypes } from '../../../src/testutil'; -import { ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER, RootWalletKeys } from '../../../src/bitgo'; - - -const network = networks.bitcoin; -const keys = [1,2,3].map((v) => bip32.fromSeed(Buffer.alloc(16, `test/2/${v}`), network)) -const rootWalletKeys = new RootWalletKeys([keys[0], keys[1], keys[2]]) -// const dummyKey1 = rootWalletKeys.deriveForChainAndIndex(50, 200); -const dummyKey2 = rootWalletKeys.deriveForChainAndIndex(60, 201); - -const psbtInputs = inputScriptTypes.map((scriptType) => ({scriptType, value: BigInt(1000)})) -const psbtOutputs = outputScriptTypes.map((scriptType) => ({ scriptType, value: BigInt(900)})) -// const dummy1PubKey = dummyKey1.user.publicKey; -// This generatePayGoAttestationProof function should be returning the bitcoin signed message -const sig2 = dummyKey2.user.privateKey!; - -// wallet pub and priv key for tbtc -const attestationPubKey = "xpub661MyMwAqRbcFU2Qx7pvGmmiQpVj8NcR7dSVpgqNChMkQyobpVWWERcrTb47WicmXwkhAY2VrC3hb29s18FDQWJf5pLm3saN6uLXAXpw1GV"; -const attestationPrvKey = "red"; -const nilUUID = '00000000-0000-0000-0000-000000000000'; -const addressProofBuffer = generatePayGoAttestationProof(Buffer.from(attestationPrvKey), nilUUID, Buffer.from(address)) - - -function getTestPsbt() { - return testutil.constructPsbt( - psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned' - ) -} - -describe('addPaygoAddressProof and verifyPaygoAddressProof', () => { - function getPaygoProprietaryKey(proprietaryKeyVals: KeyValue[]) { - return proprietaryKeyVals.map(({key, value}) => { - return { key: decodeProprietaryKey(key), value }; - }).filter((keyValue) => { - return keyValue.key.identifier === PSBT_PROPRIETARY_IDENTIFIER && keyValue.key.subtype === ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF - }); - } - - it("should fail a proof verification if the proof isn't valid", () => { - const outputIndex = 0; - const psbt = getTestPsbt(); - addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey)); - const output = checkForOutput(psbt.data.outputs, outputIndex); - const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!); - assert(proofInPsbt.length === 1) - assert.throws(() => verifyPaygoAddressProof(psbt, 0, dummyKey2.user.publicKey), (e: any) => e.message === 'Cannot verify the paygo address signature with the provided pubkey.'); - }); - - it("should add and verify a valid paygo address proof on the PSBT", () => { - const outputIndex = 0; - const psbt = getTestPsbt(); - addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey)); - // should verify function return a boolean? that way we can assert - // if this is verified, throws an error otherwise or false + error msg as an object - verifyPaygoAddressProof(psbt, outputIndex, Buffer.from(attestationPubKey)); - }); - - it("should throw an error if there are multiple PayGo proprietary keys in the PSBT", () => { - const outputIndex = 0; - const psbt = getTestPsbt(); - addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey)); - addPaygoAddressProof(psbt, outputIndex, Buffer.from(sig2), Buffer.from(attestationPubKey)); - const output = checkForOutput(psbt.data.outputs, outputIndex); - const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!); - assert(proofInPsbt.length !== 0) - assert(proofInPsbt.length <= 1) - assert.throws(() => verifyPaygoAddressProof(psbt, outputIndex, Buffer.from(attestationPubKey)), (e: any) => e.message === 'There are multiple paygo address proofs encoded in the PSBT. Something went wrong.'); - }); -}); - - -describe('verifyPaygoAddressProof', () => { - it('should throw an error if there is no PayGo address in PSBT', () => { - const psbt = getTestPsbt(); - assert.throws(() => verifyPaygoAddressProof(psbt, 0, Buffer.from(attestationPubKey)), (e: any) => e.message === 'here is no paygo address proof encoded in the PSBT.'); - }); -}); - -describe('getPaygoAddressProofIndex', () => { - it('should get PayGo address proof index from PSBT if there is one', () => { - const psbt = getTestPsbt(); - const outputIndex = 0; - addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey)); - assert(psbtOutputIncludesPaygoAddressProof(psbt)); - assert(getPaygoAddressProofOutputIndex(psbt) === 0) - }); - - it("should return undefined if there is no PayGo address proof in PSBT", () => { - const psbt = getTestPsbt(); - assert(getPaygoAddressProofOutputIndex(psbt) === undefined) - assert(!psbtOutputIncludesPaygoAddressProof(psbt)) - }); -}); \ No newline at end of file From e933da1f747c649192dc028f69e6a982d7e782a8 Mon Sep 17 00:00:00 2001 From: Christopher Fong Date: Mon, 16 Jun 2025 11:25:51 -0400 Subject: [PATCH 3/4] chore(utxo-core): add error classes for util func TICKET: BTC-2047 chore(utxo-core): renamed functions consistent and error prototypes TICKET: BTC-2047 chore(utxo-core): bip32utils to sign and verify message TICKET: BTC-2047 chore(utxo-core): added bitcoinjs-message back to package.json TICKET: BTC-2047 chore(utxo-core): refactored functions and debug tests TICKET: BTC-2047 chore(utxo-core): update verify function TICKET: BTC-2047 chore(utxo-core): updated test and verify TICKET: BTC-2047 chore(utxo-core): clean up rebase conflicts TICKET: BTC-2047 chore(utxo-core): added helper function to create attestation TICKET: BTC-2047 --- modules/utxo-core/src/paygo/psbt/Errors.ts | 29 ++++++ .../utxo-core/src/paygo/psbt/PayGoUtils.ts | 96 +++++++++---------- modules/utxo-core/src/testutil/index.ts | 1 + .../utxo-core/src/testutil/parseVaspProof.ts | 25 +++++ .../utxo-core/test/paygo/psbt/PayGoUtils.ts | 76 ++++++++------- modules/utxo-lib/src/bitgo/UtxoPsbt.ts | 71 -------------- 6 files changed, 146 insertions(+), 152 deletions(-) create mode 100644 modules/utxo-core/src/paygo/psbt/Errors.ts create mode 100644 modules/utxo-core/src/testutil/parseVaspProof.ts diff --git a/modules/utxo-core/src/paygo/psbt/Errors.ts b/modules/utxo-core/src/paygo/psbt/Errors.ts new file mode 100644 index 0000000000..ab57780bd8 --- /dev/null +++ b/modules/utxo-core/src/paygo/psbt/Errors.ts @@ -0,0 +1,29 @@ +export class ErrorNoPayGoProof extends Error { + constructor(public outputIndex: number) { + super(`There is no paygo address proof encoded in the PSBT at output ${outputIndex}.`); + } +} + +export class ErrorMultiplePayGoProof extends Error { + constructor() { + super('There are multiple paygo address proofs encoded in the PSBT. Something went wrong.'); + } +} + +export class ErrorPayGoAddressProofFailedVerification extends Error { + constructor() { + super('Cannot verify the paygo address signature with the provided pubkey.'); + } +} + +export class ErrorOutputIndexOutOfBounds extends Error { + constructor(public outputIndex: number) { + super(`Output index ${outputIndex} is out of bounds for PSBT outputs.`); + } +} + +export class ErrorMultiplePayGoProofAtPsbtIndex extends Error { + constructor(public outputIndex: number) { + super(`There are multiple PayGo addresses in the PSBT output ${outputIndex}.`); + } +} diff --git a/modules/utxo-core/src/paygo/psbt/PayGoUtils.ts b/modules/utxo-core/src/paygo/psbt/PayGoUtils.ts index f200beee90..36c80ee3d1 100644 --- a/modules/utxo-core/src/paygo/psbt/PayGoUtils.ts +++ b/modules/utxo-core/src/paygo/psbt/PayGoUtils.ts @@ -1,98 +1,84 @@ import * as utxolib from '@bitgo/utxo-lib'; -import * as bitcoinMessage from 'bitcoinjs-message'; import { checkForOutput } from 'bip174/src/lib/utils'; -import { extractAddressBufferFromPayGoAttestationProof } from '../ExtractAddressPayGoAttestation'; +import { verifyMessage } from '../../bip32utils'; -/** The function consumes the signature as a parameter and adds the PayGo address to the - * PSBT output at the output index where the signature is of the format: - * 0x18Bitcoin Signed Message:\n
signed by - * the HSM beforehand. +import { + ErrorMultiplePayGoProof, + ErrorMultiplePayGoProofAtPsbtIndex, + ErrorNoPayGoProof, + ErrorOutputIndexOutOfBounds, + ErrorPayGoAddressProofFailedVerification, +} from './Errors'; + +const NILLUUID = '00000000-0000-0000-0000-000000000000'; + +/** This function adds the entropy and signature into the PSBT output unknown key vals. + * We store the entropy so that we reconstruct the message
+ * to later verify. * * @param psbt - PSBT that we need to encode our paygo address into * @param outputIndex - the index of the address in our output * @param sig - the signature that we want to encode */ -export function addPaygoAddressProof( +export function addPayGoAddressProof( psbt: utxolib.bitgo.UtxoPsbt, outputIndex: number, sig: Buffer, - pub: Buffer + entropy: Buffer ): void { utxolib.bitgo.addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'output', outputIndex, { key: { identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER, subtype: utxolib.bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, - keydata: pub, + keydata: entropy, }, value: sig, }); } -/** Verify the paygo address signature is valid using BitGoJs statics +/** Verify the paygo address signature is valid using verification pub key. * * @param psbt - PSBT we want to verify that the paygo address is in * @param outputIndex - we have the output index that address is in - * @param pub - The public key that we want to verify the proof with - * @param message - The message we want to verify corresponding to sig + * @param uuid * @returns */ -export function verifyPaygoAddressProof( +export function verifyPayGoAddressProof( psbt: utxolib.bitgo.UtxoPsbt, outputIndex: number, - message: Buffer, - attestationPubKey: Buffer + verificationPubkey: Buffer ): void { const psbtOutputs = checkForOutput(psbt.data.outputs, outputIndex); const stored = utxolib.bitgo.getProprietaryKeyValuesFromUnknownKeyValues(psbtOutputs, { identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER, subtype: utxolib.bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF, }); - if (!stored) { - throw new Error(`No address proof.`); - } // assert stored length is 0 or 1 if (stored.length === 0) { - throw new Error(`There is no paygo address proof encoded in the PSBT at output ${outputIndex}.`); + throw new ErrorNoPayGoProof(outputIndex); } else if (stored.length > 1) { - throw new Error('There are multiple paygo address proofs encoded in the PSBT. Something went wrong.'); + throw new ErrorMultiplePayGoProof(); } + // We get the signature and entropy from our PSBT unknown key vals const signature = stored[0].value; - const pub = stored[0].key.keydata; - - // Check that the keydata pubkey is the same as the one we are verifying against - if (Buffer.compare(pub, attestationPubKey) !== 0) { - throw new Error('The public key in the PSBT does not match the provided public key.'); - } - - // It doesn't matter that this is bitcoin or not, we just need to convert the public key buffer into an address format - // for the verification - const messageToVerify = utxolib.address.toBase58Check( - utxolib.crypto.hash160(pub), - utxolib.networks.bitcoin.pubKeyHash, - utxolib.networks.bitcoin - ); - - if (!bitcoinMessage.verify(message, messageToVerify, signature, utxolib.networks.bitcoin.messagePrefix)) { - throw new Error('Cannot verify the paygo address signature with the provided pubkey.'); - } - // We should be verifying the address that was encoded into our message. - const addressFromProof = extractAddressBufferFromPayGoAttestationProof(message).toString(); + const entropy = stored[0].key.keydata; - // Check that the address from the proof matches what is in the PSBT + // Get the the PayGo address from the txOutputs const txOutputs = psbt.txOutputs; if (outputIndex >= txOutputs.length) { - throw new Error(`Output index ${outputIndex} is out of bounds for PSBT outputs.`); + throw new ErrorOutputIndexOutOfBounds(outputIndex); } const output = txOutputs[outputIndex]; const addressFromOutput = utxolib.address.fromOutputScript(output.script, psbt.network); - if (addressFromProof !== addressFromOutput) { - throw new Error( - `The address from the output (${addressFromOutput}) does not match the address that is in the proof (${addressFromProof}).` - ); + // We construct our message
+ const message = createPayGoAttestationBuffer(addressFromOutput, entropy); + + if (!verifyMessage(message.toString(), verificationPubkey, signature, utxolib.networks.bitcoin)) { + throw new ErrorPayGoAddressProofFailedVerification(); } } @@ -103,7 +89,7 @@ export function verifyPaygoAddressProof( * @param psbt * @returns number - the index of the output address */ -export function getPaygoAddressProofOutputIndex(psbt: utxolib.bitgo.UtxoPsbt): number | undefined { +export function getPayGoAddressProofOutputIndex(psbt: utxolib.bitgo.UtxoPsbt): number | undefined { const res = psbt.data.outputs.flatMap((output, outputIndex) => { const proprietaryKeyVals = utxolib.bitgo.getPsbtOutputProprietaryKeyVals(output, { identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER, @@ -111,7 +97,7 @@ export function getPaygoAddressProofOutputIndex(psbt: utxolib.bitgo.UtxoPsbt): n }); if (proprietaryKeyVals.length > 1) { - throw new Error(`There are multiple PayGo addresses in the PSBT output ${outputIndex}.`); + throw new ErrorMultiplePayGoProofAtPsbtIndex(outputIndex); } return proprietaryKeyVals.length === 0 ? [] : [outputIndex]; @@ -121,5 +107,17 @@ export function getPaygoAddressProofOutputIndex(psbt: utxolib.bitgo.UtxoPsbt): n } export function psbtOutputIncludesPaygoAddressProof(psbt: utxolib.bitgo.UtxoPsbt): boolean { - return getPaygoAddressProofOutputIndex(psbt) !== undefined; + return getPayGoAddressProofOutputIndex(psbt) !== undefined; +} + +/** This function reconstructs the proof
+ * given the address and entropy. + * + * @param address + * @param entropy + * @returns + */ +export function createPayGoAttestationBuffer(address: string, entropy: Buffer): Buffer { + const addressBuffer = Buffer.from(address); + return Buffer.concat([entropy, addressBuffer, Buffer.from(NILLUUID)]); } diff --git a/modules/utxo-core/src/testutil/index.ts b/modules/utxo-core/src/testutil/index.ts index 8d0a2dc45f..e0489f5195 100644 --- a/modules/utxo-core/src/testutil/index.ts +++ b/modules/utxo-core/src/testutil/index.ts @@ -2,3 +2,4 @@ export * from './fixtures.utils'; export * from './key.utils'; export * from './toPlainObject.utils'; export * from './generatePayGoAttestationProof.utils'; +export * from './parseVaspProof'; diff --git a/modules/utxo-core/src/testutil/parseVaspProof.ts b/modules/utxo-core/src/testutil/parseVaspProof.ts new file mode 100644 index 0000000000..8b9e32e02f --- /dev/null +++ b/modules/utxo-core/src/testutil/parseVaspProof.ts @@ -0,0 +1,25 @@ +import * as utxolib from '@bitgo/utxo-lib'; + +/** We receive a proof in the form: + * 0x18Bitcoin Signed Message:\n
+ * and when verifying our message in our PayGo utils we want to only verify + * the message portion of our proof. This helps to pare our proof in that format, + * and returns a Buffer. + * + * @param proof + * @returns + */ +export function parseVaspProof(proof: Buffer): Buffer { + const prefix = '\u0018Bitcoin Signed Message:\n'; + if (proof.toString().startsWith(prefix)) { + proof = proof.slice(Buffer.from(prefix).length); + utxolib.bufferutils.varuint.decode(proof, 0); + // Determines how many bytes were consumed during our last varuint.decode(Buffer, offset) + // So if varuint.decode(0xfd) then varuint.decode.bytes = 3 + // varuint.decode(0xfe) then varuint.decode.bytes = 5, etc. + const varintBytesLength = utxolib.bufferutils.varuint.decode.bytes; + + proof.slice(varintBytesLength); + } + return proof; +} diff --git a/modules/utxo-core/test/paygo/psbt/PayGoUtils.ts b/modules/utxo-core/test/paygo/psbt/PayGoUtils.ts index 3d745ea7f1..0aabda06c4 100644 --- a/modules/utxo-core/test/paygo/psbt/PayGoUtils.ts +++ b/modules/utxo-core/test/paygo/psbt/PayGoUtils.ts @@ -1,18 +1,21 @@ import assert from 'assert'; +import crypto from 'crypto'; import * as utxolib from '@bitgo/utxo-lib'; -import * as bitcoinMessage from 'bitcoinjs-message'; import { decodeProprietaryKey } from 'bip174/src/lib/proprietaryKeyVal'; import { KeyValue } from 'bip174/src/lib/interfaces'; import { checkForOutput } from 'bip174/src/lib/utils'; import { - addPaygoAddressProof, - getPaygoAddressProofOutputIndex, + addPayGoAddressProof, + createPayGoAttestationBuffer, + getPayGoAddressProofOutputIndex, psbtOutputIncludesPaygoAddressProof, - verifyPaygoAddressProof, + verifyPayGoAddressProof, } from '../../../src/paygo/psbt/PayGoUtils'; import { generatePayGoAttestationProof } from '../../../src/testutil/generatePayGoAttestationProof.utils'; +import { parseVaspProof } from '../../../src/testutil/parseVaspProof'; +import { signMessage } from '../../../src/bip32utils'; // To construct our PSBTs const network = utxolib.networks.bitcoin; @@ -35,7 +38,7 @@ const attestationPubKey = dummyPub1.user.publicKey; const attestationPrvKey = dummyPub1.user.privateKey!; // UUID structure -const nilUUID = '00000000-0000-0000-0000-000000000000'; +const nillUUID = '00000000-0000-0000-0000-000000000000'; // our xpub converted to base58 address const addressToVerify = utxolib.address.toBase58Check( @@ -45,9 +48,13 @@ const addressToVerify = utxolib.address.toBase58Check( ); // this should be retuning a Buffer -const addressProofBuffer = generatePayGoAttestationProof(nilUUID, Buffer.from(addressToVerify)); +const addressProofBuffer = generatePayGoAttestationProof(nillUUID, Buffer.from(addressToVerify)); +const addressProofMsgBuffer = parseVaspProof(addressProofBuffer); +// We know that that the entropy is a set 64 bytes. +const addressProofEntropy = addressProofMsgBuffer.subarray(0, 65); + // signature with the given msg addressProofBuffer -const sig = bitcoinMessage.sign(addressProofBuffer, attestationPrvKey!, true, network.messagePrefix); +const sig = signMessage(addressProofMsgBuffer.toString(), attestationPrvKey!, network); function getTestPsbt() { return utxolib.testutil.constructPsbt(psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned'); @@ -67,38 +74,25 @@ describe('addPaygoAddressProof and verifyPaygoAddressProof', () => { }); } - it("should fail a proof verification if the proof isn't valid", () => { - const outputIndex = 0; - const psbt = getTestPsbt(); - addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); - const output = checkForOutput(psbt.data.outputs, outputIndex); - const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!); - assert(proofInPsbt.length === 1); - assert.throws( - () => verifyPaygoAddressProof(psbt, 0, Buffer.from('Random Signed Message'), attestationPubKey), - (e: any) => e.message === 'Cannot verify the paygo address signature with the provided pubkey.' - ); - }); - it('should add and verify a valid paygo address proof on the PSBT', () => { const psbt = getTestPsbt(); psbt.addOutput({ script: utxolib.address.toOutputScript(addressToVerify, network), value: BigInt(10000) }); const outputIndex = psbt.data.outputs.length - 1; - addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); - verifyPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), attestationPubKey); + addPayGoAddressProof(psbt, outputIndex, sig, addressProofEntropy); + verifyPayGoAddressProof(psbt, outputIndex, attestationPubKey); }); it('should throw an error if there are multiple PayGo proprietary keys in the PSBT', () => { const outputIndex = 0; const psbt = getTestPsbt(); - addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); - addPaygoAddressProof(psbt, outputIndex, Buffer.from('signature2'), Buffer.from('fakepubkey2s')); + addPayGoAddressProof(psbt, outputIndex, sig, addressProofEntropy); + addPayGoAddressProof(psbt, outputIndex, Buffer.from('signature2'), crypto.randomBytes(64)); const output = checkForOutput(psbt.data.outputs, outputIndex); const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!); assert(proofInPsbt.length !== 0); assert(proofInPsbt.length > 1); assert.throws( - () => verifyPaygoAddressProof(psbt, outputIndex, addressProofBuffer, attestationPubKey), + () => verifyPayGoAddressProof(psbt, outputIndex, attestationPubKey), (e: any) => e.message === 'There are multiple paygo address proofs encoded in the PSBT. Something went wrong.' ); }); @@ -108,7 +102,7 @@ describe('verifyPaygoAddressProof', () => { it('should throw an error if there is no PayGo address in PSBT', () => { const psbt = getTestPsbt(); assert.throws( - () => verifyPaygoAddressProof(psbt, 0, addressProofBuffer, attestationPubKey), + () => verifyPayGoAddressProof(psbt, 0, attestationPubKey), (e: any) => e.message === 'There is no paygo address proof encoded in the PSBT at output 0.' ); }); @@ -118,25 +112,43 @@ describe('getPaygoAddressProofIndex', () => { it('should get PayGo address proof index from PSBT if there is one', () => { const psbt = getTestPsbt(); const outputIndex = 0; - addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); + addPayGoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); assert(psbtOutputIncludesPaygoAddressProof(psbt)); - assert(getPaygoAddressProofOutputIndex(psbt) === 0); + assert(getPayGoAddressProofOutputIndex(psbt) === 0); }); it('should return undefined if there is no PayGo address proof in PSBT', () => { const psbt = getTestPsbt(); - assert(getPaygoAddressProofOutputIndex(psbt) === undefined); + assert(getPayGoAddressProofOutputIndex(psbt) === undefined); assert(!psbtOutputIncludesPaygoAddressProof(psbt)); }); it('should return an error and fail if we have multiple PayGo address in the PSBT in the same output index', () => { const psbt = getTestPsbt(); const outputIndex = 0; - addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey)); - addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from('xpub12345abcdef29a028510d3b2d4')); + addPayGoAddressProof(psbt, outputIndex, sig, addressProofEntropy); + addPayGoAddressProof(psbt, outputIndex, sig, crypto.randomBytes(64)); assert.throws( - () => getPaygoAddressProofOutputIndex(psbt), + () => getPayGoAddressProofOutputIndex(psbt), (e: any) => e.message === 'There are multiple PayGo addresses in the PSBT output 0.' ); }); }); + +describe('createPayGoAttestationBuffer', () => { + it('should create a PayGo Attestation proof matching with original proof', () => { + const payGoAttestationProof = createPayGoAttestationBuffer(addressToVerify, addressProofEntropy); + assert.strictEqual(payGoAttestationProof.toString(), addressProofMsgBuffer.toString()); + assert(Buffer.compare(payGoAttestationProof, addressProofMsgBuffer) === 0); + }); + + it('should create a PayGo Attestation proof that does not match with different uuid', () => { + const addressProofBufferDiffUuid = generatePayGoAttestationProof( + '00000000-0000-0000-0000-000000000001', + Buffer.from(addressToVerify) + ); + const payGoAttestationProof = createPayGoAttestationBuffer(addressToVerify, addressProofEntropy); + assert.notStrictEqual(payGoAttestationProof.toString(), addressProofBufferDiffUuid.toString()); + assert(Buffer.compare(payGoAttestationProof, addressProofBufferDiffUuid) !== 0); + }); +}); diff --git a/modules/utxo-lib/src/bitgo/UtxoPsbt.ts b/modules/utxo-lib/src/bitgo/UtxoPsbt.ts index 4b28aead73..678bc17b2d 100644 --- a/modules/utxo-lib/src/bitgo/UtxoPsbt.ts +++ b/modules/utxo-lib/src/bitgo/UtxoPsbt.ts @@ -57,8 +57,6 @@ import { getTaprootOutputKey } from '../taproot'; import { getPsbtInputProprietaryKeyVals, getPsbtInputSignatureCount, - getPsbtOutputProprietaryKeyVals, - ProprietaryKeySearch, ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER, } from './PsbtUtil'; @@ -1154,75 +1152,6 @@ export class UtxoPsbt = UtxoTransaction ukv.key.equals(key)); - if (ukvIndex > -1) { - output.unknownKeyVals[ukvIndex] = { key, value }; - return this; - } - } - this.addUnknownKeyValToOutput(outputIndex, { - key, - value, - }); - return this; - } - - /** - * To delete any data from proprietary key value in PSBT output. - * Default identifierEncoding is utf-8 for identifier. - */ - deleteProprietaryKeyValsInOutput(outputIndex: number, keysToDelete?: ProprietaryKeySearch): this { - const output = checkForOutput(this.data.outputs, outputIndex); - if (!output.unknownKeyVals?.length) { - return this; - } - if (keysToDelete && keysToDelete.subtype === undefined && Buffer.isBuffer(keysToDelete.keydata)) { - throw new Error('invalid proprietary key search filter combination. subtype is required'); - } - output.unknownKeyVals = output.unknownKeyVals.filter((keyValue, i) => { - const key = decodeProprietaryKey(keyValue.key); - return !( - keysToDelete === undefined || - (keysToDelete.identifier === key.identifier && - (keysToDelete.subtype === undefined || - (keysToDelete.subtype === key.subtype && - (!Buffer.isBuffer(keysToDelete.keydata) || keysToDelete.keydata.equals(key.keydata))))) - ); - }); - return this; - } - private createMusig2NonceForInput( inputIndex: number, keyPair: BIP32Interface, From a5a81f99d49d6b9d9788dfcaee369d0efc4b5d39 Mon Sep 17 00:00:00 2001 From: Christopher Fong Date: Fri, 20 Jun 2025 09:26:27 -0400 Subject: [PATCH 4/4] chore(utxo-core): renamed files and moved helper to own file TICKET: BTC-2047 chore(utxo-core): updated index.ts TICKET: BTC-2047 --- modules/utxo-core/src/paygo/attestation.ts | 13 +++++++++++++ modules/utxo-core/src/paygo/psbt/index.ts | 2 +- .../psbt/{PayGoUtils.ts => payGoAddressProof.ts} | 15 +-------------- .../psbt/{PayGoUtils.ts => payGoAddressProof.ts} | 9 +++------ 4 files changed, 18 insertions(+), 21 deletions(-) create mode 100644 modules/utxo-core/src/paygo/attestation.ts rename modules/utxo-core/src/paygo/psbt/{PayGoUtils.ts => payGoAddressProof.ts} (90%) rename modules/utxo-core/test/paygo/psbt/{PayGoUtils.ts => payGoAddressProof.ts} (96%) diff --git a/modules/utxo-core/src/paygo/attestation.ts b/modules/utxo-core/src/paygo/attestation.ts new file mode 100644 index 0000000000..edaf6f0ee3 --- /dev/null +++ b/modules/utxo-core/src/paygo/attestation.ts @@ -0,0 +1,13 @@ +export const NILL_UUID = '00000000-0000-0000-0000-000000000000'; + +/** This function reconstructs the proof
+ * given the address and entropy. + * + * @param address + * @param entropy + * @returns + */ +export function createPayGoAttestationBuffer(address: string, entropy: Buffer): Buffer { + const addressBuffer = Buffer.from(address); + return Buffer.concat([entropy, addressBuffer, Buffer.from(NILL_UUID)]); +} diff --git a/modules/utxo-core/src/paygo/psbt/index.ts b/modules/utxo-core/src/paygo/psbt/index.ts index 39af535315..bea5042737 100644 --- a/modules/utxo-core/src/paygo/psbt/index.ts +++ b/modules/utxo-core/src/paygo/psbt/index.ts @@ -1 +1 @@ -export * from './PayGoUtils'; +export * from './payGoAddressProof'; diff --git a/modules/utxo-core/src/paygo/psbt/PayGoUtils.ts b/modules/utxo-core/src/paygo/psbt/payGoAddressProof.ts similarity index 90% rename from modules/utxo-core/src/paygo/psbt/PayGoUtils.ts rename to modules/utxo-core/src/paygo/psbt/payGoAddressProof.ts index 36c80ee3d1..228d9c92aa 100644 --- a/modules/utxo-core/src/paygo/psbt/PayGoUtils.ts +++ b/modules/utxo-core/src/paygo/psbt/payGoAddressProof.ts @@ -2,6 +2,7 @@ import * as utxolib from '@bitgo/utxo-lib'; import { checkForOutput } from 'bip174/src/lib/utils'; import { verifyMessage } from '../../bip32utils'; +import { createPayGoAttestationBuffer } from '../attestation'; import { ErrorMultiplePayGoProof, @@ -11,8 +12,6 @@ import { ErrorPayGoAddressProofFailedVerification, } from './Errors'; -const NILLUUID = '00000000-0000-0000-0000-000000000000'; - /** This function adds the entropy and signature into the PSBT output unknown key vals. * We store the entropy so that we reconstruct the message
* to later verify. @@ -109,15 +108,3 @@ export function getPayGoAddressProofOutputIndex(psbt: utxolib.bitgo.UtxoPsbt): n export function psbtOutputIncludesPaygoAddressProof(psbt: utxolib.bitgo.UtxoPsbt): boolean { return getPayGoAddressProofOutputIndex(psbt) !== undefined; } - -/** This function reconstructs the proof
- * given the address and entropy. - * - * @param address - * @param entropy - * @returns - */ -export function createPayGoAttestationBuffer(address: string, entropy: Buffer): Buffer { - const addressBuffer = Buffer.from(address); - return Buffer.concat([entropy, addressBuffer, Buffer.from(NILLUUID)]); -} diff --git a/modules/utxo-core/test/paygo/psbt/PayGoUtils.ts b/modules/utxo-core/test/paygo/psbt/payGoAddressProof.ts similarity index 96% rename from modules/utxo-core/test/paygo/psbt/PayGoUtils.ts rename to modules/utxo-core/test/paygo/psbt/payGoAddressProof.ts index 0aabda06c4..7876541c21 100644 --- a/modules/utxo-core/test/paygo/psbt/PayGoUtils.ts +++ b/modules/utxo-core/test/paygo/psbt/payGoAddressProof.ts @@ -8,14 +8,14 @@ import { checkForOutput } from 'bip174/src/lib/utils'; import { addPayGoAddressProof, - createPayGoAttestationBuffer, getPayGoAddressProofOutputIndex, psbtOutputIncludesPaygoAddressProof, verifyPayGoAddressProof, -} from '../../../src/paygo/psbt/PayGoUtils'; +} from '../../../src/paygo/psbt/payGoAddressProof'; import { generatePayGoAttestationProof } from '../../../src/testutil/generatePayGoAttestationProof.utils'; import { parseVaspProof } from '../../../src/testutil/parseVaspProof'; import { signMessage } from '../../../src/bip32utils'; +import { createPayGoAttestationBuffer, NILL_UUID } from '../../../src/paygo/attestation'; // To construct our PSBTs const network = utxolib.networks.bitcoin; @@ -37,9 +37,6 @@ const dummyPub1 = rootWalletKeys.deriveForChainAndIndex(50, 200); const attestationPubKey = dummyPub1.user.publicKey; const attestationPrvKey = dummyPub1.user.privateKey!; -// UUID structure -const nillUUID = '00000000-0000-0000-0000-000000000000'; - // our xpub converted to base58 address const addressToVerify = utxolib.address.toBase58Check( utxolib.crypto.hash160(Buffer.from(dummyPub1.backup.publicKey)), @@ -48,7 +45,7 @@ const addressToVerify = utxolib.address.toBase58Check( ); // this should be retuning a Buffer -const addressProofBuffer = generatePayGoAttestationProof(nillUUID, Buffer.from(addressToVerify)); +const addressProofBuffer = generatePayGoAttestationProof(NILL_UUID, Buffer.from(addressToVerify)); const addressProofMsgBuffer = parseVaspProof(addressProofBuffer); // We know that that the entropy is a set 64 bytes. const addressProofEntropy = addressProofMsgBuffer.subarray(0, 65);