Skip to content

Commit a520165

Browse files
chore(utxo-core): refactored functions and debug tests
TICKET: BTC-2047
1 parent cecc60e commit a520165

File tree

4 files changed

+61
-43
lines changed

4 files changed

+61
-43
lines changed

modules/utxo-core/src/paygo/psbt/PayGoUtils.ts

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
import * as utxolib from '@bitgo/utxo-lib';
2+
import * as bitcoinMessage from 'bitcoinjs-message';
23
import { checkForOutput } from 'bip174/src/lib/utils';
34

4-
import { extractAddressBufferFromPayGoAttestationProof } from '../ExtractAddressPayGoAttestation';
5-
import { verifyMessage } from '../../bip32utils';
6-
75
import {
86
ErrorMultiplePayGoProof,
97
ErrorMultiplePayGoProofAtPsbtIndex,
108
ErrorNoPayGoProof,
119
ErrorOutputIndexOutOfBounds,
1210
ErrorPayGoAddressProofFailedVerification,
13-
ErrorProofAddressMismatchOutputAdress,
1411
} from './Errors';
1512

16-
/** The function consumes the signature as a parameter and adds the PayGo address to the
17-
* PSBT output at the output index where the signature is of the format:
18-
* 0x18Bitcoin Signed Message:\n<varint_length><ENTROPY><ADDRESS><UUID> signed by
19-
* the HSM beforehand.
13+
/** This function adds the entropy and signature into the PSBT output unknown key vals.
14+
* We store the entropy so that we reconstruct the message <varint_len><entropy><address><uuid>
15+
* to later verify.
2016
*
2117
* @param psbt - PSBT that we need to encode our paygo address into
2218
* @param outputIndex - the index of the address in our output
@@ -26,13 +22,13 @@ export function addPayGoAddressProof(
2622
psbt: utxolib.bitgo.UtxoPsbt,
2723
outputIndex: number,
2824
sig: Buffer,
29-
pub: Buffer
25+
entropy: Buffer
3026
): void {
3127
utxolib.bitgo.addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'output', outputIndex, {
3228
key: {
3329
identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER,
3430
subtype: utxolib.bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF,
35-
keydata: pub,
31+
keydata: entropy,
3632
},
3733
value: sig,
3834
});
@@ -42,15 +38,14 @@ export function addPayGoAddressProof(
4238
*
4339
* @param psbt - PSBT we want to verify that the paygo address is in
4440
* @param outputIndex - we have the output index that address is in
45-
* @param pub - The public key that we want to verify the proof with
46-
* @param message - The message we want to verify corresponding to sig
41+
* @param uuid
4742
* @returns
4843
*/
4944
export function verifyPayGoAddressProof(
5045
psbt: utxolib.bitgo.UtxoPsbt,
5146
outputIndex: number,
52-
message: Buffer,
53-
attestationPubKey: Buffer
47+
uuid: string,
48+
msg?: Buffer
5449
): void {
5550
const psbtOutputs = checkForOutput(psbt.data.outputs, outputIndex);
5651
const stored = utxolib.bitgo.getProprietaryKeyValuesFromUnknownKeyValues(psbtOutputs, {
@@ -65,33 +60,23 @@ export function verifyPayGoAddressProof(
6560
throw new ErrorMultiplePayGoProof();
6661
}
6762

63+
// We get the signature and entropy from our PSBT unknown key vals
6864
const signature = stored[0].value;
69-
const pub = stored[0].key.keydata;
70-
71-
// Check that the keydata pubkey is the same as the one we are verifying against
72-
if (Buffer.compare(pub, attestationPubKey) !== 0) {
73-
throw new Error('The public key in the PSBT does not match the provided public key.');
74-
}
65+
const entropy = stored[0].key.keydata;
7566

76-
// It doesn't matter that this is bitcoin or not, we just need to convert the public key buffer into an address format
77-
// for the verification
78-
if (!verifyMessage(message.toString(), pub, signature, utxolib.networks.bitcoin)) {
79-
throw new ErrorPayGoAddressProofFailedVerification();
80-
}
81-
82-
// We should be verifying the address that was encoded into our message.
83-
const addressFromProof = extractAddressBufferFromPayGoAttestationProof(message).toString();
84-
85-
// Check that the address from the proof matches what is in the PSBT
67+
// Get the the PayGo address from the txOutputs
8668
const txOutputs = psbt.txOutputs;
8769
if (outputIndex >= txOutputs.length) {
8870
throw new ErrorOutputIndexOutOfBounds(outputIndex);
8971
}
9072
const output = txOutputs[outputIndex];
9173
const addressFromOutput = utxolib.address.fromOutputScript(output.script, psbt.network);
9274

93-
if (addressFromProof !== addressFromOutput) {
94-
throw new ErrorProofAddressMismatchOutputAdress(addressFromProof, addressFromOutput);
75+
// We construct our message <ENTROPY><ADDRESS><UUID>
76+
const message = Buffer.concat([entropy, Buffer.from(addressFromOutput), Buffer.from(uuid)]);
77+
78+
if (!bitcoinMessage.verify(message, addressFromOutput, signature, utxolib.networks.bitcoin.messagePrefix)) {
79+
throw new ErrorPayGoAddressProofFailedVerification();
9580
}
9681
}
9782

modules/utxo-core/src/testutil/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './fixtures.utils';
22
export * from './key.utils';
33
export * from './toPlainObject.utils';
44
export * from './generatePayGoAttestationProof.utils';
5+
export * from './parseVaspProof';
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as utxolib from '@bitgo/utxo-lib';
2+
3+
/** We receive a proof in the form:
4+
* 0x18Bitcoin Signed Message:\n<varint_length><ENTROPY><ADDRESS><UUID>
5+
* and when verifying our message in our PayGo utils we want to only verify
6+
* the message portion of our proof. This helps to pare our proof in that format,
7+
* and returns a Buffer.
8+
*
9+
* @param proof
10+
* @returns
11+
*/
12+
export function parseVaspProof(proof: Buffer): Buffer {
13+
const prefix = '\u0018Bitcoin Signed Message:\n';
14+
if (proof.toString().startsWith(prefix)) {
15+
proof = proof.slice(Buffer.from(prefix).length);
16+
utxolib.bufferutils.varuint.decode(proof, 0);
17+
// Determines how many bytes were consumed during our last varuint.decode(Buffer, offset)
18+
// So if varuint.decode(0xfd) then varuint.decode.bytes = 3
19+
// varuint.decode(0xfe) then varuint.decode.bytes = 5, etc.
20+
const varintBytesLength = utxolib.bufferutils.varuint.decode.bytes;
21+
22+
proof.slice(varintBytesLength);
23+
}
24+
return proof;
25+
}

modules/utxo-core/test/paygo/psbt/PayGoUtils.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import assert from 'assert';
2+
import crypto from 'crypto';
23

34
import * as utxolib from '@bitgo/utxo-lib';
5+
import * as bitcoinMessage from 'bitcoinjs-message';
46
import { decodeProprietaryKey } from 'bip174/src/lib/proprietaryKeyVal';
57
import { KeyValue } from 'bip174/src/lib/interfaces';
68
import { checkForOutput } from 'bip174/src/lib/utils';
@@ -12,7 +14,8 @@ import {
1214
verifyPayGoAddressProof,
1315
} from '../../../src/paygo/psbt/PayGoUtils';
1416
import { generatePayGoAttestationProof } from '../../../src/testutil/generatePayGoAttestationProof.utils';
15-
import { signMessage } from '../../../src/bip32utils';
17+
import { parseVaspProof } from '../../../src/testutil/parseVaspProof';
18+
// import { signMessage } from '../../../src/bip32utils';
1619

1720
// To construct our PSBTs
1821
const network = utxolib.networks.bitcoin;
@@ -46,8 +49,12 @@ const addressToVerify = utxolib.address.toBase58Check(
4649

4750
// this should be retuning a Buffer
4851
const addressProofBuffer = generatePayGoAttestationProof(nilUUID, Buffer.from(addressToVerify));
52+
const addressProofMsgBuffer = parseVaspProof(addressProofBuffer);
53+
// We know that that the entropy is a set 64 bytes.
54+
const addressProofEntropy = addressProofMsgBuffer.subarray(0, 65);
55+
4956
// signature with the given msg addressProofBuffer
50-
const sig = signMessage(addressProofBuffer.toString(), attestationPrvKey!, network);
57+
const sig = bitcoinMessage.sign(addressProofMsgBuffer, attestationPrvKey!, true);
5158

5259
function getTestPsbt() {
5360
return utxolib.testutil.constructPsbt(psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned');
@@ -67,15 +74,15 @@ describe('addPaygoAddressProof and verifyPaygoAddressProof', () => {
6774
});
6875
}
6976

70-
it("should fail a proof verification if the proof isn't valid", () => {
77+
it('should fail a proof verification with wrong UUID', () => {
7178
const outputIndex = 0;
7279
const psbt = getTestPsbt();
73-
addPayGoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey));
80+
addPayGoAddressProof(psbt, outputIndex, sig, addressProofEntropy);
7481
const output = checkForOutput(psbt.data.outputs, outputIndex);
7582
const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!);
7683
assert(proofInPsbt.length === 1);
7784
assert.throws(
78-
() => verifyPayGoAddressProof(psbt, 0, Buffer.from('Random Signed Message'), attestationPubKey),
85+
() => verifyPayGoAddressProof(psbt, 0, '00000000-0000-0000-0000-000000000001'),
7986
(e: any) => e.message === 'Cannot verify the paygo address signature with the provided pubkey.'
8087
);
8188
});
@@ -84,21 +91,21 @@ describe('addPaygoAddressProof and verifyPaygoAddressProof', () => {
8491
const psbt = getTestPsbt();
8592
psbt.addOutput({ script: utxolib.address.toOutputScript(addressToVerify, network), value: BigInt(10000) });
8693
const outputIndex = psbt.data.outputs.length - 1;
87-
addPayGoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey));
88-
verifyPayGoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), attestationPubKey);
94+
addPayGoAddressProof(psbt, outputIndex, sig, addressProofEntropy);
95+
verifyPayGoAddressProof(psbt, outputIndex, nilUUID, Buffer.from(addressToVerify));
8996
});
9097

9198
it('should throw an error if there are multiple PayGo proprietary keys in the PSBT', () => {
9299
const outputIndex = 0;
93100
const psbt = getTestPsbt();
94-
addPayGoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey));
95-
addPayGoAddressProof(psbt, outputIndex, Buffer.from('signature2'), Buffer.from('fakepubkey2s'));
101+
addPayGoAddressProof(psbt, outputIndex, sig, addressProofEntropy);
102+
addPayGoAddressProof(psbt, outputIndex, Buffer.from('signature2'), crypto.randomBytes(64));
96103
const output = checkForOutput(psbt.data.outputs, outputIndex);
97104
const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!);
98105
assert(proofInPsbt.length !== 0);
99106
assert(proofInPsbt.length > 1);
100107
assert.throws(
101-
() => verifyPayGoAddressProof(psbt, outputIndex, addressProofBuffer, attestationPubKey),
108+
() => verifyPayGoAddressProof(psbt, outputIndex, nilUUID),
102109
(e: any) => e.message === 'There are multiple paygo address proofs encoded in the PSBT. Something went wrong.'
103110
);
104111
});
@@ -108,7 +115,7 @@ describe('verifyPaygoAddressProof', () => {
108115
it('should throw an error if there is no PayGo address in PSBT', () => {
109116
const psbt = getTestPsbt();
110117
assert.throws(
111-
() => verifyPayGoAddressProof(psbt, 0, addressProofBuffer, attestationPubKey),
118+
() => verifyPayGoAddressProof(psbt, 0, nilUUID),
112119
(e: any) => e.message === 'There is no paygo address proof encoded in the PSBT at output 0.'
113120
);
114121
});

0 commit comments

Comments
 (0)