1+ import * as Address from 'ox/Address'
2+ import * as Hex from 'ox/Hex'
3+ import * as PublicKey from 'ox/PublicKey'
14import { SignatureEnvelope , type TokenId } from 'ox/tempo'
25import { getCode } from '../actions/public/getCode.js'
36import { verifyHash } from '../actions/public/verifyHash.js'
@@ -8,8 +11,10 @@ import { defineTransaction } from '../utils/formatters/transaction.js'
811import { defineTransactionReceipt } from '../utils/formatters/transactionReceipt.js'
912import { defineTransactionRequest } from '../utils/formatters/transactionRequest.js'
1013import { getAction } from '../utils/getAction.js'
14+ import { keccak256 } from '../utils/hash/keccak256.js'
1115import type { SerializeTransactionFn } from '../utils/transaction/serializeTransaction.js'
1216import type { Account } from './Account.js'
17+ import { getMetadata } from './actions/accessKey.js'
1318import * as Formatters from './Formatters.js'
1419import * as Concurrent from './internal/concurrent.js'
1520import * as Transaction from './Transaction.js'
@@ -89,18 +94,53 @@ export const chainConfig = {
8994 Transaction . serialize ( transaction , signature ) ) as SerializeTransactionFn ,
9095 } ,
9196 async verifyHash ( client , parameters ) {
92- const { address, hash, signature } = parameters
97+ const { address, hash, signature, mode } = parameters
98+
99+ const envelope = ( ( ) => {
100+ if ( typeof signature !== 'string' ) return
101+ try {
102+ return SignatureEnvelope . deserialize ( signature )
103+ } catch {
104+ return undefined
105+ }
106+ } ) ( )
93107
94108 // `verifyHash` supports "signature envelopes" (a Tempo proposal) to natively verify arbitrary
95109 // envelope-compatible (WebAuthn, P256, etc.) signatures.
96- // We can directly verify stateless, non-keychain signature envelopes without a
97- // network request to the chain.
98- if (
99- typeof signature === 'string' &&
100- signature . endsWith ( SignatureEnvelope . magicBytes . slice ( 2 ) )
101- ) {
102- const envelope = SignatureEnvelope . deserialize ( signature )
103- if ( envelope . type !== 'keychain' ) {
110+ if ( envelope ) {
111+ // Access key (keychain) signature verification: check the key is
112+ // authorized, not expired, and not revoked on the AccountKeychain.
113+ if ( envelope ?. type === 'keychain' && mode === 'allowAccessKey' ) {
114+ const accessKeyAddress = Address . fromPublicKey (
115+ PublicKey . from ( envelope . inner . publicKey as PublicKey . PublicKey ) ,
116+ )
117+
118+ const keyInfo = await getMetadata ( client , {
119+ account : address ,
120+ accessKey : accessKeyAddress ,
121+ blockNumber : parameters . blockNumber ,
122+ blockTag : parameters . blockTag ,
123+ } as never )
124+
125+ if ( keyInfo . isRevoked ) return false
126+ if ( keyInfo . expiry <= BigInt ( Math . floor ( Date . now ( ) / 1000 ) ) )
127+ return false
128+
129+ // For v2 keychain envelopes, the inner signature signs
130+ // keccak256(0x04 || hash || userAddress).
131+ const innerPayload =
132+ envelope . version === 'v2'
133+ ? keccak256 ( Hex . concat ( '0x04' , hash , address ) )
134+ : hash
135+ return SignatureEnvelope . verify ( envelope . inner , {
136+ address : accessKeyAddress ,
137+ payload : innerPayload ,
138+ } )
139+ }
140+
141+ // Stateless, non-keychain signature envelopes (P256, WebAuthn) can be
142+ // verified directly without a network request.
143+ if ( envelope . type === 'p256' || envelope . type === 'webAuthn' ) {
104144 const code = await getCode ( client , {
105145 address,
106146 blockNumber : parameters . blockNumber ,
0 commit comments