Skip to content

Commit

Permalink
feat: parametrized Safe version
Browse files Browse the repository at this point in the history
  • Loading branch information
70nyIT committed Feb 16, 2024
1 parent 8c4bf81 commit 35df300
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 2 deletions.
21 changes: 21 additions & 0 deletions src/generateEphemeralPrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,27 @@ export function generateEphemeralPrivateKey({
const coinTypePart1 = parseInt(coinTypeString.slice(0, 1), 16);
const coinTypePart2 = parseInt(coinTypeString.slice(1), 16);

// key derivation structure is m/5564'/N'/c0'/c1'/0'/p/n

// 5564 is the purpose as defined in BIP-43 and aligns with the stealth address EIP number, EIP-5564:
// https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki
// https://eips.ethereum.org/EIPS/eip-5564

// N is the number of the node shared with the server

// c is the coinType as derived in ENSIP-11 for EVM chains: https://docs.ens.domains/ens-improvement-proposals/ensip-11-evmchain-address-resolution
// as it is a number above 0x80000000, which is not allowed in secure-bip32 (see HARDENED_OFFSET), it is split into two parts:
// c0, the first byte of c
// c1, the remaining bytes of c

// only the node m/5564'/0' of the private viewing key is shared with the server (see extractPrivateKeyNode)
// the server then generates pseudo-random addresses by incrementing n
// since each value cannot be larger than 2^31 - 1 (see HARDENED_OFFSET - 0x7FFFFFF)
// we therefore introduce a parent nonce p to allow for more addresses to be generated.
// If the nonce is bigger than n, we put the overflow part into a parentNonce. To simplify
// the creation of parentNonce, we set MAX_N to be 0xFFFFFFF. With this schema the combination of nonce
// and parent nonce has a max value of 0x7FFFFFFFFFFFFFF = 576460752303423487₁₀

// Split the nonce into two parts to ensure no number above 0x80000000 is used in the derivation path
const MAX_NONCE = 0xfffffff;
let parentNonce = 0;
Expand Down
12 changes: 10 additions & 2 deletions src/predictStealthSafeAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
toBytes,
} from 'viem';
import * as chains from 'viem/chains';
import { SafeVersion } from './predictStealthSafeAddressTypes';

/**
* Using Viem transaction simulation, predict a new Safe address using the parameters passed in input.
Expand All @@ -32,12 +33,14 @@ export async function predictStealthSafeAddressWithClient({
stealthAddresses,
transport,
useDefaultAddress,
safeVersion,
}: {
threshold: number;
stealthAddresses: string[];
chainId?: number;
transport?: any;
useDefaultAddress?: boolean;
safeVersion: SafeVersion;
}): Promise<{ stealthSafeAddress: `0x${string}` }> {

// if useDefaultAddress is false, chainId is required
Expand All @@ -53,6 +56,7 @@ export async function predictStealthSafeAddressWithClient({
threshold,
stealthAddresses,
useDefaultAddress,
safeVersion,
});

const safeSingletonAddress = useDefaultAddress ? safeSingleton.defaultAddress : safeSingleton.networkAddresses[chainId.toString()];
Expand Down Expand Up @@ -109,12 +113,14 @@ export function predictStealthSafeAddressWithBytecode({
threshold,
stealthAddresses,
useDefaultAddress,
safeVersion,
}: {
safeProxyBytecode: `0x${string}`;
threshold: number;
stealthAddresses: string[];
chainId?: number;
useDefaultAddress?: boolean;
safeVersion: SafeVersion;
}): { stealthSafeAddress: `0x${string}` } {
// if useDefaultAddress is false, chainId is required
if (!useDefaultAddress) {
Expand All @@ -129,6 +135,7 @@ export function predictStealthSafeAddressWithBytecode({
threshold,
stealthAddresses,
useDefaultAddress,
safeVersion,
});

const safeSingletonAddress = useDefaultAddress ? safeSingleton.defaultAddress : safeSingleton.networkAddresses[chainId.toString()];
Expand Down Expand Up @@ -170,19 +177,20 @@ function getSafeInitializerData ({
threshold,
stealthAddresses,
useDefaultAddress,
safeVersion,
}: {
threshold: number;
stealthAddresses: string[];
chainId?: number;
useDefaultAddress?: boolean;
safeVersion: SafeVersion;
}): {
initializer: `0x${string}`;
proxyFactory: SingletonDeployment;
safeSingleton: SingletonDeployment;
} {
// Constants
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
const SAFE_VERSION = '1.3.0';

// if useDefaultAddress is false, chainId is required
if (!useDefaultAddress) {
Expand All @@ -195,7 +203,7 @@ function getSafeInitializerData ({
// Configuration to fetch the Safe contracts
const configuration = {
network: chainId.toString(),
version: SAFE_VERSION,
version: safeVersion,
released: true,
};

Expand Down
1 change: 1 addition & 0 deletions src/predictStealthSafeAddressTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type SafeVersion = '1.4.1' | '1.3.0' | '1.2.0' | '1.1.1' | '1.0.0';
11 changes: 11 additions & 0 deletions test/predictStealthSafeAddress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('predictStealthSafeAddressWithClient', () => {
chainId,
threshold,
stealthAddresses,
safeVersion: '1.3.0',
});

expect(result).toEqual({ stealthSafeAddress: expectedStealthSafeAddress });
Expand All @@ -35,12 +36,14 @@ describe('predictStealthSafeAddressWithClient', () => {
useDefaultAddress: true,
threshold,
stealthAddresses,
safeVersion: '1.3.0',
});

const resultWithChainMainnet = await predictStealthSafeAddressWithClient({
chainId: 1,
threshold,
stealthAddresses,
safeVersion: '1.3.0',
});

expect(resultWithUseDefaultAddress).toEqual(resultWithChainMainnet);
Expand All @@ -61,6 +64,7 @@ describe('predictStealthSafeAddressWithClient', () => {
chainId,
threshold,
stealthAddresses,
safeVersion: '1.3.0',
}),
).rejects.toThrow('No safe contracts found for this configuration.');
});
Expand All @@ -76,6 +80,7 @@ describe('predictStealthSafeAddressWithClient', () => {
chainId,
threshold,
stealthAddresses,
safeVersion: '1.3.0',
});
expect(result).toHaveProperty('stealthSafeAddress');
expect(result.stealthSafeAddress).toMatch(/^0x[a-fA-F0-9]{40}$/);
Expand Down Expand Up @@ -107,6 +112,7 @@ describe('predictStealthSafeAddressWithBytecode', () => {
threshold,
stealthAddresses,
safeProxyBytecode,
safeVersion: '1.3.0',
});

expect(result).toEqual({ stealthSafeAddress: expectedStealthSafeAddress });
Expand All @@ -126,13 +132,15 @@ describe('predictStealthSafeAddressWithBytecode', () => {
threshold,
stealthAddresses,
safeProxyBytecode,
safeVersion: '1.3.0',
});

const resultWithChainMainnet = predictStealthSafeAddressWithBytecode({
chainId: 1,
threshold,
stealthAddresses,
safeProxyBytecode,
safeVersion: '1.3.0',
});

expect(resultWithUseDefaultAddress).toEqual(resultWithChainMainnet);
Expand All @@ -154,6 +162,7 @@ describe('predictStealthSafeAddressWithBytecode', () => {
threshold,
stealthAddresses,
safeProxyBytecode,
safeVersion: '1.3.0',
}),
).toThrow('No safe contracts found for this configuration.');
});
Expand All @@ -172,6 +181,7 @@ describe('predictStealthSafeAddressWithBytecode', () => {
threshold,
stealthAddresses,
safeProxyBytecode,
safeVersion: '1.3.0',
}),
).toThrow('chainId is required when useDefaultAddress is false');
});
Expand All @@ -188,6 +198,7 @@ describe('predictStealthSafeAddressWithBytecode', () => {
threshold,
stealthAddresses,
safeProxyBytecode,
safeVersion: '1.3.0',
});
expect(result).toHaveProperty('stealthSafeAddress');
expect(result.stealthSafeAddress).toMatch(/^0x[a-fA-F0-9]{40}$/);
Expand Down

0 comments on commit 35df300

Please sign in to comment.