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

Commit 3d76d55

Browse files
committed
refactor(experimental): implement createAddressWithSeed
1 parent d6c3cfe commit 3d76d55

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

packages/addresses/src/__tests__/program-derived-address-test.ts packages/addresses/src/__tests__/computed-address-test.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Base58EncodedAddress } from '../base58';
2-
import { getProgramDerivedAddress } from '../program-derived-address';
2+
import { createAddressWithSeed, getProgramDerivedAddress } from '../computed-address';
33

44
describe('getProgramDerivedAddress()', () => {
55
it('fatals when supplied more than 16 seeds', async () => {
@@ -139,3 +139,38 @@ describe('getProgramDerivedAddress()', () => {
139139
'fatals when supplied a combination of program address and seeds for which no off-curve point can be found'
140140
);
141141
});
142+
143+
describe('createAddressWithSeed', () => {
144+
it('returns an address that is the SHA-256 hash of the concatenated base address, seed, and program address', async () => {
145+
expect.assertions(2);
146+
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Base58EncodedAddress;
147+
const programAddress = 'FGrddpvjBUAG6VdV4fR8Q2hEZTHS6w4SEveVBgfwbfdm' as Base58EncodedAddress;
148+
const expectedAddress = 'HUKxCeXY6gZohFJFARbLE6L6C9wDEHz1SfK8ENM7QY7z' as Base58EncodedAddress;
149+
150+
await expect(createAddressWithSeed({ baseAddress, programAddress, seed: 'seed' })).resolves.toEqual(
151+
expectedAddress
152+
);
153+
154+
await expect(
155+
createAddressWithSeed({ baseAddress, programAddress, seed: new Uint8Array([0x73, 0x65, 0x65, 0x64]) })
156+
).resolves.toEqual(expectedAddress);
157+
});
158+
it('fails when the seed is longer than 32 bytes', async () => {
159+
expect.assertions(1);
160+
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Base58EncodedAddress;
161+
const programAddress = 'FGrddpvjBUAG6VdV4fR8Q2hEZTHS6w4SEveVBgfwbfdm' as Base58EncodedAddress;
162+
163+
await expect(createAddressWithSeed({ baseAddress, programAddress, seed: 'a'.repeat(33) })).rejects.toThrow(
164+
'The seed exceeds the maximum length of 32 bytes'
165+
);
166+
});
167+
it('fails with a malicious programAddress meant to produce an address that would collide with a PDA', async () => {
168+
expect.assertions(1);
169+
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Base58EncodedAddress;
170+
const programAddress = '4vJ9JU1bJJE96FbKdjWme2JfVK1knU936FHTDZV7AC2' as Base58EncodedAddress;
171+
172+
await expect(createAddressWithSeed({ baseAddress, programAddress, seed: 'seed' })).rejects.toThrow(
173+
'programAddress cannot end with the PDA marker'
174+
);
175+
});
176+
});

packages/addresses/src/program-derived-address.ts packages/addresses/src/computed-address.ts

+38
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ type PDAInput = Readonly<{
77
programAddress: Base58EncodedAddress;
88
seeds: Seed[];
99
}>;
10+
11+
type SeedInput = Readonly<{
12+
baseAddress: Base58EncodedAddress;
13+
programAddress: Base58EncodedAddress;
14+
seed: Seed;
15+
}>;
16+
1017
type Seed = string | Uint8Array;
1118

1219
const MAX_SEED_LENGTH = 32;
@@ -76,3 +83,34 @@ export async function getProgramDerivedAddress({ programAddress, seeds }: PDAInp
7683
// TODO: Coded error.
7784
throw new Error('Unable to find a viable program address bump seed');
7885
}
86+
87+
export async function createAddressWithSeed({
88+
baseAddress,
89+
programAddress,
90+
seed,
91+
}: SeedInput): Promise<Base58EncodedAddress> {
92+
const { serialize, deserialize } = getBase58EncodedAddressCodec();
93+
94+
const seedBytes = typeof seed === 'string' ? new TextEncoder().encode(seed) : seed;
95+
if (seedBytes.byteLength > MAX_SEED_LENGTH) {
96+
// TODO: Coded error.
97+
throw new Error(`The seed exceeds the maximum length of 32 bytes`);
98+
}
99+
100+
const programAddressBytes = serialize(programAddress);
101+
if (
102+
programAddressBytes.length >= PDA_MARKER_BYTES.length &&
103+
programAddressBytes.slice(-PDA_MARKER_BYTES.length).every((byte, index) => byte === PDA_MARKER_BYTES[index])
104+
) {
105+
// TODO: Coded error.
106+
throw new Error(`programAddress cannot end with the PDA marker`);
107+
}
108+
109+
const addressBytesBuffer = await crypto.subtle.digest(
110+
'SHA-256',
111+
new Uint8Array([...serialize(baseAddress), ...seedBytes, ...programAddressBytes])
112+
);
113+
const addressBytes = new Uint8Array(addressBytesBuffer);
114+
115+
return deserialize(addressBytes)[0];
116+
}

packages/addresses/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export * from './base58';
2-
export * from './program-derived-address';
2+
export * from './computed-address';
33
export * from './public-key';

0 commit comments

Comments
 (0)