Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 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
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions packages/common/src/eips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,14 @@ export const eipsDict: EIPsDict = {
*/
requiredEIPs: [2929],
},
/***
* Description: Precompile for secp256r1 Curve Support
* URL: https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md
* Status: Final
*/
7212: {
minimumHardfork: Hardfork.Prague,
},
/**
* Description : Increase the MAX_EFFECTIVE_BALANCE -> Execution layer triggered consolidations (experimental)
* URL : https://eips.ethereum.org/EIPS/eip-7251
Expand Down
1 change: 1 addition & 0 deletions packages/evm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@types/minimist": "^1.2.5",
"@types/node-dir": "^0.0.37",
"benchmark": "^2.1.4",
"cbor": "^10.0.3",
"level": "^9.0.0",
"mcl-wasm": "^1.8.0",
"memory-level": "^3.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export class EVM implements EVMInterface {
const supportedEIPs = [
663, 1153, 1559, 2537, 2565, 2718, 2929, 2930, 2935, 3198, 3529, 3540, 3541, 3607, 3651, 3670,
3855, 3860, 4200, 4399, 4750, 4788, 4844, 4895, 5133, 5450, 5656, 6110, 6206, 6780, 6800,
7002, 7069, 7251, 7480, 7516, 7620, 7685, 7691, 7692, 7698, 7702, 7709,
7002, 7069, 7212, 7251, 7480, 7516, 7620, 7685, 7691, 7692, 7698, 7702, 7709,
]

for (const eip of this.common.eips()) {
Expand Down
7 changes: 7 additions & 0 deletions packages/evm/src/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,13 @@ export const paramsEVM: ParamsDict = {
datacopyGas: 3, // Base fee of the DATACOPY opcode
},
/**
* Precompile for secp256r1 Curve Support
*/
7212: {
// gasPrices
p256verifyGas: 3450, // Base fee of the P256VERIFY precompile
},
/**
. * BLOBBASEFEE opcode
. */
7516: {
Expand Down
56 changes: 56 additions & 0 deletions packages/evm/src/precompiles/12-p256-verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { BIGINT_0, BIGINT_1, bytesToBigInt, bytesToHex } from '@ethereumjs/util'

import { p256 } from '@noble/curves/p256.js'
import { EVMError } from '../errors.ts'
import { EVMErrorResult, OOGResult } from '../evm.ts'
import { getPrecompileName } from './index.ts'
import { gasLimitCheck } from './util.ts'

import type { ExecResult } from '../types.ts'
import type { PrecompileInput } from './types.ts'

export const p256_MODULUS = BigInt(
'0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff',
)
export async function precompile12(opts: PrecompileInput): Promise<ExecResult> {
const pName = getPrecompileName('12')

const gasUsed = opts.common.param('p256verifyGas')
if (!gasLimitCheck(opts, gasUsed, pName)) {
return OOGResult(opts.gasLimit)
}

if (opts.data.length !== 160) {
opts._debug?.(`${pName} failed: Invalid input length: expeted 160, got ${opts.data.length}`)
return EVMErrorResult(new EVMError(EVMError.errorMessages.INVALID_INPUT_LENGTH), opts.gasLimit)
}

const hash = bytesToHex(opts.data.subarray(0, 32)) // hash of signed data
const r = bytesToBigInt(opts.data.subarray(32, 64)) // r component of signature
const s = bytesToBigInt(opts.data.subarray(64, 96)) // s component of signature
const x = bytesToBigInt(opts.data.subarray(96, 128)) // x coordinate of public key
const y = bytesToBigInt(opts.data.subarray(128, 160)) // y coordinate of public key

if (r >= p256.CURVE.n || s >= p256.CURVE.n || r < BIGINT_1 || s < BIGINT_1) {
opts._debug?.(`${pName} failed: Invalid signature`)
return EVMErrorResult(new EVMError(EVMError.errorMessages.INVALID_INPUTS), opts.gasLimit)
}

if ((x <= BIGINT_0 && y <= BIGINT_0) || (x >= p256_MODULUS && y >= p256_MODULUS)) {
opts._debug?.(`${pName} failed: Invalid public key`)
return EVMErrorResult(new EVMError(EVMError.errorMessages.INVALID_INPUTS), opts.gasLimit)
}

const isValid = p256.verify(
{ r, s },
hash.slice(2),
p256.ProjectivePoint.fromAffine({ x, y }).toHex(),
)

const returnValue = new Uint8Array(32)
returnValue[0] = isValid ? 1 : 0
return {
executionGasUsed: gasUsed,
returnValue,
}
}
11 changes: 11 additions & 0 deletions packages/evm/src/precompiles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { precompile08 } from './08-bn254-pairing.ts'
import { precompile09 } from './09-blake2f.ts'
import { precompile10 } from './10-bls12-map-fp-to-g1.ts'
import { precompile11 } from './11-bls12-map-fp2-to-g2.ts'
import { precompile12 } from './12-p256-verify.ts'
import { MCLBLS, NobleBLS } from './bls12_381/index.ts'
import { NobleBN254, RustBN254 } from './bn254/index.ts'

Expand Down Expand Up @@ -213,6 +214,15 @@ const precompileEntries: PrecompileEntry[] = [
precompile: precompile11,
name: 'BLS12_MAP_FP_TO_G2 (0x11)',
},
{
address: BYTES_19 + '12',
check: {
type: PrecompileAvailabilityCheck.EIP,
param: 7212,
},
precompile: precompile12,
name: 'P256_VERIFY (0x12)',
},
]

const precompiles: Precompiles = {
Expand All @@ -233,6 +243,7 @@ const precompiles: Precompiles = {
[BYTES_19 + '0f']: precompile0f,
[BYTES_19 + '10']: precompile10,
[BYTES_19 + '11']: precompile11,
[BYTES_19 + '12']: precompile12,
}

type DeletePrecompile = {
Expand Down
124 changes: 124 additions & 0 deletions packages/evm/test/precompiles/12-p256verify.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import {
BIGINT_0,
bigIntToBytes,
bytesToBigInt,
concatBytes,
hexToBytes,
setLengthLeft,
unpadBytes,
} from '@ethereumjs/util'
import * as cbor from 'cbor'
import { assert, describe, it } from 'vitest'
import { createEVM, getActivePrecompiles } from '../../src/index.ts'

import type { PrefixedHexString } from '@ethereumjs/util'
import { p256 } from '@noble/curves/p256'
import { base64urlnopad } from '@scure/base'
import { sha256 } from 'ethereum-cryptography/sha256'
import type { PrecompileInput } from '../../src/index.ts'
describe('Precompiles: p256 verify', () => {
it('should work', async () => {
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Prague, eips: [7212] })
const evm = await createEVM({
common,
})
const addressStr = '0000000000000000000000000000000000000012'
const p256Verify = getActivePrecompiles(common).get(addressStr)!

// Random inputs
const testCase = {
hash: '0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' as PrefixedHexString,
r: '0x0000000000000000000000000000000000000000000000000000000000000002' as PrefixedHexString,
s: '0x0000000000000000000000000000000000000000000000000000000000000001' as PrefixedHexString,
x: '0x0000000000000000000000000000000000000000000000000000000000000002' as PrefixedHexString,
y: '0x0000000000000000000000000000000000000000000000000000000000000003' as PrefixedHexString,
}

const opts: PrecompileInput = {
data: concatBytes(
hexToBytes(testCase.hash),
hexToBytes(testCase.r),
hexToBytes(testCase.s),
hexToBytes(testCase.x),
hexToBytes(testCase.y),
),
gasLimit: 0xfffffffffn,
_EVM: evm,
common,
}

const res = await p256Verify(opts)
assert.strictEqual(
bytesToBigInt(unpadBytes(res.returnValue.slice(32))),
BIGINT_0,
'p256 verify precompile fails to verify nonsense inputs',
)

// webauthn generated inputs from https://opotonniee.github.io/webauthn-playground/

// Registration output with public key
// rpIdHash: t8DGRTBfls-BhOH2QC404lvdhe_t2_NkvM0nQWEEADc
// Flags: 0b01011101 (User Present, User Verified, Backup Eligible, Backed-up, Attested data)
// Counter: 0
// Attested cred data:
// AAGUID: ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4
// CredId: 5CfOKpISziPZT87xaQinBQ
// Pub Key: pQECAyYgASFYIMqn0ISx7iJ1Bq_l2Ektnx8EP4vBunTtIwtBVJXx6I5hIlgg5ZeUA4PKKzIGeolx-8tXoP21R-eNURT22ezmUxQxshU

// Authentication/signature output
// {
// "clientExtensionResults": {},
// "rawId": "5CfOKpISziPZT87xaQinBQ",
// "response": {
// "authenticatorData": "t8DGRTBfls-BhOH2QC404lvdhe_t2_NkvM0nQWEEADcdAAAAAA",
// "signature": "MEUCIQCAq9bBDIlH3008aD_p1dnQEnAPFFoumuttjFO5B0txGgIgXgkv9h8OorysU8YK972hnNQLvjdob1dUSI7nTH4-yHI",
// "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiRUdZdEFNZ2k4QjJFeTFGTlZmVkY5M201TEV6X0Nmd1R5MDBXMnpvUEVONCIsIm9yaWdpbiI6Imh0dHBzOi8vb3BvdG9ubmllZS5naXRodWIuaW8iLCJjcm9zc09yaWdpbiI6ZmFsc2V9"
// },
// "authenticatorAttachment": "cross-platform",
// "id": "5CfOKpISziPZT87xaQinBQ",
// "type": "public-key"
// }

const webAuthnInput = {
publicKey:
'pQECAyYgASFYINYCnLjxaitogSl7vSSVDixB6JojPCLveobOmmTgV8UMIlggznl2Cy2oqSwvWN5M17E_r2f82QM-sazcYaHj43IADcs',
signature:
'MEYCIQDW2X6Bg0BfZRCpWPsFUD1btTKjn25MZUGQczm8Q4-n9wIhALFxp95N-yuxRKOpBnQmOjneUjT9s-mkmEl_rtfaJSs4',
authenticatorData: 't8DGRTBfls-BhOH2QC404lvdhe_t2_NkvM0nQWEEADcdAAAAAA',
clientDataJson:
'eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiRUdZdEFNZ2k4QjJFeTFGTlZmVkY5M201TEV6X0Nmd1R5MDBXMnpvUEVONCIsIm9yaWdpbiI6Imh0dHBzOi8vb3BvdG9ubmllZS5naXRodWIuaW8iLCJjcm9zc09yaWdpbiI6ZmFsc2V9',
}

const decoded = base64urlnopad.decode(webAuthnInput.publicKey)
// Use cbor to decode the COSE key
const coseKey = cbor.decodeFirstSync(decoded)
// COSE keys for x and y are -2 and -3
const x = Uint8Array.from(coseKey.get(-2))
const y = Uint8Array.from(coseKey.get(-3))

const hash = sha256(base64urlnopad.decode(webAuthnInput.clientDataJson))

const sig = p256.Signature.fromDER(base64urlnopad.decode(webAuthnInput.signature))

const webAuthnInputOpts: PrecompileInput = {
data: concatBytes(
hash,
setLengthLeft(bigIntToBytes(sig.r, true), 32),
setLengthLeft(bigIntToBytes(sig.s, true), 32),
x,
y,
),
gasLimit: 0xfffffffffn,
common,
_EVM: evm,
}

const webAuthnInputRes = await p256Verify(webAuthnInputOpts)
assert.strictEqual(

Check failure on line 118 in packages/evm/test/precompiles/12-p256verify.spec.ts

View workflow job for this annotation

GitHub Actions / evm / test-evm

test/precompiles/12-p256verify.spec.ts > Precompiles: p256 verify > should work

AssertionError: p256-verify precompile verifies webauthn inputs: expected +0 to equal 1 - Expected + Received - 1 + 0 ❯ test/precompiles/12-p256verify.spec.ts:118:12
webAuthnInputRes.returnValue[0],
1,
'p256-verify precompile verifies webauthn inputs',
)
})
})
Loading