Skip to content

Commit 1b3d475

Browse files
chore: wip added functions and tests
TICKET: BTC-2047
1 parent 4f6021d commit 1b3d475

File tree

5 files changed

+75
-30
lines changed

5 files changed

+75
-30
lines changed

modules/utxo-lib/src/bitgo/PsbtUtil.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export enum ProprietaryKeySubtype {
1515
MUSIG2_PARTICIPANT_PUB_KEYS = 0x01,
1616
MUSIG2_PUB_NONCE = 0x02,
1717
MUSIG2_PARTIAL_SIG = 0x03,
18-
PAYGO_ADDRESS_PROOF = 0x04,
18+
PAYGO_ADDRESS_ATTESTATION_PROOF = 0x04,
1919
}
2020

2121
/**

modules/utxo-lib/src/bitgo/UtxoPsbt.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,8 @@ export class UtxoPsbt<Tx extends UtxoTransaction<bigint> = UtxoTransaction<bigin
11481148
* Default identifier is utf-8 for identifier
11491149
*/
11501150
addProprietaryKeyValToOutput(outputIndex: number, keyValueData: ProprietaryKeyValue): this {
1151+
const output = checkForOutput(this.data.outputs, outputIndex);
1152+
assert(output.unknownKeyVals);
11511153
return this.addUnknownKeyValToOutput(outputIndex, {
11521154
key: encodeProprietaryKey(keyValueData.key),
11531155
value: keyValueData.value

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

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
import * as assert from 'assert';
12
import * as bitcoinMessage from 'bitcoinjs-message';
23
import { crypto } from 'bitcoinjs-lib';
34

5+
import { address } from '../..';
46
import { networks } from '../../networks';
57
import { toBase58Check } from '../../address';
68
import { getPsbtOutputProprietaryKeyVals, ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER } from '../PsbtUtil';
79
import { UtxoPsbt } from '../UtxoPsbt';
810

9-
/**
11+
/** The function consumes the signature as a parameter and adds the PayGo address to the
12+
* PSBT output at the output index where the signature is of the format:
13+
* 0x18Bitcoin Signed Message:\n<varint_length><ENTROPY><ADDRESS><UUID> signed by
14+
* the HSM beforehand.
1015
*
1116
* @param psbt - PSBT that we need to encode our paygo address into
1217
* @param outputIndex - the index of the address in our output
@@ -16,7 +21,7 @@ export function addPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, sig: B
1621
psbt.addProprietaryKeyValToOutput(outputIndex, {
1722
key: {
1823
identifier: PSBT_PROPRIETARY_IDENTIFIER,
19-
subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_PROOF,
24+
subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF,
2025
keydata: Buffer.from(pub)
2126
},
2227
value: sig,
@@ -33,29 +38,38 @@ export function addPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, sig: B
3338
export function verifyPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, pub: Buffer): void {
3439
const stored = psbt.getOutputProprietaryKeyVals(outputIndex, {
3540
identifier: PSBT_PROPRIETARY_IDENTIFIER,
36-
subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_PROOF,
41+
subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF,
3742
});
3843
if (!stored) {
3944
throw new Error('No address proof');
4045
}
4146

4247
// assert stored length is 0 or 1
4348
if (stored.length === 0) {
44-
throw new Error('There is no paygo address proof encoded in the PSBT.');
49+
throw new Error(`There is no paygo address proof encoded in the PSBT at output ${outputIndex}.`);
4550
} else if (stored.length > 1) {
4651
throw new Error('There are multiple paygo address proofs encoded in the PSBT. Something went wrong.');
4752
}
4853

4954
const signature = stored[0].value;
5055
// It doesn't matter that this is bitcoin or not, we just need to convert the public key buffer into an address format
5156
// for the verification
52-
const verifyingAddress = toBase58Check(crypto.hash160(pub), networks.bitcoin.pubKeyHash, networks.bitcoin);
57+
const messageToVerify = toBase58Check(crypto.hash160(pub), networks.bitcoin.pubKeyHash, networks.bitcoin);
5358

5459
// TODO: need to figure out what the message is in this context
5560
// Are we verifying the address of the PayGo? we can call getAddressFromScript given output index.
56-
if (!bitcoinMessage.verify(message, verifyingAddress, signature)) {
61+
if (!bitcoinMessage.verify(message, messageToVerify, signature)) {
5762
throw new Error('Cannot verify the paygo address signature with the provided pubkey.');
5863
}
64+
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}).`)
72+
}
5973
}
6074

6175
/** Get the output index of the paygo output if there is one. It does this by
@@ -65,11 +79,11 @@ export function verifyPaygoAddressProof(psbt: UtxoPsbt, outputIndex: number, pub
6579
* @param psbt
6680
* @returns number - the index of the output address
6781
*/
68-
export function getPaygoAddressProofIndex(psbt: UtxoPsbt): number | undefined {
82+
export function getPaygoAddressProofOutputIndex(psbt: UtxoPsbt): number | undefined {
6983
const res = psbt.data.outputs.flatMap((output, outputIndex) => {
7084
const proprietaryKeyVals = getPsbtOutputProprietaryKeyVals(output, {
7185
identifier: PSBT_PROPRIETARY_IDENTIFIER,
72-
subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_PROOF,
86+
subtype: ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF,
7387
});
7488

7589
return proprietaryKeyVals.length === 0 ? [] : [outputIndex];
@@ -78,6 +92,6 @@ export function getPaygoAddressProofIndex(psbt: UtxoPsbt): number | undefined {
7892
return res.length === 0 ? undefined : res[0];
7993
}
8094

81-
export function psbtIncludesPaygoAddressProof(psbt: UtxoPsbt): boolean {
82-
return getPaygoAddressProofIndex(psbt) !== undefined;
95+
export function psbtOutputIncludesPaygoAddressProof(psbt: UtxoPsbt): boolean {
96+
return getPaygoAddressProofOutputIndex(psbt) !== undefined;
8397
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,26 @@ 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: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,30 @@ import { KeyValue } from 'bip174/src/lib/interfaces';
44
import { checkForOutput } from 'bip174/src/lib/utils';
55

66
import { bip32, networks, testutil } from '../../../src'
7-
import { addPaygoAddressProof, verifyPaygoAddressProof, getPaygoAddressProofIndex, psbtIncludesPaygoAddressProof } from "../../../src/bitgo/psbt/paygoAddressProof";
8-
import { inputScriptTypes, outputScriptTypes } from '../../../src/testutil';
7+
import { addPaygoAddressProof, verifyPaygoAddressProof, getPaygoAddressProofOutputIndex, psbtOutputIncludesPaygoAddressProof } from "../../../src/bitgo/psbt/paygoAddressProof";
8+
import { generatePayGoAttestationProof, inputScriptTypes, outputScriptTypes } from '../../../src/testutil';
99
import { ProprietaryKeySubtype, PSBT_PROPRIETARY_IDENTIFIER, RootWalletKeys } from '../../../src/bitgo';
1010

1111

1212
const network = networks.bitcoin;
1313
const keys = [1,2,3].map((v) => bip32.fromSeed(Buffer.alloc(16, `test/2/${v}`), network))
1414
const rootWalletKeys = new RootWalletKeys([keys[0], keys[1], keys[2]])
15-
const dummyKey1 = rootWalletKeys.deriveForChainAndIndex(50, 200);
15+
// const dummyKey1 = rootWalletKeys.deriveForChainAndIndex(50, 200);
1616
const dummyKey2 = rootWalletKeys.deriveForChainAndIndex(60, 201);
17-
const dumm1yXPubs = dummyKey1.publicKeys;
18-
const dummy1PubKey = dummyKey1.user.publicKey;
1917

2018
const psbtInputs = inputScriptTypes.map((scriptType) => ({scriptType, value: BigInt(1000)}))
2119
const psbtOutputs = outputScriptTypes.map((scriptType) => ({ scriptType, value: BigInt(900)}))
22-
const sig = dummyKey1.user.privateKey!;
20+
// const dummy1PubKey = dummyKey1.user.publicKey;
21+
// This generatePayGoAttestationProof function should be returning the bitcoin signed message
2322
const sig2 = dummyKey2.user.privateKey!;
2423

24+
// wallet pub and priv key for tbtc
25+
const attestationPubKey = "xpub661MyMwAqRbcFU2Qx7pvGmmiQpVj8NcR7dSVpgqNChMkQyobpVWWERcrTb47WicmXwkhAY2VrC3hb29s18FDQWJf5pLm3saN6uLXAXpw1GV";
26+
const attestationPrvKey = "red";
27+
const nilUUID = '00000000-0000-0000-0000-000000000000';
28+
const addressProofBuffer = generatePayGoAttestationProof(Buffer.from(attestationPrvKey), nilUUID, Buffer.from(address))
29+
30+
2531
function getTestPsbt() {
2632
return testutil.constructPsbt(
2733
psbtInputs, psbtOutputs, network, rootWalletKeys, 'unsigned'
@@ -33,14 +39,14 @@ describe('addPaygoAddressProof and verifyPaygoAddressProof', () => {
3339
return proprietaryKeyVals.map(({key, value}) => {
3440
return { key: decodeProprietaryKey(key), value };
3541
}).filter((keyValue) => {
36-
return keyValue.key.identifier === PSBT_PROPRIETARY_IDENTIFIER && keyValue.key.subtype === ProprietaryKeySubtype.PAYGO_ADDRESS_PROOF
42+
return keyValue.key.identifier === PSBT_PROPRIETARY_IDENTIFIER && keyValue.key.subtype === ProprietaryKeySubtype.PAYGO_ADDRESS_ATTESTATION_PROOF
3743
});
3844
}
3945

4046
it("should fail a proof verification if the proof isn't valid", () => {
4147
const outputIndex = 0;
4248
const psbt = getTestPsbt();
43-
addPaygoAddressProof(psbt, outputIndex, Buffer.from(sig), dummy1PubKey);
49+
addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey));
4450
const output = checkForOutput(psbt.data.outputs, outputIndex);
4551
const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!);
4652
assert(proofInPsbt.length === 1)
@@ -50,45 +56,45 @@ describe('addPaygoAddressProof and verifyPaygoAddressProof', () => {
5056
it("should add and verify a valid paygo address proof on the PSBT", () => {
5157
const outputIndex = 0;
5258
const psbt = getTestPsbt();
53-
addPaygoAddressProof(psbt, outputIndex, Buffer.from(sig), dummy1PubKey);
59+
addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey));
5460
// should verify function return a boolean? that way we can assert
5561
// if this is verified, throws an error otherwise or false + error msg as an object
56-
verifyPaygoAddressProof(psbt, outputIndex, dummy1PubKey);
62+
verifyPaygoAddressProof(psbt, outputIndex, Buffer.from(attestationPubKey));
5763
});
5864

5965
it("should throw an error if there are multiple PayGo proprietary keys in the PSBT", () => {
6066
const outputIndex = 0;
6167
const psbt = getTestPsbt();
62-
addPaygoAddressProof(psbt, outputIndex, Buffer.from(sig), dummy1PubKey);
63-
addPaygoAddressProof(psbt, outputIndex, Buffer.from(sig2), dummy1PubKey);
68+
addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey));
69+
addPaygoAddressProof(psbt, outputIndex, Buffer.from(sig2), Buffer.from(attestationPubKey));
6470
const output = checkForOutput(psbt.data.outputs, outputIndex);
6571
const proofInPsbt = getPaygoProprietaryKey(output.unknownKeyVals!);
6672
assert(proofInPsbt.length !== 0)
6773
assert(proofInPsbt.length <= 1)
68-
assert.throws(() => verifyPaygoAddressProof(psbt, outputIndex, dummy1PubKey), (e: any) => e.message === 'There are multiple paygo address proofs encoded in the PSBT. Something went wrong.');
74+
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.');
6975
});
7076
});
7177

7278

7379
describe('verifyPaygoAddressProof', () => {
7480
it('should throw an error if there is no PayGo address in PSBT', () => {
7581
const psbt = getTestPsbt();
76-
assert.throws(() => verifyPaygoAddressProof(psbt, 0, dummy1PubKey), (e: any) => e.message === 'here is no paygo address proof encoded in the PSBT.');
82+
assert.throws(() => verifyPaygoAddressProof(psbt, 0, Buffer.from(attestationPubKey)), (e: any) => e.message === 'here is no paygo address proof encoded in the PSBT.');
7783
});
7884
});
7985

8086
describe('getPaygoAddressProofIndex', () => {
8187
it('should get PayGo address proof index from PSBT if there is one', () => {
8288
const psbt = getTestPsbt();
8389
const outputIndex = 0;
84-
addPaygoAddressProof(psbt, outputIndex, Buffer.from(sig), dummy1PubKey);
85-
assert(psbtIncludesPaygoAddressProof(psbt));
86-
assert(getPaygoAddressProofIndex(psbt) === 1)
90+
addPaygoAddressProof(psbt, outputIndex, Buffer.from(addressProofBuffer), Buffer.from(attestationPubKey));
91+
assert(psbtOutputIncludesPaygoAddressProof(psbt));
92+
assert(getPaygoAddressProofOutputIndex(psbt) === 0)
8793
});
8894

8995
it("should return undefined if there is no PayGo address proof in PSBT", () => {
9096
const psbt = getTestPsbt();
91-
assert(getPaygoAddressProofIndex(psbt) === undefined)
92-
assert(!psbtIncludesPaygoAddressProof(psbt))
97+
assert(getPaygoAddressProofOutputIndex(psbt) === undefined)
98+
assert(!psbtOutputIncludesPaygoAddressProof(psbt))
9399
});
94100
});

0 commit comments

Comments
 (0)