Skip to content

Commit 795048f

Browse files
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
1 parent 1b3d475 commit 795048f

File tree

6 files changed

+169
-152
lines changed

6 files changed

+169
-152
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './ExtractAddressPayGoAttestation';
2+
export * from './psbt';
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import * as assert from 'assert';
21
import * as bitcoinMessage from 'bitcoinjs-message';
32
import { crypto } from 'bitcoinjs-lib';
3+
import { networks, bitgo } from '@bitgo/utxo-lib';
4+
import { toBase58Check } from '@bitgo/utxo-lib/src/address';
45

5-
import { address } from '../..';
6-
import { networks } from '../../networks';
7-
import { toBase58Check } from '../../address';
8-
import { getPsbtOutputProprietaryKeyVals, ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER } from '../PsbtUtil';
9-
import { UtxoPsbt } from '../UtxoPsbt';
6+
import { extractAddressBufferFromPayGoAttestationProof } from '../ExtractAddressPayGoAttestation';
107

11-
/** The function consumes the signature as a parameter and adds the PayGo address to the
8+
/** The function consumes the signature as a parameter and adds the PayGo address to the
129
* PSBT output at the output index where the signature is of the format:
1310
* 0x18Bitcoin Signed Message:\n<varint_length><ENTROPY><ADDRESS><UUID> signed by
1411
* the HSM beforehand.
@@ -17,12 +14,12 @@ import { UtxoPsbt } from '../UtxoPsbt';
1714
* @param outputIndex - the index of the address in our output
1815
* @param sig - the signature that we want to encode
1916
*/
20-
export function addPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, sig: Buffer, pub: Buffer): void {
17+
export function addPaygoAddressProof(psbt: bitgo.UtxoPsbt, outputIndex: number, sig: Buffer, pub: Buffer): void {
2118
psbt.addProprietaryKeyValToOutput(outputIndex, {
2219
key: {
23-
identifier: PSBT_PROPRIETARY_IDENTIFIER,
24-
subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF,
25-
keydata: Buffer.from(pub)
20+
identifier: bitgo.PSBT_PROPRIETARY_IDENTIFIER,
21+
subtype: bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF,
22+
keydata: pub,
2623
},
2724
value: sig,
2825
});
@@ -33,15 +30,16 @@ export function addPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, sig: B
3330
* @param psbt - PSBT we want to verify that the paygo address is in
3431
* @param outputIndex - we have the output index that address is in
3532
* @param pub - The public key that we want to verify the proof with
33+
* @param message - The message we want to verify corresponding to sig
3634
* @returns
3735
*/
38-
export function verifyPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, pub: Buffer): void {
36+
export function verifyPaygoAddressProof(psbt: bitgo.UtxoPsbt, outputIndex: number, message: Buffer): void {
3937
const stored = psbt.getOutputProprietaryKeyVals(outputIndex, {
40-
identifier: PSBT_PROPRIETARY_IDENTIFIER,
41-
subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF,
38+
identifier: bitgo.PSBT_PROPRIETARY_IDENTIFIER,
39+
subtype: bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF,
4240
});
4341
if (!stored) {
44-
throw new Error('No address proof');
42+
throw new Error(`No address proof.`);
4543
}
4644

4745
// assert stored length is 0 or 1
@@ -52,23 +50,23 @@ export function verifyPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, pub
5250
}
5351

5452
const signature = stored[0].value;
53+
const pub = stored[0].key.keydata;
5554
// It doesn't matter that this is bitcoin or not, we just need to convert the public key buffer into an address format
5655
// for the verification
5756
const messageToVerify = toBase58Check(crypto.hash160(pub), networks.bitcoin.pubKeyHash, networks.bitcoin);
5857

59-
// TODO: need to figure out what the message is in this context
60-
// Are we verifying the address of the PayGo? we can call getAddressFromScript given output index.
6158
if (!bitcoinMessage.verify(message, messageToVerify, signature)) {
6259
throw new Error('Cannot verify the paygo address signature with the provided pubkey.');
6360
}
61+
// We should be verifying the address that was encoded into our message, not from our transaction output.
62+
// We already do this because our message should be signed with our address
63+
// which is the same one we used to verify the authenticity of the message given its signature as well.
64+
const addressFromProof = extractAddressBufferFromPayGoAttestationProof(message);
6465

65-
const out = psbt.txOutputs[outputIndex];
66-
assert(out);
67-
const addressFromOutput = address.fromOutputScript(out.script, psbt.network);
68-
const addressFromProof = extractAddressFromPayGoAttestationProof(message, addressFromOutput.length);
69-
70-
if (addressFromProof !== addressFromOutput) {
71-
throw new Error(`The address from the output (${addressFromOutput}) does not match the address that is in the proof (${addressFromProof}).`)
66+
if (Buffer.compare(addressFromProof, Buffer.from(messageToVerify)) !== 0) {
67+
throw new Error(
68+
`The address from the output (${messageToVerify}) does not match the address that is in the proof (${addressFromProof}).`
69+
);
7270
}
7371
}
7472

@@ -79,19 +77,23 @@ export function verifyPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, pub
7977
* @param psbt
8078
* @returns number - the index of the output address
8179
*/
82-
export function getPaygoAddressProofOutputIndex(psbt: UtxoPsbt): number | undefined {
80+
export function getPaygoAddressProofOutputIndex(psbt: bitgo.UtxoPsbt): number | undefined {
8381
const res = psbt.data.outputs.flatMap((output, outputIndex) => {
84-
const proprietaryKeyVals = getPsbtOutputProprietaryKeyVals(output, {
85-
identifier: PSBT_PROPRIETARY_IDENTIFIER,
86-
subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF,
82+
const proprietaryKeyVals = bitgo.getPsbtOutputProprietaryKeyVals(output, {
83+
identifier: bitgo.PSBT_PROPRIETARY_IDENTIFIER,
84+
subtype: bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF,
8785
});
8886

87+
if (proprietaryKeyVals.length > 1) {
88+
throw new Error(`There are multiple PayGo addresses in the PSBT output ${outputIndex}.`);
89+
}
90+
8991
return proprietaryKeyVals.length === 0 ? [] : [outputIndex];
9092
});
9193

9294
return res.length === 0 ? undefined : res[0];
9395
}
9496

95-
export function psbtOutputIncludesPaygoAddressProof(psbt: UtxoPsbt): boolean {
97+
export function psbtOutputIncludesPaygoAddressProof(psbt: bitgo.UtxoPsbt): boolean {
9698
return getPaygoAddressProofOutputIndex(psbt) !== undefined;
9799
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './PayGoUtils';
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import assert from 'assert';
2+
3+
import * as bitcoinMessage from 'bitcoinjs-message';
4+
import { decodeProprietaryKey } from 'bip174/src/lib/proprietaryKeyVal';
5+
import { KeyValue } from 'bip174/src/lib/interfaces';
6+
import { checkForOutput } from 'bip174/src/lib/utils';
7+
import { bitgo, networks, testutil, bip32, crypto, address } from '@bitgo/utxo-lib';
8+
9+
import {
10+
addPaygoAddressProof,
11+
getPaygoAddressProofOutputIndex,
12+
psbtOutputIncludesPaygoAddressProof,
13+
verifyPaygoAddressProof,
14+
} from '../../../src/paygo/psbt/PayGoUtils';
15+
import { generatePayGoAttestationProof } from '../../../src/testutil/generatePayGoAttestationProof.utils';
16+
17+
// To construct our PSBTs
18+
const network = networks.bitcoin;
19+
const keys = [1, 2, 3].map((v) => bip32.fromSeed(Buffer.alloc(16, `test/2/${v}`), network));
20+
const rootWalletKeys = new bitgo.RootWalletKeys([keys[0], keys[1], keys[2]]);
21+
const psbtInputs = testutil.inputScriptTypes.map((scriptType) => ({ scriptType, value: BigInt(1000) }));
22+
const psbtOutputs = testutil.outputScriptTypes.map((scriptType) => ({ scriptType, value: BigInt(900) }));
23+
const dummyPub1 = rootWalletKeys.deriveForChainAndIndex(50, 200);
24+
// wallet pub and priv key for tbtc
25+
const attestationPubKey = dummyPub1.user.publicKey;
26+
const attestationPrvKey = dummyPub1.user.privateKey!;
27+
// const attestationPubKey =
28+
// 'xpub661MyMwAqRbcFU2Qx7pvGmmiQpVj8NcR7dSVpgqNChMkQyobpVWWERcrTb47WicmXwkhAY2VrC3hb29s18FDQWJf5pLm3saN6uLXAXpw1GV';
29+
// const attestationPrvKey = '3FlzrW1WuPbab2GWGyx+k/pHUNefDlw3SV0NQHLrWA+YEzqbvEQmosFGXMslYqtgpeIy6HiEoEvKzbNKM7yGY8GQHv7E++/sQRFDprOyklaW7GVyC2yEZe/LdaEfBvxf2VHBmu2hYubjsdHYF5+RQ3FhnyaNT+0=';
30+
// const keypair = ECPair.fromPrivateKey(Buffer.from(attestationPrvKey));
31+
32+
// UUID structure
33+
const nilUUID = '00000000-0000-0000-0000-000000000000';
34+
35+
// our xpub converted to base58 address
36+
const addressFromPubkey = address.toBase58Check(
37+
crypto.hash160(Buffer.from(attestationPubKey)),
38+
networks.bitcoin.pubKeyHash,
39+
networks.bitcoin
40+
);
41+
// this should be retuning a Buffer
42+
const addressProofBuffer = generatePayGoAttestationProof(nilUUID, Buffer.from(addressFromPubkey));
43+
// signature with the given msg addressProofBuffer
44+
// console.log(attestationPrvKey)
45+
const sig = bitcoinMessage.sign(addressProofBuffer, attestationPrvKey!);
46+
47+
function getTestPsbt() {
48+
return testutil.constructPsbt(psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned');
49+
}
50+
51+
describe('addPaygoAddressProof and verifyPaygoAddressProof', () => {
52+
function getPaygoProprietaryKey(proprietaryKeyVals: KeyValue[]) {
53+
return proprietaryKeyVals
54+
.map(({ key, value }) => {
55+
return { key: decodeProprietaryKey(key), value };
56+
})
57+
.filter((keyValue) => {
58+
return (
59+
keyValue.key.identifier === bitgo.PSBT_PROPRIETARY_IDENTIFIER &&
60+
keyValue.key.subtype === bitgo.ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF
61+
);
62+
});
63+
}
64+
65+
it("should fail a proof verification if the proof isn't valid", () => {
66+
const outputIndex = 0;
67+
const psbt = getTestPsbt();
68+
addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey));
69+
const output = checkForOutput(psbt.data.outputs, outputIndex);
70+
const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!);
71+
assert(proofInPsbt.length === 1);
72+
assert.throws(
73+
() => verifyPaygoAddressProof(psbt, 0, Buffer.from('Random Signed Message')),
74+
(e: any) => e.message === 'Cannot verify the paygo address signature with the provided pubkey.'
75+
);
76+
});
77+
78+
it('should add and verify a valid paygo address proof on the PSBT', () => {
79+
const outputIndex = 0;
80+
const psbt = getTestPsbt();
81+
addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey));
82+
verifyPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer));
83+
});
84+
85+
it('should throw an error if there are multiple PayGo proprietary keys in the PSBT', () => {
86+
const outputIndex = 0;
87+
const psbt = getTestPsbt();
88+
addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey));
89+
addPaygoAddressProof(psbt, outputIndex, Buffer.from('signature2'), Buffer.from('fakepubkey2s'));
90+
const output = checkForOutput(psbt.data.outputs, outputIndex);
91+
const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!);
92+
assert(proofInPsbt.length !== 0);
93+
assert(proofInPsbt.length > 1);
94+
assert.throws(
95+
() => verifyPaygoAddressProof(psbt, outputIndex, addressProofBuffer),
96+
(e: any) => e.message === 'There are multiple paygo address proofs encoded in the PSBT. Something went wrong.'
97+
);
98+
});
99+
});
100+
101+
describe('verifyPaygoAddressProof', () => {
102+
it('should throw an error if there is no PayGo address in PSBT', () => {
103+
const psbt = getTestPsbt();
104+
assert.throws(
105+
() => verifyPaygoAddressProof(psbt, 0, addressProofBuffer),
106+
(e: any) => e.message === 'There is no paygo address proof encoded in the PSBT at output 0.'
107+
);
108+
});
109+
});
110+
111+
describe('getPaygoAddressProofIndex', () => {
112+
it('should get PayGo address proof index from PSBT if there is one', () => {
113+
const psbt = getTestPsbt();
114+
const outputIndex = 0;
115+
addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey));
116+
assert(psbtOutputIncludesPaygoAddressProof(psbt));
117+
assert(getPaygoAddressProofOutputIndex(psbt) === 0);
118+
});
119+
120+
it('should return undefined if there is no PayGo address proof in PSBT', () => {
121+
const psbt = getTestPsbt();
122+
assert(getPaygoAddressProofOutputIndex(psbt) === undefined);
123+
assert(!psbtOutputIncludesPaygoAddressProof(psbt));
124+
});
125+
126+
it('should return an error and fail if we have multiple PayGo address in the PSBT in the same output index', () => {
127+
const psbt = getTestPsbt();
128+
const outputIndex = 0;
129+
addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from(attestationPubKey));
130+
addPaygoAddressProof(psbt, outputIndex, sig, Buffer.from('xpub12345abcdef29a028510d3b2d4'));
131+
assert.throws(
132+
() => getPaygoAddressProofOutputIndex(psbt),
133+
(e: any) => e.message === 'There are multiple PayGo addresses in the PSBT output 0.'
134+
);
135+
});
136+
});

modules/utxo-lib/src/testutil/psbt.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -261,26 +261,3 @@ export function verifyFullySignedSignatures(
261261
}
262262
});
263263
}
264-
265-
/** We generate the PayGo attestation proof based on the private key, UUID, and address of our PayGo.
266-
* We create a random entropy of 64 bytes encoded to base58 and used to create the attestation proof
267-
* in the format 0x18Bitcoin Signed Message:\n<varint_length><ENTROPY><ADDRESS><UUID>
268-
*
269-
* @param attestationPrvKey
270-
* @param uuid
271-
* @param address
272-
*/
273-
export function generatePayGoAttestationProof(attestationPrvKey: Buffer, uuid: string, address: Buffer): Buffer {
274-
// This is our prefix to our bitcoin signed message
275-
const prefixByte = Buffer.from([0x18]);
276-
const signedMessagePrefix = Buffer.from('Bitcoin Signed Message:\n', 'utf8');
277-
// We always create a 32 byte buffer array but we can implement a random
278-
// function to generate between two ranges of our length of entropy
279-
const entropy = Buffer.allocUnsafe(32);
280-
crypto.getRandomValues(entropy);
281-
const uuidToBuffer = Buffer.from(uuid, 'hex');
282-
const signedMessageBufferRaw = Buffer.concat([prefixByte, signedMessagePrefix, entropy, address, uuidToBuffer]);
283-
const varInt = Buffer.from([signedMessageBufferRaw.length]);
284-
const fullResMessageBuffer = Buffer.concat([prefixByte, signedMessagePrefix, varInt, entropy, address, uuidToBuffer])l
285-
286-
}

modules/utxo-lib/test/bitgo/psbt/paygoAddressProof.ts

Lines changed: 0 additions & 100 deletions
This file was deleted.

0 commit comments

Comments
 (0)