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

Fix bug in getOutpointByAddress() method #302

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 27 additions & 2 deletions src/_tests/unit/bip47.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,36 @@ describe('Test generation of payment codes and payment addresses', function () {
);

// Alice generates a payment address for sending funds to Bob
const bobAddress = await aliceQiWallet.generateSendAddress(bobPaymentCode, Zone.Cyprus1);
const bobAddress = await aliceQiWallet.getNextSendAddress(bobPaymentCode, Zone.Cyprus1);
assert.equal(bobAddress, '0x0083d552Fc0A3f9269089cbb9Ca11eaba93802e3');

// Bob generates a payment address for receiving funds from Alice
const receiveAddress = await bobQiWallet.generateReceiveAddress(alicePaymentCode, Zone.Cyprus1);
const receiveAddress = await bobQiWallet.getNextReceiveAddress(alicePaymentCode, Zone.Cyprus1);
assert.equal(receiveAddress, '0x0083d552Fc0A3f9269089cbb9Ca11eaba93802e3');
});
});

describe('Test opening channels', function () {
const BOB_MNEMONIC =
'innocent perfect bus miss prevent night oval position aspect nut angle usage expose grace juice';
const bobMnemonic = Mnemonic.fromPhrase(BOB_MNEMONIC);
const bobQiWallet = QiHDWallet.fromMnemonic(bobMnemonic);
it('opens a channel correctly', async function () {
const paymentCode =
'PM8TJTzqM3pqdQxBA52AX9M5JBCdkyJYWpNfJZpNX9H7FY2XitYFd99LSfCCQamCN5LubK1YNQMoz33g1WgVNX2keWoDtfDG9H1AfGcupRzHsPn6Rc2z';
bobQiWallet.openChannel(paymentCode, 'receiver');
assert.deepEqual(bobQiWallet.receiverPaymentCodeInfo[paymentCode], []);
});

it('does nothing if the channel is already open', async function () {
const paymentCode =
'PM8TJTzqM3pqdQxBA52AX9M5JBCdkyJYWpNfJZpNX9H7FY2XitYFd99LSfCCQamCN5LubK1YNQMoz33g1WgVNX2keWoDtfDG9H1AfGcupRzHsPn6Rc2z';
bobQiWallet.openChannel(paymentCode, 'receiver');
assert.deepEqual(bobQiWallet.receiverPaymentCodeInfo[paymentCode], []);
});

it('returns an error if the payment code is not valid', async function () {
const invalidPaymentCode = 'InvalidPaymentCode';
assert.throws(() => bobQiWallet.openChannel(invalidPaymentCode, 'receiver'), /Invalid payment code/);
});
});
4 changes: 2 additions & 2 deletions src/_tests/unit/qihdwallet.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ describe('QiHDWallet: Test serialization and deserialization of QiHDWallet with
const bobPaymentCode = await bobQiWallet.getPaymentCode(0);

// Generate addresses
await aliceQiWallet.generateSendAddress(bobPaymentCode, Zone.Cyprus1);
await aliceQiWallet.generateReceiveAddress(bobPaymentCode, Zone.Cyprus1);
await aliceQiWallet.getNextSendAddress(bobPaymentCode, Zone.Cyprus1);
await aliceQiWallet.getNextReceiveAddress(bobPaymentCode, Zone.Cyprus1);

// Serialize Alice's wallet
const serializedAliceWallet = aliceQiWallet.serialize();
Expand Down
9 changes: 4 additions & 5 deletions src/providers/abstract-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1484,16 +1484,15 @@ export class AbstractProvider<C = FetchRequest> implements Provider {
}

async getOutpointsByAddress(address: AddressLike): Promise<Outpoint[]> {
const outpoints: OutpointResponseParams[] = await this.#getAccountValue(
const outpointsObj: Record<string, OutpointResponseParams> = await this.#getAccountValue(
{ method: 'getOutpointsByAddress' },
address,
'latest',
);

const outpointsArray = Array.isArray(outpoints) ? outpoints : [];

return outpointsArray.map((outpoint: OutpointResponseParams) => ({
txhash: outpoint.Txhash,
// Convert the object to an array of Outpoint objects
return Object.values(outpointsObj).map((outpoint: OutpointResponseParams) => ({
txhash: outpoint.TxHash,
index: outpoint.Index,
denomination: outpoint.Denomination,
}));
Expand Down
2 changes: 1 addition & 1 deletion src/providers/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ export interface QiTransactionResponseParams {
}

export interface OutpointResponseParams {
Txhash: string;
TxHash: string;
Index: number;
Denomination: number;
}
29 changes: 15 additions & 14 deletions src/wallet/payment-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,39 +285,41 @@ export class PaymentCodePrivate extends PaymentCodePublic {
* @param {string} paymentCode - The payment code to validate.
* @throws {Error} If the payment code is invalid.
*/
export async function validatePaymentCode(paymentCode: string): Promise<boolean> {
export function validatePaymentCode(paymentCode: string): boolean {
const VERSION_BYTE = 0x47;
const FEATURE_BYTE = 0x00;

try {
const decoded = bs58check.decode(paymentCode);

if (decoded.length !== 82) {
if (decoded.length !== 81) {
return false;
}

if (decoded[0] !== 0x47) {
if (decoded[0] !== VERSION_BYTE) {
return false;
}

const payload = decoded.slice(0, -4);
const checksum = decoded.slice(-4);
const calculatedChecksum = sha256(sha256(payload)).slice(0, 4);
if (!checksum.every((b, i) => b === calculatedChecksum[i])) {
const paymentCodeBytes = decoded.slice(1);

if (paymentCodeBytes[0] !== 0x01) {
return false;
}

const paymentCodeBytes = decoded.slice(1, -4);

if (paymentCodeBytes[0] !== 0x01 && paymentCodeBytes[0] !== 0x02) {
// Check if the second byte is 0 (features byte)
if (paymentCodeBytes[1] !== FEATURE_BYTE) {
return false;
}

// Check if the public key starts with 0x02 or 0x03
if (paymentCodeBytes[2] !== 0x02 && paymentCodeBytes[2] !== 0x03) {
return false;
}

const xCoordinate = paymentCodeBytes.slice(3, 35);
const pubKey = paymentCodeBytes.slice(2, 35);
try {
secp256k1.ProjectivePoint.fromHex(xCoordinate).assertValidity();
secp256k1.ProjectivePoint.fromHex(Buffer.from(pubKey).toString('hex')).assertValidity();
} catch (error) {
console.log('error validating paymentcode x-coordinate: ', error);
return false;
}

Expand All @@ -327,7 +329,6 @@ export async function validatePaymentCode(paymentCode: string): Promise<boolean>

return true;
} catch (error) {
console.log('error validating paymentcode: ', error);
return false;
}
}
34 changes: 27 additions & 7 deletions src/wallet/qi-hdwallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,11 +482,7 @@ export class QiHDWallet extends AbstractHDWallet {
*/
private async getOutpointsByAddress(address: string): Promise<Outpoint[]> {
try {
const outpointsMap = await this.provider!.getOutpointsByAddress(address);
if (!outpointsMap) {
return [];
}
return Object.values(outpointsMap) as Outpoint[];
return await this.provider!.getOutpointsByAddress(address);
} catch (error) {
throw new Error(`Failed to get outpoints for address: ${address} - error: ${error}`);
}
Expand Down Expand Up @@ -754,7 +750,7 @@ export class QiHDWallet extends AbstractHDWallet {
* @returns {Promise<string>} A promise that resolves to the payment address for sending funds.
* @throws {Error} Throws an error if the payment code version is invalid.
*/
public async generateSendAddress(receiverPaymentCode: string, zone: Zone, account: number = 0): Promise<string> {
public async getNextSendAddress(receiverPaymentCode: string, zone: Zone, account: number = 0): Promise<string> {
const bip32 = await this._getBIP32API();
const buf = await this._decodeBase58(receiverPaymentCode);
const version = buf[0];
Expand Down Expand Up @@ -802,7 +798,7 @@ export class QiHDWallet extends AbstractHDWallet {
* @returns {Promise<string>} A promise that resolves to the payment address for receiving funds.
* @throws {Error} Throws an error if the payment code version is invalid.
*/
public async generateReceiveAddress(senderPaymentCode: string, zone: Zone, account: number = 0): Promise<string> {
public async getNextReceiveAddress(senderPaymentCode: string, zone: Zone, account: number = 0): Promise<string> {
const bip32 = await this._getBIP32API();
const buf = await this._decodeBase58(senderPaymentCode);
const version = buf[0];
Expand Down Expand Up @@ -841,4 +837,28 @@ export class QiHDWallet extends AbstractHDWallet {
`Failed to derive a valid address for the zone ${zone} after ${MAX_ADDRESS_DERIVATION_ATTEMPTS} attempts.`,
);
}

/**
* Receives a payment code and stores it in the wallet for future use. If the payment code is already in the wallet,
* it will be ignored.
*
* @param {string} paymentCode - The payment code to store.
* @param {'receiver' | 'sender'} type - The type of payment code ('receiver' or 'sender').
*/
public openChannel(paymentCode: string, type: 'receiver' | 'sender'): void {
if (!validatePaymentCode(paymentCode)) {
throw new Error(`Invalid payment code: ${paymentCode}`);
}
if (type === 'receiver') {
if (this._receiverPaymentCodeInfo.has(paymentCode)) {
return;
}
this._receiverPaymentCodeInfo.set(paymentCode, []);
} else {
if (this._senderPaymentCodeInfo.has(paymentCode)) {
return;
}
this._senderPaymentCodeInfo.set(paymentCode, []);
}
}
}
Loading