Created
March 6, 2025 20:13
-
-
Save EdsonAlcala/1735886b2cda71c0c20c8f661c34a336 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { NEAR_MAINNET_NETWORK_ID, NEAR_TESTNET_NETWORK_ID } from '../../../network'; | |
import { MPC_SIGNER_MAINNET, MPC_SIGNER_TESTNET, ROOT_PUBLIC_KEY_MAINNET, ROOT_PUBLIC_KEY_TESTNET } from '../constants'; | |
import { AddressType, deriveChildPublicKey, generateAddress, getMpcAccountIdByNetwork, getRootPublicKey } from './nearChainSignature'; | |
describe("Chain Signature Utils", () => { | |
describe('deriveChildPublicKey', () => { | |
it('should derive a valid child public key', () => { | |
const accountId = "omnitester.testnet"; | |
const derivedKey = deriveChildPublicKey("secp256k1:4NfTiv3UsGahebgTaHyD9vF8KYKMBnfd6kh94mK6xv8fGBiJB8TBtFMP5WWXz6B89Ac1fbpzPwAvoyQebemHFwx3", accountId, "ethereum-1"); | |
expect(derivedKey).toEqual("046a99e52f042a96ea2293a69f3154b63c06cd0770cf89a46d0d1ab331dab64d8b1472638899edf8904e1c06ac7fce3e47ea1403279341c147303fd6c5df806623") | |
}); | |
it('should derive a valid child public key [against signet]', async () => { | |
const accountId = "omnitester.testnet"; | |
const derivedKey = deriveChildPublicKey("secp256k1:4NfTiv3UsGahebgTaHyD9vF8KYKMBnfd6kh94mK6xv8fGBiJB8TBtFMP5WWXz6B89Ac1fbpzPwAvoyQebemHFwx3", accountId, "ethereum-1"); | |
expect(derivedKey).toEqual("046a99e52f042a96ea2293a69f3154b63c06cd0770cf89a46d0d1ab331dab64d8b1472638899edf8904e1c06ac7fce3e47ea1403279341c147303fd6c5df806623") | |
}); | |
}); | |
describe('generateAddress', () => { | |
it('should generate a valid EVM address', () => { | |
const cases = [ | |
{ | |
publicKey: 'secp256k1:4NfTiv3UsGahebgTaHyD9vF8KYKMBnfd6kh94mK6xv8fGBiJB8TBtFMP5WWXz6B89Ac1fbpzPwAvoyQebemHFwx3', | |
accountId: 'omnitester.testnet', | |
path: 'ethereum-1', | |
addressType: AddressType.EVM, | |
expectedAddress: '0xd8d25820c9b9e2aa9cce55504355e500efcce715', | |
expectedPublicKey: '04e612e7650febebc50b448bf790f6bdd70a8a6ce3b111a1d7e72c87afe84be776e36226e3f89de1ba3cbb62c0f3fc05bffae672c9c59d5fa8a4737b6547c64eb7' | |
}, | |
{ | |
publicKey: 'secp256k1:3tFRbMqmoa6AAALMrEFAYCEoHcqKxeW38YptwowBVBtXK1vo36HDbUWuR6EZmoK4JcH6HDkNMGGqP1ouV7VZUWya', | |
accountId: 'example.near', | |
path: 'ethereum-1', | |
addressType: AddressType.EVM, | |
expectedAddress: "0xef4ea05e5c11057decc8e1bd6f5369f2acd91fa5", | |
expectedPublicKey: '04f981e58fa26199180ea71b627362c7d1a6bebd73f61fedbe2e0acea5c2e75128a327822ee298967e485d787e0e98163b7d385516811f913cb7256182f5a20394' | |
}, | |
{ | |
publicKey: 'secp256k1:3tFRbMqmoa6AAALMrEFAYCEoHcqKxeW38YptwowBVBtXK1vo36HDbUWuR6EZmoK4JcH6HDkNMGGqP1ouV7VZUWya', | |
accountId: 'example.near', | |
path: 'ethereum-2', | |
addressType: AddressType.EVM, | |
expectedAddress: "0x273eeb4d7651d9296ec5bd9a6ceebca219101cb8", | |
expectedPublicKey: '04599038b49d32f1f345e4321dca5f3619d96100126ef940f904c1f7cb0ada3ad8f2bb44672ba5b686940230505d0bdaf4764ee7df58a5300691f60eeff095e9fc' | |
} | |
] | |
cases.forEach(({ publicKey, accountId, path, addressType, expectedAddress, expectedPublicKey }) => { | |
const result = generateAddress({ publicKey, accountId, path, addressType }); | |
expect(result).toHaveProperty('address', expectedAddress); | |
expect(result).toHaveProperty('publicKey', expectedPublicKey); | |
}); | |
}); | |
it('should throw an error for unsupported address types', () => { | |
expect(() => generateAddress({ | |
publicKey: 'secp256k1:4NfTiv3UsGahebgTaHyD9vF8KYKMBnfd6kh94mK6xv8fGBiJB8TBtFMP5WWXz6B89Ac1fbpzPwAvoyQebemHFwx3', | |
accountId: 'omnitester.testnet', | |
path: '0', | |
addressType: AddressType.BITCOIN_MAINNET_LEGACY | |
})).toThrow('Unsupported address type: bitcoin-mainnet-legacy'); | |
}); | |
}); | |
describe('getRootPublicKey', () => { | |
it('should return the correct root public key for testnet', () => { | |
expect(getRootPublicKey(NEAR_TESTNET_NETWORK_ID)).toEqual(ROOT_PUBLIC_KEY_TESTNET); | |
}); | |
it('should return the correct root public key for mainnet', () => { | |
expect(getRootPublicKey(NEAR_MAINNET_NETWORK_ID)).toEqual(ROOT_PUBLIC_KEY_MAINNET); | |
}); | |
it('should throw an error for unsupported networks', () => { | |
expect(() => getRootPublicKey('invalid-network' as any)).toThrow('Unsupported network: invalid-network'); | |
}); | |
}); | |
describe('getMpcAccountIdByNetwork', () => { | |
it('should return the correct MPC signer for testnet', () => { | |
expect(getMpcAccountIdByNetwork(NEAR_TESTNET_NETWORK_ID)).toEqual(MPC_SIGNER_TESTNET); | |
}); | |
it('should return the correct MPC signer for mainnet', () => { | |
expect(getMpcAccountIdByNetwork(NEAR_MAINNET_NETWORK_ID)).toEqual(MPC_SIGNER_MAINNET); | |
}); | |
it('should throw an error for unsupported networks', () => { | |
expect(() => getMpcAccountIdByNetwork('invalid-network' as any)).toThrow('Unsupported network: invalid-network'); | |
}); | |
}); | |
}) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { baseDecode } from '@near-js/utils'; | |
import { ec as EC } from 'elliptic'; | |
import { sha3_256 } from 'js-sha3'; | |
import { uncompressedHexPointToEvmAddress } from './address'; | |
import { NEAR_MAINNET_NETWORK_ID, NEAR_NETWORK_ID, NEAR_TESTNET_NETWORK_ID } from '../../../network'; | |
import { MPC_SIGNER_MAINNET, MPC_SIGNER_TESTNET, ROOT_PUBLIC_KEY_MAINNET, ROOT_PUBLIC_KEY_TESTNET } from '../constants'; | |
function najPublicKeyStrToUncompressedHexPoint(najPublicKeyStr: string): string { | |
const decodedKey = baseDecode(najPublicKeyStr.split(':')[1]!); | |
return '04' + Buffer.from(decodedKey).toString('hex'); | |
} | |
export function deriveChildPublicKey( | |
parentUncompressedPublicKeyHex: string, | |
accountId: string, | |
path: string = '', | |
): string { | |
const ec = new EC('secp256k1'); | |
const scalarHex = sha3_256( | |
`near-mpc-recovery v0.1.0 epsilon derivation:${accountId},${path}`, | |
); | |
const x = parentUncompressedPublicKeyHex.substring(2, 66); | |
const y = parentUncompressedPublicKeyHex.substring(66); | |
// Create a point object from X and Y coordinates | |
const oldPublicKeyPoint = ec.curve.point(x, y); | |
// Multiply the scalar by the generator point G | |
const scalarTimesG = ec.g.mul(scalarHex); | |
// Add the result to the old public key point | |
const newPublicKeyPoint = oldPublicKeyPoint.add(scalarTimesG); | |
const newX = newPublicKeyPoint.getX().toString('hex').padStart(64, '0'); | |
const newY = newPublicKeyPoint.getY().toString('hex').padStart(64, '0'); | |
return '04' + newX + newY; | |
} | |
export enum AddressType { | |
EVM = 'evm', | |
BITCOIN_MAINNET_LEGACY = 'bitcoin-mainnet-legacy', | |
BITCOIN_MAINNET_SEGWIT = 'bitcoin-mainnet-segwit', | |
BITCOIN_TESTNET_LEGACY = 'bitcoin-testnet-legacy', | |
BITCOIN_TESTNET_SEGWIT = 'bitcoin-testnet-segwit', | |
} | |
type GenerateAddressParams = { | |
publicKey: string; | |
accountId: string; | |
path: string; | |
addressType: AddressType; | |
} | |
export function generateAddress({ publicKey, accountId, path, addressType }: GenerateAddressParams) { | |
let childPublicKey = deriveChildPublicKey( | |
najPublicKeyStrToUncompressedHexPoint(publicKey), | |
accountId, | |
path, | |
); | |
if (!addressType) | |
throw new Error('addressType is required'); | |
let address; | |
switch (addressType) { | |
case AddressType.EVM: | |
address = uncompressedHexPointToEvmAddress(childPublicKey); | |
break; | |
case AddressType.BITCOIN_MAINNET_LEGACY: | |
throw new Error(`Unsupported address type: ${addressType}`); | |
case AddressType.BITCOIN_MAINNET_SEGWIT: | |
throw new Error(`Unsupported address type: ${addressType}`); | |
case AddressType.BITCOIN_TESTNET_LEGACY: | |
throw new Error(`Unsupported address type: ${addressType}`); | |
case AddressType.BITCOIN_TESTNET_SEGWIT: | |
throw new Error(`Unsupported address type: ${addressType}`); | |
default: | |
throw new Error(`Unsupported address type: ${addressType}`); | |
} | |
return { | |
address, | |
publicKey: childPublicKey | |
}; | |
} | |
export function getRootPublicKey(network: NEAR_NETWORK_ID): string { | |
switch (network) { | |
case NEAR_TESTNET_NETWORK_ID: | |
return ROOT_PUBLIC_KEY_TESTNET; | |
case NEAR_MAINNET_NETWORK_ID: | |
return ROOT_PUBLIC_KEY_MAINNET; | |
default: | |
throw new Error(`Unsupported network: ${network}`); | |
} | |
} | |
export function getMpcAccountIdByNetwork(network: NEAR_NETWORK_ID): string { | |
switch (network) { | |
case NEAR_TESTNET_NETWORK_ID: | |
return MPC_SIGNER_TESTNET; | |
case NEAR_MAINNET_NETWORK_ID: | |
return MPC_SIGNER_MAINNET; | |
default: | |
throw new Error(`Unsupported network: ${network}`); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment