Skip to content

Commit 02a4f2a

Browse files
Merge pull request #6210 from BitGo/BTC-2150.extractAddressFromPaygoAttestationProof
2 parents 43be408 + 2cda75e commit 02a4f2a

File tree

7 files changed

+136
-0
lines changed

7 files changed

+136
-0
lines changed

modules/utxo-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * as bip65 from './bip65';
22
export * as descriptor from './descriptor';
33
export * as testutil from './testutil';
4+
export * as paygo from './paygo';
45
export * from './dustThreshold';
56
export * from './Output';
67
export * from './xOnlyPubkey';
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import assert from 'assert';
2+
3+
import { bufferutils } from '@bitgo/utxo-lib';
4+
5+
// The signed address will always have the following structure:
6+
// 0x18Bitcoin Signed Message:\n<varint_length><ENTROPY><ADDRESS><UUID>
7+
8+
const PrefixLength = Buffer.from([0x18]).length + Buffer.from('Bitcoin Signed Message:\n').length;
9+
// UUID has the structure 00000000-0000-0000-0000-000000000000, and after
10+
// we Buffer.from and get it's length its 36.
11+
const UuidBufferLength = 36;
12+
// The entropy will always be 64 bytes
13+
const EntropyLen = 64;
14+
15+
/**
16+
* This function takes in the attestation proof of a PayGo address of the from
17+
* 0x18Bitcoin Signed Message:\n<varint_length><ENTROPY><ADDRESS><UUID> and returns
18+
* the address given its length. It is assumed that the ENTROPY is 64 bytes in the Buffer
19+
* so if not given an address proof length we can still extract the address from the proof.
20+
*
21+
* @param message
22+
* @param adressProofLength
23+
*/
24+
export function extractAddressBufferFromPayGoAttestationProof(message: Buffer): Buffer {
25+
if (message.length <= PrefixLength + EntropyLen + UuidBufferLength) {
26+
throw new Error('PayGo attestation proof is too short to contain a valid address');
27+
}
28+
29+
// This generates the first part before the varint length so that we can
30+
// determine how many bytes this is and iterate through the Buffer.
31+
let offset = PrefixLength;
32+
33+
// we decode the varint of the message which is uint32
34+
// https://en.bitcoin.it/wiki/Protocol_documentation
35+
const varInt = bufferutils.varuint.decode(message, offset);
36+
assert(varInt);
37+
offset += 1;
38+
39+
const addressLength = varInt - EntropyLen - UuidBufferLength;
40+
offset += EntropyLen;
41+
42+
// we return what the Buffer subarray from the offset (beginning of address)
43+
// to the end of the address index in the buffer.
44+
return message.subarray(offset, offset + addressLength);
45+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ExtractAddressPayGoAttestation';
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import crypto from 'crypto';
2+
3+
import { bufferutils } from '@bitgo/utxo-lib';
4+
/** We have a mirrored function similar to our hsm that generates our Bitcoin signed
5+
* message so that we can use for testing. This creates a random entropy as well using
6+
* the nilUUID structure to construct our uuid buffer and given our address we can
7+
* directly encode it into our message.
8+
*
9+
* @param attestationPrvKey
10+
* @param uuid
11+
* @param address
12+
* @returns
13+
*/
14+
export function generatePayGoAttestationProof(uuid: string, address: Buffer): Buffer {
15+
// <0x18Bitcoin Signed Message:\n
16+
const prefixByte = Buffer.from([0x18]);
17+
const prefixMessage = Buffer.from('Bitcoin Signed Message:\n');
18+
const prefixBuffer = Buffer.concat([prefixByte, prefixMessage]);
19+
20+
// <ENTROPY>
21+
const entropyLength = 64;
22+
const entropy = crypto.randomBytes(entropyLength);
23+
24+
// <UUID>
25+
const uuidBuffer = Buffer.from(uuid);
26+
const uuidBufferLength = uuidBuffer.length;
27+
28+
// <ADDRESS>
29+
const addressBufferLength = address.length;
30+
31+
// <VARINT_LENGTH>
32+
const msgLength = entropyLength + addressBufferLength + uuidBufferLength;
33+
const msgLengthBuffer = bufferutils.varuint.encode(msgLength);
34+
35+
// <0x18Bitcoin Signed Message:\n<LENGTH><ENTROPY><ADDRESS><UUID>
36+
const proofMessage = Buffer.concat([prefixBuffer, msgLengthBuffer, entropy, address, uuidBuffer]);
37+
38+
// we sign this with the priv key
39+
// don't know what sign function to call. Since this is just a mirrored function don't know if we need
40+
// to include this part.
41+
// const signedMsg = sign(attestationPrvKey, proofMessage);
42+
return proofMessage;
43+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './fixtures.utils';
22
export * from './key.utils';
33
export * from './toPlainObject.utils';
4+
export * from './generatePayGoAttestationProof.utils';
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as assert from 'assert';
2+
3+
import { extractAddressBufferFromPayGoAttestationProof } from '../../src/paygo';
4+
import { generatePayGoAttestationProof } from '../../src/testutil';
5+
6+
const addressFromPubKeyBase58 = 'bitgoAddressToExtract';
7+
const bufferAddressPubKeyB58 = Buffer.from(addressFromPubKeyBase58);
8+
9+
describe('extractAddressBufferFromPayGoAttestationProof', () => {
10+
it('should extractAddressBufferFromPayGoAttestationProof properly', () => {
11+
const paygoAttestationProof = generatePayGoAttestationProof(
12+
'00000000-0000-0000-0000-000000000000',
13+
bufferAddressPubKeyB58
14+
);
15+
const addressFromProof = extractAddressBufferFromPayGoAttestationProof(paygoAttestationProof);
16+
assert.deepStrictEqual(Buffer.compare(addressFromProof, bufferAddressPubKeyB58), 0);
17+
});
18+
19+
it('should extract the paygo address paygo attestation proof given a non nilUUID', () => {
20+
const paygoAttestationProof = generatePayGoAttestationProof(
21+
'12345678-1234-4567-6890-231928472123',
22+
bufferAddressPubKeyB58
23+
);
24+
const addressFromProof = extractAddressBufferFromPayGoAttestationProof(paygoAttestationProof);
25+
assert.deepStrictEqual(Buffer.compare(addressFromProof, bufferAddressPubKeyB58), 0);
26+
});
27+
28+
it('should not extract the correct address given a uuid of wrong format', () => {
29+
const paygoAttestationProof = generatePayGoAttestationProof(
30+
'000000000000000-000000-0000000-000000-0000000000000000',
31+
bufferAddressPubKeyB58
32+
);
33+
const addressFromProof = extractAddressBufferFromPayGoAttestationProof(paygoAttestationProof);
34+
assert.notDeepStrictEqual(Buffer.compare(addressFromProof, bufferAddressPubKeyB58), 0);
35+
});
36+
37+
it('should throw an error if the paygo attestation proof is too short', () => {
38+
assert.throws(
39+
() => extractAddressBufferFromPayGoAttestationProof(Buffer.from('shortproof-shrug')),
40+
'PayGo attestation proof is too short to contain a valid address.'
41+
);
42+
});
43+
});

modules/utxo-lib/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export * from 'bitcoinjs-lib';
22

3+
export * as bufferutils from 'bitcoinjs-lib/src/bufferutils';
4+
35
export * as bitgo from './bitgo';
46

57
export * as address from './address';

0 commit comments

Comments
 (0)