Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

refactor(experimental): implement getBase58EncodedAddressForSeed #1563

Merged
merged 2 commits into from
Sep 7, 2023
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Base58EncodedAddress } from '../base58';
import { getProgramDerivedAddress } from '../program-derived-address';
import { createAddressWithSeed, getProgramDerivedAddress } from '../computed-address';

describe('getProgramDerivedAddress()', () => {
it('fatals when supplied more than 16 seeds', async () => {
Expand Down Expand Up @@ -139,3 +139,39 @@ describe('getProgramDerivedAddress()', () => {
'fatals when supplied a combination of program address and seeds for which no off-curve point can be found'
);
});

describe('createAddressWithSeed', () => {
it('returns an address that is the SHA-256 hash of the concatenated base address, seed, and program address', async () => {
expect.assertions(2);
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Base58EncodedAddress;
const programAddress = 'FGrddpvjBUAG6VdV4fR8Q2hEZTHS6w4SEveVBgfwbfdm' as Base58EncodedAddress;
const expectedAddress = 'HUKxCeXY6gZohFJFARbLE6L6C9wDEHz1SfK8ENM7QY7z' as Base58EncodedAddress;

await expect(createAddressWithSeed({ baseAddress, programAddress, seed: 'seed' })).resolves.toEqual(
expectedAddress
);

await expect(
createAddressWithSeed({ baseAddress, programAddress, seed: new Uint8Array([0x73, 0x65, 0x65, 0x64]) })
).resolves.toEqual(expectedAddress);
});
it('fails when the seed is longer than 32 bytes', async () => {
expect.assertions(1);
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Base58EncodedAddress;
const programAddress = 'FGrddpvjBUAG6VdV4fR8Q2hEZTHS6w4SEveVBgfwbfdm' as Base58EncodedAddress;

await expect(createAddressWithSeed({ baseAddress, programAddress, seed: 'a'.repeat(33) })).rejects.toThrow(
'The seed exceeds the maximum length of 32 bytes'
);
});
it('fails with a malicious programAddress meant to produce an address that would collide with a PDA', async () => {
expect.assertions(1);
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Base58EncodedAddress;
// The ending bytes of this address decode to the ASCII string 'ProgramDerivedAddress'
const programAddress = '4vJ9JU1bJJE96FbKdjWme2JfVK1knU936FHTDZV7AC2' as Base58EncodedAddress;

await expect(createAddressWithSeed({ baseAddress, programAddress, seed: 'seed' })).rejects.toThrow(
'programAddress cannot end with the PDA marker'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ type PDAInput = Readonly<{
programAddress: Base58EncodedAddress;
seeds: Seed[];
}>;

type SeedInput = Readonly<{
baseAddress: Base58EncodedAddress;
programAddress: Base58EncodedAddress;
seed: Seed;
}>;

type Seed = string | Uint8Array;

const MAX_SEED_LENGTH = 32;
Expand Down Expand Up @@ -76,3 +83,34 @@ export async function getProgramDerivedAddress({ programAddress, seeds }: PDAInp
// TODO: Coded error.
throw new Error('Unable to find a viable program address bump seed');
}

export async function createAddressWithSeed({
baseAddress,
programAddress,
seed,
}: SeedInput): Promise<Base58EncodedAddress> {
const { serialize, deserialize } = getBase58EncodedAddressCodec();

const seedBytes = typeof seed === 'string' ? new TextEncoder().encode(seed) : seed;
if (seedBytes.byteLength > MAX_SEED_LENGTH) {
// TODO: Coded error.
throw new Error(`The seed exceeds the maximum length of 32 bytes`);
}

const programAddressBytes = serialize(programAddress);
if (
programAddressBytes.length >= PDA_MARKER_BYTES.length &&
programAddressBytes.slice(-PDA_MARKER_BYTES.length).every((byte, index) => byte === PDA_MARKER_BYTES[index])
) {
// TODO: Coded error.
throw new Error(`programAddress cannot end with the PDA marker`);
}

const addressBytesBuffer = await crypto.subtle.digest(
'SHA-256',
new Uint8Array([...serialize(baseAddress), ...seedBytes, ...programAddressBytes])
);
const addressBytes = new Uint8Array(addressBytesBuffer);

return deserialize(addressBytes)[0];
}
2 changes: 1 addition & 1 deletion packages/addresses/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './base58';
export * from './program-derived-address';
export * from './computed-address';
export * from './public-key';