Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: modify uint8array comparing functions #14

Merged
merged 6 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "dlc-btc-lib",
"version": "1.0.17",
"version": "1.0.18",
"description": "This library provides a comprehensive set of interfaces and functions for minting dlcBTC tokens on supported blockchains.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
50 changes: 10 additions & 40 deletions src/functions/bitcoin/bitcoin-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ import {
PaymentTypes,
UTXO,
} from '../../models/bitcoin-models.js';
import { createRangeFromLength, isDefined, isUndefined } from '../../utilities/index.js';
import {
compareUint8Arrays,
createRangeFromLength,
isDefined,
isUndefined,
} from '../../utilities/index.js';

const TAPROOT_UNSPENDABLE_KEY_HEX =
'0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0';
Expand Down Expand Up @@ -428,54 +433,19 @@ export function getValueMatchingOutputFromTransaction(
return valueMatchingTransactionOutput;
}

export function validateScript(script: Uint8Array, outputScript: Uint8Array): boolean {
return (
outputScript.length === script.length &&
outputScript.every((value, index) => value === script[index])
);
}

export function getInputIndicesByScript(script: Uint8Array, transaction: Transaction): number[] {
const inputIndices: number[] = [];

createRangeFromLength(transaction.inputsLength).forEach(index => {
return createRangeFromLength(transaction.inputsLength).flatMap(index => {
const inputScript = transaction.getInput(index).witnessUtxo?.script;

if (!inputScript) {
throw new Error('Could not get Input Script');
}

if (
inputScript.length === script.length &&
inputScript.every((value, index) => value === script[index])
) {
inputIndices.push(index);
}
return inputScript && compareUint8Arrays(inputScript, script) ? [index] : [];
});
return inputIndices;
}

export function finalizeUserInputs(
transaction: Transaction,
userPayment: P2TROut | P2Ret
): Transaction {
const userPaymentScript = userPayment.script;
export function finalizeUserInputs(transaction: Transaction, userPayment: P2TROut | P2Ret): void {
createRangeFromLength(transaction.inputsLength).forEach(index => {
const inputScript = transaction.getInput(index).witnessUtxo?.script;

if (!inputScript) {
throw new Error('Could not get Input Script');
}

if (
inputScript.length === userPaymentScript.length &&
inputScript.every((value, index) => value === userPaymentScript[index])
) {
if (inputScript && compareUint8Arrays(inputScript, userPayment.script))
transaction.finalizeIdx(index);
}
});

return transaction;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/proof-of-reserve-handlers/proof-of-reserve-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import {
deriveUnhardenedPublicKey,
getUnspendableKeyCommittedToUUID,
getValueMatchingOutputFromTransaction,
validateScript,
} from '../functions/bitcoin/bitcoin-functions.js';
import {
checkBitcoinTransactionConfirmations,
fetchBitcoinBlockchainBlockHeight,
fetchBitcoinTransaction,
} from '../functions/bitcoin/bitcoin-request-functions.js';
import { RawVault } from '../models/ethereum-models.js';
import { compareUint8Arrays } from '../utilities/index.js';

export class ProofOfReserveHandler {
private bitcoinBlockchainAPI: string;
Expand Down Expand Up @@ -65,7 +65,7 @@ export class ProofOfReserveHandler {
this.bitcoinNetwork
);

return validateScript(
return compareUint8Arrays(
taprootMultisigPayment.script,
hex.decode(vaultTransactionOutput.scriptpubkey)
);
Expand Down
4 changes: 4 additions & 0 deletions src/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export function isDefined<T>(argument: T | undefined): argument is T {
return !isUndefined(argument);
}

export function compareUint8Arrays(a: Uint8Array, b: Uint8Array): boolean {
return a.length === b.length && a.every((value, index) => value === b[index]);
}

export async function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Expand Down
27 changes: 27 additions & 0 deletions tests/mocks/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,30 @@ export const TEST_REGTEST_ATTESTOR_APIS = [
];
export const TEST_REGTEST_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY =
'tpubDDqN2CmTDKaGeqXMayfCZEvjZqntifi4r1ztmRWsGuE1VE4bosR3mBKQwVaCxZcmg8R1nHDMDzDmzjoccBMgwZV1hhz51tAXVnhjABCQcwA';

export const TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_1 =
'70736274ff0100c5020000000269680a0f87525a598abaf823342a4cb6d632027252e51d242e51c61770f17c990200000000f0ffffff751757d16e395a1da0457531b8a9eed579bdd87f3d60034b179976be5fb5178a0100000000ffffffff0370170000000000001600145a81f36535980769bccc23c196337a78458abd1e80969800000000002251203789ed985ab9d1c94f5889973376a88d9940835206461151685a7a3fbb84de599ec6202901000000160014add70fb03578d3ac85aab80897395bb046223c92000000000001011f302b6d2901000000160014add70fb03578d3ac85aab80897395bb046223c92220202f4d8696f9b275f4e10af63f03bcb7fbba7b1ed44dd9b12a973c8d20212beb8d1483045022100d7788684a0d1f05f35a861f72457a8d1aa7efbfdf1227d3e6cdf72b7feab9f7e02203d57106adbaf190731c0447e990833835c6de86052161e39b06193ad306e80a1010001012b404b4c00000000002251203789ed985ab9d1c94f5889973376a88d9940835206461151685a7a3fbb84de594114bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfce24db130ab82d20d322c295aa18921a7fe87fb13df3d5b3b2ee0469e2a84511440d8fb920db7745d01f2762b6285e10a3d16ef056fd1dc26879eea5dc5410d4da53a458820088e5a22c11ef27527a8db546a0e8997d80b4b3c4f861a6b4410bd482215c1e52a5f154612da28faade658332157966492aa20a523162a8b584682a23dc72645205216652630455eb8546bbab45e6fdef6498eee683ed339f405d326414b05b192ad20bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfcacc0011720e52a5f154612da28faade658332157966492aa20a523162a8b584682a23dc726011820e24db130ab82d20d322c295aa18921a7fe87fb13df3d5b3b2ee0469e2a84511400000000';

export const TEST_WITHDRAW_PSBT_PARTIALLY_SIGNED_WITHDRAW_PSBT_1 =
'70736274ff01009c0200000001acf3a308a937ebcc2464fa33f99f1197145b49a7124a72f5511666649d3236030000000000f0ffffff034c1d0000000000001600145a81f36535980769bccc23c196337a78458abd1e404b4c00000000002251203789ed985ab9d1c94f5889973376a88d9940835206461151685a7a3fbb84de599a2c4c0000000000160014add70fb03578d3ac85aab80897395bb046223c92000000000001012b80969800000000002251203789ed985ab9d1c94f5889973376a88d9940835206461151685a7a3fbb84de594114bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfce24db130ab82d20d322c295aa18921a7fe87fb13df3d5b3b2ee0469e2a84511440525267fb9b7ea3797aad490251906662f24599c086733a95f33aa1c25c6e47d408d58c8be7a56edb491d56741744e85b746366f9175754f053a7703e69f6ab392215c1e52a5f154612da28faade658332157966492aa20a523162a8b584682a23dc72645205216652630455eb8546bbab45e6fdef6498eee683ed339f405d326414b05b192ad20bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfcacc0011720e52a5f154612da28faade658332157966492aa20a523162a8b584682a23dc726011820e24db130ab82d20d322c295aa18921a7fe87fb13df3d5b3b2ee0469e2a84511400000000';

export const TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_2 =
'70736274ff0100d1020000000266916a1f40ca9c611a60002f81854402ffb424e457c1c23931d06267cf5bc8950000000000f0ffffff8f5413dcad4cbe8876459a1b1fe1817a94206f0d0bbb79765403438c3bb07dbe0100000000ffffffff0370170000000000001600145a81f36535980769bccc23c196337a78458abd1e8096980000000000225120b033a3d9562d22aa4a5476ae9cb6a5858b0959e084b49a3fd92edfba87e8254faa59f42400000000225120676504fcaf89119cc9762c2f867aaa56aa3fffc85158f7dd61da345cdbf4a9be000000000001012b40be402500000000225120676504fcaf89119cc9762c2f867aaa56aa3fffc85158f7dd61da345cdbf4a9be011340fd74c7650cb7edb0eea5fca080ac7dea90ab23ecf616eac1ad4a263d75fcf3940d3c7c09512369cb39a473e4eb1249204df36115095dc2849556686b9087824e011720bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfc0001012b404b4c0000000000225120b033a3d9562d22aa4a5476ae9cb6a5858b0959e084b49a3fd92edfba87e8254f4114bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfce24db130ab82d20d322c295aa18921a7fe87fb13df3d5b3b2ee0469e2a845114404387a71d8174f9c42ef8ce8cbe0c88c01863fb80ea796955f6678b95a7ae7e07dd1559688bee882feefdda2e98880ea6e6ba886c438bdb2188b41f4f68d09f992215c01fba2e8661f2ed0a747d76578d3a6508a31879569d97da86a0a207c2538ebaa445205216652630455eb8546bbab45e6fdef6498eee683ed339f405d326414b05b192ad20bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfcacc00117201fba2e8661f2ed0a747d76578d3a6508a31879569d97da86a0a207c2538ebaa4011820e24db130ab82d20d322c295aa18921a7fe87fb13df3d5b3b2ee0469e2a84511400000000';

export const TEST_WITHDRAW_PSBT_PARTIALLY_SIGNED_WITHDRAW_PSBT_2 =
'70736274ff0100a80200000001b2097c129d74fc6e80d673ba3a83e4858c046444993991cad52f66d28ff65b970000000000f0ffffff034c1d0000000000001600145a81f36535980769bccc23c196337a78458abd1e404b4c0000000000225120b033a3d9562d22aa4a5476ae9cb6a5858b0959e084b49a3fd92edfba87e8254f822c4c0000000000225120676504fcaf89119cc9762c2f867aaa56aa3fffc85158f7dd61da345cdbf4a9be000000000001012b8096980000000000225120b033a3d9562d22aa4a5476ae9cb6a5858b0959e084b49a3fd92edfba87e8254f4114bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfce24db130ab82d20d322c295aa18921a7fe87fb13df3d5b3b2ee0469e2a84511440c06edfdd88bc66ffec4c8ff30fd3f6b38e25ebd85c09635a7b1594f66af614ba320c4d98e1c6017f38c8d752227ca24a5cd178cb462a4444c465fc773738e4b52215c01fba2e8661f2ed0a747d76578d3a6508a31879569d97da86a0a207c2538ebaa445205216652630455eb8546bbab45e6fdef6498eee683ed339f405d326414b05b192ad20bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfcacc00117201fba2e8661f2ed0a747d76578d3a6508a31879569d97da86a0a207c2538ebaa4011820e24db130ab82d20d322c295aa18921a7fe87fb13df3d5b3b2ee0469e2a84511400000000';

export const TEST_ALICE_NATIVE_SEGWIT_PAYMENT_SCRIPT_1 =
'0014add70fb03578d3ac85aab80897395bb046223c92';

export const TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_1 =
'02f4d8696f9b275f4e10af63f03bcb7fbba7b1ed44dd9b12a973c8d20212beb8d1';

export const TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_2 =
'0385c8f8844b7c197b96d933b01b2b82b97b9f9d55c81947e1db5cfb3de375508b';

export const TEST_ALICE_TAPROOT_PUBLIC_KEY_1 =
'03bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfc';

export const TEST_ALICE_TAPROOT_PUBLIC_KEY_2 =
'03940f5559dc92a3253e8699e9c632badfe5c2b1a13a113b85022d30cdab9c0ed8';
103 changes: 103 additions & 0 deletions tests/unit/bitcoin-functions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { hexToBytes } from '@noble/hashes/utils';
import { Transaction, p2tr, p2wpkh } from '@scure/btc-signer';
import { regtest } from 'bitcoinjs-lib/src/networks';

import {
ecdsaPublicKeyToSchnorr,
finalizeUserInputs,
getInputIndicesByScript,
} from '../../src/functions/bitcoin/bitcoin-functions';
import {
TEST_ALICE_NATIVE_SEGWIT_PAYMENT_SCRIPT_1,
TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_1,
TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_2,
TEST_ALICE_TAPROOT_PUBLIC_KEY_1,
TEST_ALICE_TAPROOT_PUBLIC_KEY_2,
TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_1,
TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_2,
TEST_WITHDRAW_PSBT_PARTIALLY_SIGNED_WITHDRAW_PSBT_1,
} from '../mocks/constants';

describe('Bitcoin Functions', () => {
describe('getInputIndicesByScript', () => {
it('correctly retrieves the input indices by script', () => {
const transaction = Transaction.fromPSBT(
hexToBytes(TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_1)
);
const aliceScript = hexToBytes(TEST_ALICE_NATIVE_SEGWIT_PAYMENT_SCRIPT_1);
const inputIndices = getInputIndicesByScript(aliceScript, transaction);
expect(inputIndices).toEqual([0]);
});

it('correctly retrieve an empty array when the script is not found', () => {
const transaction = Transaction.fromPSBT(
hexToBytes(TEST_WITHDRAW_PSBT_PARTIALLY_SIGNED_WITHDRAW_PSBT_1)
);
const aliceScript = hexToBytes(TEST_ALICE_NATIVE_SEGWIT_PAYMENT_SCRIPT_1);
const inputIndices = getInputIndicesByScript(aliceScript, transaction);
expect(inputIndices).toEqual([]);
});
});

describe('finalizeUserInputs', () => {
it('correctly finalizes inputs given a transaction and a native segwit payment script', () => {
const transaction = Transaction.fromPSBT(
hexToBytes(TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_1)
);

const alicePublicKey = Buffer.from(TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_1, 'hex');
const alicePayment = p2wpkh(alicePublicKey, regtest);

finalizeUserInputs(transaction, alicePayment);

expect(transaction.getInput(0).finalScriptWitness).toBeDefined();
expect(transaction.getInput(1).finalScriptWitness).toBeUndefined();
});

it('does not finalize inputs given a transaction and a native segwit payment script', () => {
const transaction = Transaction.fromPSBT(
hexToBytes(TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_1)
);

const alicePublicKey = Buffer.from(TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_2, 'hex');
const alicePayment = p2wpkh(alicePublicKey, regtest);

finalizeUserInputs(transaction, alicePayment);

expect(transaction.getInput(0).finalScriptWitness).toBeUndefined();
expect(transaction.getInput(1).finalScriptWitness).toBeUndefined();
});
});

it('correctly finalizes inputs given a transaction and a taproot payment script', () => {
const transaction = Transaction.fromPSBT(
hexToBytes(TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_2)
);

const alicePublicKey = ecdsaPublicKeyToSchnorr(
Buffer.from(TEST_ALICE_TAPROOT_PUBLIC_KEY_1, 'hex')
);
const alicePayment = p2tr(alicePublicKey, undefined, regtest);

finalizeUserInputs(transaction, alicePayment);

expect(transaction.getInput(0).finalScriptWitness).toBeDefined();
expect(transaction.getInput(1).finalScriptWitness).toBeUndefined();
});

it('does not finalize inputs given a transaction and a taproot payment script', () => {
const transaction = Transaction.fromPSBT(
hexToBytes(TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_2)
);

const alicePublicKey = ecdsaPublicKeyToSchnorr(
Buffer.from(TEST_ALICE_TAPROOT_PUBLIC_KEY_2, 'hex')
);
const alicePayment = p2tr(alicePublicKey, undefined, regtest);

finalizeUserInputs(transaction, alicePayment);

expect(transaction.getInput(0).finalScriptWitness).toBeUndefined();
expect(transaction.getInput(1).finalScriptWitness).toBeUndefined();
});
});
145 changes: 145 additions & 0 deletions tests/unit/utility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import {
compareUint8Arrays,
createRangeFromLength,
customShiftValue,
delay,
isDefined,
isUndefined,
reverseBytes,
shiftValue,
truncateAddress,
unshiftValue,
} from '../../src/utilities';

describe('Utility Functions', () => {
describe('shiftValue', () => {
it('correctly shifts the value by a power of 10^8', () => {
const value = 1;
const shiftedValue = shiftValue(value);
expect(shiftedValue).toBe(100000000);
});
});

describe('unshiftValue', () => {
it('correctly unshifts the value by a power of 10^8', () => {
const value = 100000000;
const unshiftedValue = unshiftValue(value);
expect(unshiftedValue).toBe(1);
});
});

describe('customShiftValue', () => {
it('correctly shifts the value based on the provided parameters', () => {
const value = 1;
const shift = 8;
const shiftedValue = customShiftValue(value, shift, false);
expect(shiftedValue).toBe(100000000);
});

it('correctly unshifts the value based on the provided parameters', () => {
const value = 100000000;
const shift = 8;
const unshiftedValue = customShiftValue(value, shift, true);
expect(unshiftedValue).toBe(1);
});
});

describe('truncateAddress', () => {
it('correctly truncates the address to the specified format', () => {
const address = '0x1234567890abcdef';
const truncatedAddress = truncateAddress(address);
expect(truncatedAddress).toBe('0x12...cdef');
});
});

describe('createRangeFromLength', () => {
it('creates a range of numbers based on the provided length', () => {
const length = 5;
const range = createRangeFromLength(length);
expect(range).toStrictEqual([0, 1, 2, 3, 4]);
});
});

describe('isUndefined', () => {
it('correctly identifies if a value is undefined', () => {
const value = undefined;
const isValueUndefined = isUndefined(value);
expect(isValueUndefined).toBe(true);
});

it('correctly identifies if a value is not undefined', () => {
const value = 1;
const isValueUndefined = isUndefined(value);
expect(isValueUndefined).toBe(false);
});
});

describe('isDefined', () => {
it('correctly identifies if a value is defined', () => {
const value = 1;
const isValueDefined = isDefined(value);
expect(isValueDefined).toBe(true);
});

it('correctly identifies if a value is not defined', () => {
const value = undefined;
const isValueDefined = isDefined(value);
expect(isValueDefined).toBe(false);
});
});

describe('compareUint8Arrays', () => {
it('correctly compares two Uint8Arrays for equality', () => {
const uint8ArrayA = new Uint8Array([
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0xff, 0x00, 0x7f,
0x80, 0xfe, 0x01, 0x76, 0x31, 0x30,
]);
const uint8ArrayB = new Uint8Array([
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0xff, 0x00, 0x7f,
0x80, 0xfe, 0x01, 0x76, 0x31, 0x30,
]);
const areArraysEqual = compareUint8Arrays(uint8ArrayA, uint8ArrayB);
expect(areArraysEqual).toBe(true);
});

it('correctly identifies two different Uint8Arrays', () => {
const uint8ArrayA = new Uint8Array([
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0xff, 0x00, 0x7f,
0x80, 0xfe, 0x01, 0x76, 0x31, 0x30,
]);
const uint8ArrayB = new Uint8Array([
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0xff, 0x00, 0x7f,
0x80, 0xfe, 0x01, 0x76, 0x31, 0x31,
]);

const areArraysEqual = compareUint8Arrays(uint8ArrayA, uint8ArrayB);
expect(areArraysEqual).toBe(false);
});
});

describe('reverseBytes', () => {
it('correctly reverses the bytes of a Uint8Array', () => {
const uint8Array = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
const reversedUint8Array = reverseBytes(uint8Array);
expect(reversedUint8Array).toStrictEqual(new Uint8Array([0x6f, 0x6c, 0x6c, 0x65, 0x48]));
});

it('correctly reverses the bytes of a Buffer', () => {
const buffer = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
const reversedBuffer = reverseBytes(buffer);
expect(reversedBuffer).toStrictEqual(Buffer.from([0x6f, 0x6c, 0x6c, 0x65, 0x48]));
});
});

describe('delay', () => {
it('delays execution for the specified number of milliseconds', () => {
const delayTime = 1000;
const startTime = Date.now();
delay(delayTime).then(() => {
const endTime = Date.now();
const elapsedTime = endTime - startTime;
expect(elapsedTime).toBeGreaterThanOrEqual(delayTime);
});
});
});
});
Loading