Skip to content

Commit

Permalink
refactor(experimental): implement getBase58EncodedAddressForSeed (#1563)
Browse files Browse the repository at this point in the history
* refactor(experimental): implement createAddressWithSeed

* nit: add comment to test about malicious PDA marker

---------

Co-authored-by: steveluscher <[email protected]>
  • Loading branch information
2501babe and steveluscher authored Sep 7, 2023
1 parent 6a124b1 commit 5030000
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 2 deletions.
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';

0 comments on commit 5030000

Please sign in to comment.