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

createIntegratedAddress, address and payment id validation was added #14

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion src/address/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export const BUFFER_ADDRESS_LENGTH: number =
CHECKSUM_LENGTH;
export const ADDRESS_REGEX = /^Z[a-zA-Z0-9]{96}$/;
export const INTEGRATED_ADDRESS_REGEX = /^iZ[a-zA-Z0-9]{106}$/;
export const PAYMENT_ID_LENGTH = 4;
export const PAYMENT_ID_LENGTH = 8;
export const PAYMENT_ID_REGEX = /^[a-zA-Z0-9]{8}$/;
export const INTEGRATED_ADDRESS_FLAG_PREFIX = 0x6c;
export const INTEGRATED_ADDRESS_TAG_PREFIX = 0xf8;
export const BUFFER_INTEGRATED_ADDRESS_LENGTH =
Expand Down
7 changes: 7 additions & 0 deletions src/address/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@ export type ZarcanumAddressKeys = {
spendPublicKey: string;
viewPublicKey: string;
}

export type DecodedAddress = {
tag: number;
flag: number;
viewPublicKey: Buffer;
spendPublicKey: Buffer;
}
97 changes: 77 additions & 20 deletions src/address/zano-address-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,53 @@ import {
TAG_LENGTH,
VIEW_KEY_LENGTH,
ADDRESS_REGEX,
PAYMENT_ID_REGEX,
} from './constants';
import { ZarcanumAddressKeys } from './types';
import { DecodedAddress, ZarcanumAddressKeys } from './types';
import { base58Encode, base58Decode } from '../core/base58';
import { getChecksum } from '../core/crypto';


export class ZanoAddressUtils {

getIntegratedAddress(address: string): string {
try {
const decodedAddress: Buffer = base58Decode(address);
private formatIntegratedAddress(addressDecoded: DecodedAddress, paymentIdBuffer: Buffer): string {
const {
tag, flag, viewPublicKey, spendPublicKey,
}: DecodedAddress = addressDecoded;

const tag: number = INTEGRATED_ADDRESS_TAG_PREFIX;
const flag: number = INTEGRATED_ADDRESS_FLAG_PREFIX;
const integratedAddressBuffer: Buffer = Buffer.concat([
Buffer.from([tag, flag]),
viewPublicKey,
spendPublicKey,
paymentIdBuffer,
]);

let offset: number = TAG_LENGTH + FLAG_LENGTH;
const viewPublicKey: Buffer = decodedAddress.subarray(offset, offset + VIEW_KEY_LENGTH);
offset += VIEW_KEY_LENGTH;
const spendPublicKey: Buffer = decodedAddress.subarray(offset, offset + SPEND_KEY_LENGTH);
const paymentId: Buffer = Buffer.from(this.generatePaymentId());
const checksum: string = this.calculateChecksum(integratedAddressBuffer);
return base58Encode(Buffer.concat([integratedAddressBuffer, Buffer.from(checksum, 'hex')]));
}

const integratedAddressBuffer: Buffer = Buffer.concat([
Buffer.from([tag, flag]),
viewPublicKey,
spendPublicKey,
paymentId,
]);
generateIntegratedAddress(address: string): string | null {
return this.createIntegratedAddress(address, this.generatePaymentId());
}

createIntegratedAddress(address: string, paymentId: string): string {
if (!this.validateAddress(address)) {
throw new Error('Invalid address format');
}

if (!this.validatePaymentId(paymentId)) {
throw new Error('Invalid payment ID format');
}

try {
const paymentIdBuffer: Buffer = Buffer.from(paymentId, 'hex');
const addressDecoded: DecodedAddress = this.decodeAddress(address);

const checksum: string = getChecksum(integratedAddressBuffer);
return base58Encode(Buffer.concat([integratedAddressBuffer, Buffer.from(checksum, 'hex')]));
if (!addressDecoded) {
throw new Error('Missing decoded address');
}

return this.formatIntegratedAddress(addressDecoded, paymentIdBuffer);
} catch (error) {
throw new Error(error.message);
}
Expand Down Expand Up @@ -120,13 +138,52 @@ export class ZanoAddressUtils {

if (!spendPublicKey || spendPublicKey.length !== SPEND_KEY_LENGTH * 2 ||
!viewPublicKey || viewPublicKey.length !== VIEW_KEY_LENGTH * 2) {
throw new Error('Invalid key format in the address.');
throw new Error('Invalid key format in the address');
}

return { spendPublicKey, viewPublicKey };
}

private decodeAddress(address: string): DecodedAddress | null {
try {
const decodedAddress: Buffer = base58Decode(address);
if(!decodedAddress) {
throw new Error('Invalid decode address');
}

let offset = TAG_LENGTH + FLAG_LENGTH;
const viewPublicKey: Buffer = decodedAddress.subarray(offset, offset + VIEW_KEY_LENGTH);
offset += VIEW_KEY_LENGTH;
const spendPublicKey: Buffer = decodedAddress.subarray(offset, offset + SPEND_KEY_LENGTH);

return {
tag: INTEGRATED_ADDRESS_TAG_PREFIX,
flag: INTEGRATED_ADDRESS_FLAG_PREFIX,
viewPublicKey,
spendPublicKey,
};
} catch (error) {
throw new Error(error.message);
}
}

private generatePaymentId(): string {
return crypto.randomBytes(PAYMENT_ID_LENGTH).toString('hex');
}

private calculateChecksum(buffer: Buffer): string {
return getChecksum(buffer);
}

private validatePaymentId(paymentId: string): boolean {
return PAYMENT_ID_REGEX.test(paymentId);
}

private validateAddress(address: string): boolean {
if (!(INTEGRATED_ADDRESS_REGEX.test(address) || ADDRESS_REGEX.test(address))) {
throw new Error('Invalid address format');
}
return true;
}

}
37 changes: 36 additions & 1 deletion tests/address-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe(
);

describe(
'testing the correctness of the address decoding function getKeysFromZarcanumAddress',
'testing the correctness of the address decoding function getKeysFromAddress',
() => {
const zanoAddressUtils = new ZanoAddressUtils();
const address = 'ZxD5aoLDPTdcaRx4uCpyW4XiLfEXejepAVz8cSY2fwHNEiJNu6NmpBBDLGTJzCsUvn3acCVDVDPMV8yQXdPooAp338Se7AxeH';
Expand Down Expand Up @@ -123,3 +123,38 @@ describe('getIntegratedAddress', () => {
expect(addressFromMaster2.slice(0, -SUFFIX_LENGTH)).toBe(master2BasedIntegratedAddressWithoutSuffix);
});
});

describe('createIntegratedAddress', () => {
const zanoAddressUtils: ZanoAddressUtils = new ZanoAddressUtils();
const paymentId: string = '3535bb68';

// Define test data
const integratedAddress: string = 'iZ2kFmwxRHoaRxm1ni8HnfUTkYuKbni8s4CE2Z4GgFfH99BJ6cnbAtJTgUnZjPj9CTCTKy1qqM9wPCTp92uBC7e47JPyhpqjfr11U14a4Tpm';
const masterAddress: string = 'ZxD5aoLDPTdcaRx4uCpyW4XiLfEXejepAVz8cSY2fwHNEiJNu6NmpBBDLGTJzCsUvn3acCVDVDPMV8yQXdPooAp338Se7AxeH';
const masterAddress2: string = 'ZxDG8UrQMEVaRxm1ni8HnfUTkYuKbni8s4CE2Z4GgFfH99BJ6cnbAtJTgUnZjPj9CTCTKy1qqM9wPCTp92uBC7e41KkqnWH8F';

const masterBasedIntegratedAddress: string = 'iZ2Zi6RmTWwcaRx4uCpyW4XiLfEXejepAVz8cSY2fwHNEiJNu6NmpBBDLGTJzCsUvn3acCVDVDPMV8yQXdPooAp3iTpX1x9Qbmj1U12u9nn2';
const master2BasedIntegratedAddress: string = 'iZ2kFmwxRHoaRxm1ni8HnfUTkYuKbni8s4CE2Z4GgFfH99BJ6cnbAtJTgUnZjPj9CTCTKy1qqM9wPCTp92uBC7e47JPyhpqjfr11U14a4Tpm';

// Compute desired outcomes for the slice operation
// const integratedAddressWithoutSuffix: string = integratedAddress.slice(0, -SUFFIX_LENGTH);
// const masterBasedIntegratedAddressWithoutSuffix: string = masterBasedIntegratedAddress.slice(0, -SUFFIX_LENGTH);
// const master2BasedIntegratedAddressWithoutSuffix: string = master2BasedIntegratedAddress.slice(0, -SUFFIX_LENGTH);

// Addresses returned by zanoAddressUtils
const addressFromIntegrated: string = zanoAddressUtils.createIntegratedAddress(integratedAddress, paymentId);
const addressFromMaster: string = zanoAddressUtils.createIntegratedAddress(masterAddress, paymentId);
const addressFromMaster2: string = zanoAddressUtils.createIntegratedAddress(masterAddress2, paymentId);

it('ensures that truncating the last 18 characters from the integrated address is correct', () => {
expect(addressFromIntegrated).toBe(integratedAddress);
});

it('ensures that truncating the last 18 characters from the master-based integrated address is correct', () => {
expect(addressFromMaster).toBe(masterBasedIntegratedAddress);
});

it('ensures that truncating the last 18 characters from the second master-based integrated address is correct', () => {
expect(addressFromMaster2).toBe(master2BasedIntegratedAddress);
});
});