ox
Version:
573 lines • 18.9 kB
TypeScript
import * as Address from '../core/Address.js';
import type * as Bytes from '../core/Bytes.js';
import * as Errors from '../core/Errors.js';
import * as Hex from '../core/Hex.js';
import type { Assign, Compute, IsNarrowable, OneOf, PartialBy, UnionPartialBy } from '../core/internal/types.js';
import type * as PublicKey from '../core/PublicKey.js';
import * as Signature from '../core/Signature.js';
import type * as WebAuthnP256 from '../core/WebAuthnP256.js';
/** Serialized magic identifier for Tempo signature envelopes. */
export declare const magicBytes = "0x7777777777777777777777777777777777777777777777777777777777777777";
/**
* Statically determines the signature type of an envelope at compile time.
*
* @example
* ```ts twoslash
* import type { SignatureEnvelope } from 'ox/tempo'
*
* type Type = SignatureEnvelope.GetType<{ r: bigint; s: bigint; yParity: number }>
* // @log: 'secp256k1'
* ```
*/
export type GetType<envelope extends PartialBy<SignatureEnvelope, 'type'> | unknown> = unknown extends envelope ? envelope extends unknown ? Type : never : envelope extends {
type: infer T extends Type;
} ? T : envelope extends {
signature: {
r: bigint;
s: bigint;
};
prehash: boolean;
publicKey: PublicKey.PublicKey;
} ? 'p256' : envelope extends {
signature: {
r: bigint;
s: bigint;
};
metadata: any;
publicKey: PublicKey.PublicKey;
} ? 'webAuthn' : envelope extends {
r: bigint;
s: bigint;
yParity: number;
} ? 'secp256k1' : envelope extends {
signature: {
r: bigint;
s: bigint;
yParity: number;
};
} ? 'secp256k1' : envelope extends {
userAddress: Address.Address;
} ? 'keychain' : never;
/**
* Represents a signature envelope that can contain different signature types.
*
* Tempo transactions support multiple signature types, each with different wire formats:
*
* - **secp256k1** (no type prefix, 65 bytes): Standard Ethereum ECDSA signature. The sender
* address is recovered via `ecrecover`. Base transaction cost: 21,000 gas.
*
* - **p256** (type `0x01`, 130 bytes): P256/secp256r1 curve signature for passkey accounts.
* Includes embedded public key (64 bytes) and prehash flag. Enables native WebCrypto
* key support. Additional gas cost: +5,000 gas over secp256k1.
*
* - **webAuthn** (type `0x02`, 129-2049 bytes): WebAuthn signature with authenticator data
* and clientDataJSON. Enables browser passkey authentication. The signature is also
* charged as calldata (16 gas/non-zero byte, 4 gas/zero byte).
*
* - **keychain** (type `0x03`): Access key signature that wraps an inner signature (secp256k1,
* p256, or webAuthn). Format: `0x03` + user_address (20 bytes) + inner signature. The
* protocol validates the access key authorization via the AccountKeychain precompile.
*
* [Signature Types Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
*/
export type SignatureEnvelope<bigintType = bigint, numberType = number> = OneOf<Secp256k1<bigintType, numberType> | P256<bigintType, numberType> | WebAuthn<bigintType, numberType> | Keychain<bigintType, numberType>>;
/**
* RPC-formatted signature envelope.
*/
export type SignatureEnvelopeRpc = OneOf<Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc>;
export type Keychain<bigintType = bigint, numberType = number> = {
/** Root account address that this transaction is being executed for */
userAddress: Address.Address;
/** The actual signature from the access key (can be Secp256k1, P256, or WebAuthn) */
inner: SignatureEnvelope<bigintType, numberType>;
type: 'keychain';
};
export type KeychainRpc = {
type: 'keychain';
userAddress: Address.Address;
signature: SignatureEnvelopeRpc;
};
export type P256<bigintType = bigint, numberType = number> = {
prehash: boolean;
publicKey: PublicKey.PublicKey;
signature: Signature.Signature<false, bigintType, numberType>;
type: 'p256';
};
export type P256Rpc = {
preHash: boolean;
pubKeyX: Hex.Hex;
pubKeyY: Hex.Hex;
r: Hex.Hex;
s: Hex.Hex;
type: 'p256';
};
export type Secp256k1<bigintType = bigint, numberType = number> = {
signature: Signature.Signature<true, bigintType, numberType>;
type: 'secp256k1';
};
export type Secp256k1Rpc = Compute<Signature.Rpc<true> & {
v?: Hex.Hex | undefined;
type: 'secp256k1';
}>;
export type Secp256k1Flat<bigintType = bigint, numberType = number> = Signature.Signature<true, bigintType, numberType> & {
type?: 'secp256k1' | undefined;
};
export type WebAuthn<bigintType = bigint, numberType = number> = {
metadata: Pick<WebAuthnP256.SignMetadata, 'authenticatorData' | 'clientDataJSON'>;
signature: Signature.Signature<false, bigintType, numberType>;
publicKey: PublicKey.PublicKey;
type: 'webAuthn';
};
export type WebAuthnRpc = {
pubKeyX: Hex.Hex;
pubKeyY: Hex.Hex;
r: Hex.Hex;
s: Hex.Hex;
type: 'webAuthn';
webauthnData: Hex.Hex;
};
/** Hex-encoded serialized signature envelope. */
export type Serialized = Hex.Hex;
/** List of supported signature types. */
export declare const types: readonly ["secp256k1", "p256", "webAuthn"];
/** Union type of supported signature types. */
export type Type = (typeof types)[number];
/**
* Asserts that a {@link ox#SignatureEnvelope.SignatureEnvelope} is valid.
*
* @example
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
*
* SignatureEnvelope.assert({
* type: 'secp256k1',
* signature: {
* r: 0n,
* s: 0n,
* yParity: 0,
* },
* })
* ```
*
* @param envelope - The signature envelope to assert.
* @throws `CoercionError` if the envelope type cannot be determined.
*/
export declare function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void;
export declare namespace assert {
type ErrorType = CoercionError | MissingPropertiesError | Signature.assert.ErrorType | Errors.GlobalErrorType;
}
/**
* Deserializes a hex-encoded signature envelope into a typed signature object.
*
* Wire format detection:
* - 65 bytes (no prefix): secp256k1 signature
* - Type `0x01` + 129 bytes: P256 signature (r, s, pubKeyX, pubKeyY, prehash)
* - Type `0x02` + variable: WebAuthn signature (webauthnData, r, s, pubKeyX, pubKeyY)
* - Type `0x03` + 20 bytes + inner: Keychain signature (userAddress + inner signature)
*
* [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
*
* @example
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
*
* const envelope = SignatureEnvelope.deserialize('0x...')
* ```
*
* @param serialized - The hex-encoded signature envelope to deserialize.
* @returns The deserialized signature envelope.
* @throws `CoercionError` if the serialized value cannot be coerced to a valid signature envelope.
*/
export declare function deserialize(value: Serialized): SignatureEnvelope;
/**
* Coerces a value to a signature envelope.
*
* Accepts either a serialized hex string or an existing signature envelope object.
* Use this to wrap raw signatures from {@link ox#Secp256k1.(sign:function)}, {@link ox#P256.(sign:function)},
* {@link ox#WebCryptoP256.(sign:function)}, or {@link ox#WebAuthnP256.(sign:function)} into the envelope format
* required by Tempo transactions.
*
* [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
*
* @example
* ### Secp256k1
*
* Standard Ethereum ECDSA signature using the secp256k1 curve.
*
* ```ts twoslash
* import { Secp256k1 } from 'ox'
* import { SignatureEnvelope } from 'ox/tempo'
*
* const privateKey = Secp256k1.randomPrivateKey()
* const signature = Secp256k1.sign({ payload: '0xdeadbeef', privateKey })
*
* const envelope = SignatureEnvelope.from(signature)
* ```
*
* @example
* ### P256
*
* ECDSA signature using the P-256 (secp256r1) curve. Requires embedding the
* public key.
*
* ```ts twoslash
* import { P256 } from 'ox'
* import { SignatureEnvelope } from 'ox/tempo'
*
* const { privateKey, publicKey } = P256.createKeyPair()
* const signature = P256.sign({ payload: '0xdeadbeef', privateKey })
*
* const envelope = SignatureEnvelope.from({
* signature,
* publicKey,
* })
* ```
*
* @example
* ### P256 (WebCrypto)
*
* When using WebCrypto keys, `prehash` must be `true` since WebCrypto always
* SHA256 hashes the digest before signing.
*
* ```ts twoslash
* // @noErrors
* import { WebCryptoP256 } from 'ox'
* import { SignatureEnvelope } from 'ox/tempo'
*
* const { privateKey, publicKey } = await WebCryptoP256.createKeyPair()
* const signature = await WebCryptoP256.sign({ payload: '0xdeadbeef', privateKey })
*
* const envelope = SignatureEnvelope.from({
* signature,
* publicKey,
* prehash: true,
* })
* ```
*
* @example
* ### WebAuthn
*
* Passkey-based signature using WebAuthn. Includes authenticator metadata
* (authenticatorData and clientDataJSON) along with the P-256 signature and
* public key.
*
* ```ts twoslash
* // @noErrors
* import { WebAuthnP256 } from 'ox'
* import { SignatureEnvelope } from 'ox/tempo'
*
* const credential = await WebAuthnP256.createCredential({
* name: 'Example',
* })
*
* const { metadata, signature } = await WebAuthnP256.sign({
* challenge: '0xdeadbeef',
* credentialId: credential.id,
* })
*
* const envelope = SignatureEnvelope.from({
* signature,
* publicKey: credential.publicKey,
* metadata,
* })
* ```
*
* @example
* ### Keychain
*
* Wraps another signature type with a user address, used for delegated signing
* via access keys on behalf of a root account.
*
* ```ts twoslash
* import { Secp256k1 } from 'ox'
* import { SignatureEnvelope } from 'ox/tempo'
*
* const privateKey = Secp256k1.randomPrivateKey()
* const signature = Secp256k1.sign({ payload: '0xdeadbeef', privateKey })
*
* const envelope = SignatureEnvelope.from({
* userAddress: '0x1234567890123456789012345678901234567890',
* inner: SignatureEnvelope.from(signature),
* })
* ```
*
* @param value - The value to coerce (either a hex string or signature envelope).
* @returns The signature envelope.
*/
export declare function from<const value extends from.Value>(value: value | from.Value): from.ReturnValue<value>;
export declare namespace from {
type Value = UnionPartialBy<SignatureEnvelope, 'prehash' | 'type'> | Secp256k1Flat | Serialized;
type ReturnValue<value extends Value> = Compute<OneOf<value extends Serialized ? SignatureEnvelope : value extends Secp256k1Flat ? Secp256k1 : IsNarrowable<value, SignatureEnvelope> extends true ? SignatureEnvelope : Assign<value, {
readonly type: GetType<value>;
}>>>;
}
/**
* Converts an RPC-formatted signature envelope to a typed signature envelope.
*
* @example
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
*
* const envelope = SignatureEnvelope.fromRpc({
* r: '0x0',
* s: '0x0',
* yParity: '0x0',
* type: 'secp256k1',
* })
* ```
*
* @param envelope - The RPC signature envelope to convert.
* @returns The signature envelope with bigint values.
*/
export declare function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope;
export declare namespace fromRpc {
type ErrorType = CoercionError | InvalidSerializedError | Signature.fromRpc.ErrorType | Errors.GlobalErrorType;
}
/**
* Determines the signature type of an envelope.
*
* @example
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
*
* const type = SignatureEnvelope.getType({
* signature: { r: 0n, s: 0n, yParity: 0 },
* })
* // @log: 'secp256k1'
* ```
*
* @param envelope - The signature envelope to inspect.
* @returns The signature type ('secp256k1', 'p256', or 'webAuthn').
* @throws `CoercionError` if the envelope type cannot be determined.
*/
export declare function getType<envelope extends PartialBy<SignatureEnvelope, 'type'> | Secp256k1Flat | unknown>(envelope: envelope): GetType<envelope>;
/**
* Serializes a signature envelope to a hex-encoded string.
*
* Wire format:
* - secp256k1: 65 bytes (no type prefix, for backward compatibility)
* - P256: `0x01` + r (32) + s (32) + pubKeyX (32) + pubKeyY (32) + prehash (1) = 130 bytes
* - WebAuthn: `0x02` + webauthnData (variable) + r (32) + s (32) + pubKeyX (32) + pubKeyY (32)
* - Keychain: `0x03` + userAddress (20) + inner signature (recursive)
*
* [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
*
* @example
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
*
* const serialized = SignatureEnvelope.serialize({
* signature: { r: 0n, s: 0n, yParity: 0 },
* type: 'secp256k1',
* })
* ```
*
* @param envelope - The signature envelope to serialize.
* @returns The hex-encoded serialized signature.
* @throws `CoercionError` if the envelope cannot be serialized.
*/
export declare function serialize(envelope: UnionPartialBy<SignatureEnvelope, 'prehash'>, options?: serialize.Options): Serialized;
export declare namespace serialize {
type Options = {
/**
* Whether to serialize the signature envelope with the Tempo magic identifier.
* This is useful for being able to distinguish between Tempo and non-Tempo (e.g. ERC-1271) signatures.
*/
magic?: boolean | undefined;
};
}
/**
* Converts a signature envelope to RPC format.
*
* @example
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
*
* const rpc = SignatureEnvelope.toRpc({
* signature: { r: 0n, s: 0n, yParity: 0 },
* type: 'secp256k1',
* })
* ```
*
* @param envelope - The signature envelope to convert.
* @returns The RPC signature envelope with hex values.
*/
export declare function toRpc(envelope: SignatureEnvelope): SignatureEnvelopeRpc;
export declare namespace toRpc {
type ErrorType = CoercionError | Signature.toRpc.ErrorType | Errors.GlobalErrorType;
}
/**
* Validates a signature envelope. Returns `true` if the envelope is valid, `false` otherwise.
*
* @example
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
*
* const valid = SignatureEnvelope.validate({
* signature: { r: 0n, s: 0n, yParity: 0 },
* type: 'secp256k1',
* })
* // @log: true
* ```
*
* @param envelope - The signature envelope to validate.
* @returns `true` if valid, `false` otherwise.
*/
export declare function validate(envelope: PartialBy<SignatureEnvelope, 'type'>): boolean;
export declare namespace validate {
type ErrorType = Errors.GlobalErrorType;
}
/**
* Verifies a signature envelope against a digest/payload.
*
* Supports `secp256k1`, `p256`, and `webAuthn` signature types.
*
* :::warning
* `keychain` signatures are not supported and will throw an error.
* :::
*
* @example
* ### Secp256k1
*
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
* import { Secp256k1 } from 'ox'
*
* const privateKey = Secp256k1.randomPrivateKey()
* const publicKey = Secp256k1.getPublicKey({ privateKey })
* const payload = '0xdeadbeef'
*
* const signature = Secp256k1.sign({ payload, privateKey })
* const envelope = SignatureEnvelope.from(signature)
*
* const valid = SignatureEnvelope.verify(envelope, {
* payload,
* publicKey,
* })
* // @log: true
* ```
*
* @example
* ### P256
*
* For P256 signatures, the `address` or `publicKey` must match the embedded
* public key in the signature envelope.
*
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
* import { P256 } from 'ox'
*
* const privateKey = P256.randomPrivateKey()
* const publicKey = P256.getPublicKey({ privateKey })
* const payload = '0xdeadbeef'
*
* const signature = P256.sign({ payload, privateKey })
* const envelope = SignatureEnvelope.from({ prehash: false, publicKey, signature })
*
* const valid = SignatureEnvelope.verify(envelope, {
* payload,
* publicKey,
* })
* // @log: true
* ```
*
* @example
* ### WebCryptoP256
*
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
* import { WebCryptoP256 } from 'ox'
*
* const { privateKey, publicKey } = await WebCryptoP256.createKeyPair()
* const payload = '0xdeadbeef'
*
* const signature = await WebCryptoP256.sign({ payload, privateKey })
* const envelope = SignatureEnvelope.from({ prehash: true, publicKey, signature })
*
* const valid = SignatureEnvelope.verify(envelope, {
* payload,
* publicKey,
* })
* // @log: true
* ```
*
* @example
* ### WebAuthnP256
*
* ```ts twoslash
* import { SignatureEnvelope } from 'ox/tempo'
* import { WebAuthnP256 } from 'ox'
*
* const credential = await WebAuthnP256.createCredential({ name: 'Example' })
* const payload = '0xdeadbeef'
*
* const { metadata, signature } = await WebAuthnP256.sign({
* challenge: payload,
* credentialId: credential.id,
* })
* const envelope = SignatureEnvelope.from({
* metadata,
* signature,
* publicKey: credential.publicKey,
* })
*
* const valid = SignatureEnvelope.verify(envelope, {
* payload,
* publicKey: credential.publicKey,
* })
* // @log: true
* ```
*
* @param parameters - Verification parameters.
* @returns `true` if the signature is valid, `false` otherwise.
*/
export declare function verify(signature: SignatureEnvelope, parameters: verify.Parameters): boolean;
export declare namespace verify {
type Parameters = {
/** Payload that was signed. */
payload: Hex.Hex | Bytes.Bytes;
} & OneOf<{
/** Public key that signed the payload. */
publicKey: PublicKey.PublicKey;
} | {
/** Address that signed the payload. */
address: Address.Address;
}>;
}
/**
* Error thrown when a signature envelope cannot be coerced to a valid type.
*/
export declare class CoercionError extends Errors.BaseError {
readonly name = "SignatureEnvelope.CoercionError";
constructor({ envelope }: {
envelope: unknown;
});
}
/**
* Error thrown when a signature envelope is missing required properties.
*/
export declare class MissingPropertiesError extends Errors.BaseError {
readonly name = "SignatureEnvelope.MissingPropertiesError";
constructor({ envelope, missing, type, }: {
envelope: unknown;
missing: string[];
type: Type;
});
}
/**
* Error thrown when a serialized signature envelope cannot be deserialized.
*/
export declare class InvalidSerializedError extends Errors.BaseError {
readonly name = "SignatureEnvelope.InvalidSerializedError";
constructor({ reason, serialized, }: {
reason: string;
serialized: Hex.Hex;
});
}
/**
* Error thrown when a signature envelope fails to verify.
*/
export declare class VerificationError extends Errors.BaseError {
readonly name = "SignatureEnvelope.VerificationError";
}
//# sourceMappingURL=SignatureEnvelope.d.ts.map