"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VerificationError = exports.InvalidSerializedError = exports.MissingPropertiesError = exports.CoercionError = exports.types = exports.magicBytes = void 0;
exports.assert = assert;
exports.extractAddress = extractAddress;
exports.extractPublicKey = extractPublicKey;
exports.deserialize = deserialize;
exports.from = from;
exports.fromRpc = fromRpc;
exports.getType = getType;
exports.serialize = serialize;
exports.toRpc = toRpc;
exports.validate = validate;
exports.verify = verify;
const Address = require("../core/Address.js");
const Errors = require("../core/Errors.js");
const Hex = require("../core/Hex.js");
const Json = require("../core/Json.js");
const ox_P256 = require("../core/P256.js");
const ox_Secp256k1 = require("../core/Secp256k1.js");
const Signature = require("../core/Signature.js");
const ox_WebAuthnP256 = require("../core/WebAuthnP256.js");
const serializedP256Type = '0x01';
const serializedWebAuthnType = '0x02';
const serializedKeychainType = '0x03';
const serializedKeychainV2Type = '0x04';
exports.magicBytes = '0x7777777777777777777777777777777777777777777777777777777777777777';
exports.types = ['secp256k1', 'p256', 'webAuthn'];
function assert(envelope) {
    const type = getType(envelope);
    if (type === 'secp256k1') {
        const secp256k1 = envelope;
        Signature.assert(secp256k1.signature);
        return;
    }
    if (type === 'p256') {
        const p256 = envelope;
        const missing = [];
        if (typeof p256.signature?.r !== 'bigint')
            missing.push('signature.r');
        if (typeof p256.signature?.s !== 'bigint')
            missing.push('signature.s');
        if (typeof p256.prehash !== 'boolean')
            missing.push('prehash');
        if (!p256.publicKey)
            missing.push('publicKey');
        else {
            if (typeof p256.publicKey.x !== 'bigint')
                missing.push('publicKey.x');
            if (typeof p256.publicKey.y !== 'bigint')
                missing.push('publicKey.y');
        }
        if (missing.length > 0)
            throw new MissingPropertiesError({ envelope, missing, type: 'p256' });
        return;
    }
    if (type === 'webAuthn') {
        const webauthn = envelope;
        const missing = [];
        if (typeof webauthn.signature?.r !== 'bigint')
            missing.push('signature.r');
        if (typeof webauthn.signature?.s !== 'bigint')
            missing.push('signature.s');
        if (!webauthn.metadata)
            missing.push('metadata');
        else {
            if (!webauthn.metadata.authenticatorData)
                missing.push('metadata.authenticatorData');
            if (!webauthn.metadata.clientDataJSON)
                missing.push('metadata.clientDataJSON');
        }
        if (!webauthn.publicKey)
            missing.push('publicKey');
        else {
            if (typeof webauthn.publicKey.x !== 'bigint')
                missing.push('publicKey.x');
            if (typeof webauthn.publicKey.y !== 'bigint')
                missing.push('publicKey.y');
        }
        if (missing.length > 0)
            throw new MissingPropertiesError({ envelope, missing, type: 'webAuthn' });
        return;
    }
    if (type === 'keychain') {
        const keychain = envelope;
        assert(keychain.inner);
        return;
    }
}
function extractAddress(options) {
    const { signature, root } = options;
    if (signature.type === 'keychain') {
        if (root)
            return signature.userAddress;
        return extractAddress({ ...options, signature: signature.inner });
    }
    return Address.fromPublicKey(extractPublicKey(options));
}
function extractPublicKey(options) {
    const { payload, signature } = options;
    switch (signature.type) {
        case 'secp256k1':
            return ox_Secp256k1.recoverPublicKey({
                payload,
                signature: signature.signature,
            });
        case 'p256':
        case 'webAuthn':
            return signature.publicKey;
        case 'keychain':
            return extractPublicKey({ payload, signature: signature.inner });
    }
}
function deserialize(value) {
    const serialized = value.endsWith(exports.magicBytes.slice(2))
        ? Hex.slice(value, 0, -Hex.size(exports.magicBytes))
        : value;
    const size = Hex.size(serialized);
    if (size === 65) {
        const signature = Signature.fromHex(serialized);
        Signature.assert(signature);
        return { signature, type: 'secp256k1' };
    }
    const typeId = Hex.slice(serialized, 0, 1);
    const data = Hex.slice(serialized, 1);
    const dataSize = Hex.size(data);
    if (typeId === serializedP256Type) {
        if (dataSize !== 129)
            throw new InvalidSerializedError({
                reason: `Invalid P256 signature envelope size: expected 129 bytes, got ${dataSize} bytes`,
                serialized,
            });
        return {
            publicKey: {
                prefix: 4,
                x: Hex.toBigInt(Hex.slice(data, 64, 96)),
                y: Hex.toBigInt(Hex.slice(data, 96, 128)),
            },
            prehash: Hex.toNumber(Hex.slice(data, 128, 129)) !== 0,
            signature: {
                r: Hex.toBigInt(Hex.slice(data, 0, 32)),
                s: Hex.toBigInt(Hex.slice(data, 32, 64)),
            },
            type: 'p256',
        };
    }
    if (typeId === serializedWebAuthnType) {
        if (dataSize < 128)
            throw new InvalidSerializedError({
                reason: `Invalid WebAuthn signature envelope size: expected at least 128 bytes, got ${dataSize} bytes`,
                serialized,
            });
        const webauthnDataSize = dataSize - 128;
        const webauthnData = Hex.slice(data, 0, webauthnDataSize);
        let authenticatorData;
        let clientDataJSON;
        for (let split = 37; split < webauthnDataSize; split++) {
            const potentialJson = Hex.toString(Hex.slice(webauthnData, split));
            if (potentialJson.startsWith('{') && potentialJson.endsWith('}')) {
                try {
                    JSON.parse(potentialJson);
                    authenticatorData = Hex.slice(webauthnData, 0, split);
                    clientDataJSON = potentialJson;
                    break;
                }
                catch { }
            }
        }
        if (!authenticatorData || !clientDataJSON)
            throw new InvalidSerializedError({
                reason: 'Unable to parse WebAuthn metadata: could not extract valid authenticatorData and clientDataJSON',
                serialized,
            });
        return {
            publicKey: {
                prefix: 4,
                x: Hex.toBigInt(Hex.slice(data, webauthnDataSize + 64, webauthnDataSize + 96)),
                y: Hex.toBigInt(Hex.slice(data, webauthnDataSize + 96, webauthnDataSize + 128)),
            },
            metadata: {
                authenticatorData,
                clientDataJSON,
            },
            signature: {
                r: Hex.toBigInt(Hex.slice(data, webauthnDataSize, webauthnDataSize + 32)),
                s: Hex.toBigInt(Hex.slice(data, webauthnDataSize + 32, webauthnDataSize + 64)),
            },
            type: 'webAuthn',
        };
    }
    if (typeId === serializedKeychainType ||
        typeId === serializedKeychainV2Type) {
        const userAddress = Hex.slice(data, 0, 20);
        const inner = deserialize(Hex.slice(data, 20));
        return {
            userAddress,
            inner,
            type: 'keychain',
            version: typeId === serializedKeychainV2Type ? 'v2' : 'v1',
        };
    }
    throw new InvalidSerializedError({
        reason: `Unknown signature type identifier: ${typeId}. Expected ${serializedP256Type} (P256), ${serializedWebAuthnType} (WebAuthn), ${serializedKeychainType} (Keychain V1), or ${serializedKeychainV2Type} (Keychain V2)`,
        serialized,
    });
}
function from(value, options) {
    if (typeof value === 'string')
        return deserialize(value);
    if (typeof value === 'object' &&
        value !== null &&
        'r' in value &&
        's' in value &&
        'yParity' in value)
        return { signature: value, type: 'secp256k1' };
    const type = getType(value);
    return {
        ...value,
        ...(type === 'p256' ? { prehash: value.prehash } : {}),
        ...(type === 'keychain'
            ? {
                ...(!(typeof value === 'object' &&
                    value !== null &&
                    'version' in value &&
                    value.version)
                    ? { version: 'v2' }
                    : {}),
                ...(!(typeof value === 'object' && 'keyId' in value && value.keyId)
                    ? (() => {
                        const inner = value.inner;
                        if (inner.type === 'p256' || inner.type === 'webAuthn')
                            return { keyId: Address.fromPublicKey(inner.publicKey) };
                        if (inner.type === 'secp256k1' && options?.payload)
                            return {
                                keyId: Address.fromPublicKey(ox_Secp256k1.recoverPublicKey({
                                    payload: options.payload,
                                    signature: inner.signature,
                                })),
                            };
                        return {};
                    })()
                    : {}),
            }
            : {}),
        type,
    };
}
function fromRpc(envelope) {
    if (envelope.type === 'secp256k1')
        return {
            signature: Signature.fromRpc(envelope),
            type: 'secp256k1',
        };
    if (envelope.type === 'p256') {
        return {
            prehash: envelope.preHash,
            publicKey: {
                prefix: 4,
                x: Hex.toBigInt(envelope.pubKeyX),
                y: Hex.toBigInt(envelope.pubKeyY),
            },
            signature: {
                r: Hex.toBigInt(envelope.r),
                s: Hex.toBigInt(envelope.s),
            },
            type: 'p256',
        };
    }
    if (envelope.type === 'webAuthn') {
        const webauthnData = envelope.webauthnData;
        const webauthnDataSize = Hex.size(webauthnData);
        let authenticatorData;
        let clientDataJSON;
        for (let split = 37; split < webauthnDataSize; split++) {
            const potentialJson = Hex.toString(Hex.slice(webauthnData, split));
            if (potentialJson.startsWith('{') && potentialJson.endsWith('}')) {
                try {
                    JSON.parse(potentialJson);
                    authenticatorData = Hex.slice(webauthnData, 0, split);
                    clientDataJSON = potentialJson;
                    break;
                }
                catch { }
            }
        }
        if (!authenticatorData || !clientDataJSON)
            throw new InvalidSerializedError({
                reason: 'Unable to parse WebAuthn metadata: could not extract valid authenticatorData and clientDataJSON',
                serialized: webauthnData,
            });
        return {
            metadata: {
                authenticatorData,
                clientDataJSON,
            },
            publicKey: {
                prefix: 4,
                x: Hex.toBigInt(envelope.pubKeyX),
                y: Hex.toBigInt(envelope.pubKeyY),
            },
            signature: {
                r: Hex.toBigInt(envelope.r),
                s: Hex.toBigInt(envelope.s),
            },
            type: 'webAuthn',
        };
    }
    if (envelope.type === 'keychain' ||
        ('userAddress' in envelope && 'signature' in envelope))
        return {
            type: 'keychain',
            userAddress: envelope.userAddress,
            inner: fromRpc(envelope.signature),
            ...(envelope.keyId ? { keyId: envelope.keyId } : {}),
            ...(envelope.version ? { version: envelope.version } : {}),
        };
    throw new CoercionError({ envelope });
}
function getType(envelope) {
    if (typeof envelope !== 'object' || envelope === null)
        throw new CoercionError({ envelope });
    if ('type' in envelope && envelope.type)
        return envelope.type;
    if ('signature' in envelope &&
        !('publicKey' in envelope) &&
        typeof envelope.signature === 'object' &&
        envelope.signature !== null &&
        'r' in envelope.signature &&
        's' in envelope.signature &&
        'yParity' in envelope.signature)
        return 'secp256k1';
    if ('r' in envelope && 's' in envelope && 'yParity' in envelope)
        return 'secp256k1';
    if ('signature' in envelope &&
        'prehash' in envelope &&
        'publicKey' in envelope &&
        typeof envelope.prehash === 'boolean')
        return 'p256';
    if ('signature' in envelope &&
        'metadata' in envelope &&
        'publicKey' in envelope)
        return 'webAuthn';
    if ('userAddress' in envelope && 'inner' in envelope)
        return 'keychain';
    throw new CoercionError({
        envelope,
    });
}
function serialize(envelope, options = {}) {
    const type = getType(envelope);
    if (type === 'secp256k1') {
        const secp256k1 = envelope;
        return Hex.concat(Signature.toHex(secp256k1.signature), options.magic ? exports.magicBytes : '0x');
    }
    if (type === 'p256') {
        const p256 = envelope;
        return Hex.concat(serializedP256Type, Hex.fromNumber(p256.signature.r, { size: 32 }), Hex.fromNumber(p256.signature.s, { size: 32 }), Hex.fromNumber(p256.publicKey.x, { size: 32 }), Hex.fromNumber(p256.publicKey.y, { size: 32 }), Hex.fromNumber(p256.prehash ? 1 : 0, { size: 1 }), options.magic ? exports.magicBytes : '0x');
    }
    if (type === 'webAuthn') {
        const webauthn = envelope;
        const webauthnData = Hex.concat(webauthn.metadata.authenticatorData, Hex.fromString(webauthn.metadata.clientDataJSON));
        return Hex.concat(serializedWebAuthnType, webauthnData, Hex.fromNumber(webauthn.signature.r, { size: 32 }), Hex.fromNumber(webauthn.signature.s, { size: 32 }), Hex.fromNumber(webauthn.publicKey.x, { size: 32 }), Hex.fromNumber(webauthn.publicKey.y, { size: 32 }), options.magic ? exports.magicBytes : '0x');
    }
    if (type === 'keychain') {
        const keychain = envelope;
        const keychainTypeId = keychain.version === 'v1'
            ? serializedKeychainType
            : serializedKeychainV2Type;
        return Hex.concat(keychainTypeId, keychain.userAddress, serialize(keychain.inner), options.magic ? exports.magicBytes : '0x');
    }
    throw new CoercionError({ envelope });
}
function toRpc(envelope) {
    const type = getType(envelope);
    if (type === 'secp256k1') {
        const secp256k1 = envelope;
        return {
            ...Signature.toRpc(secp256k1.signature),
            type: 'secp256k1',
        };
    }
    if (type === 'p256') {
        const p256 = envelope;
        return {
            preHash: p256.prehash,
            pubKeyX: Hex.fromNumber(p256.publicKey.x, { size: 32 }),
            pubKeyY: Hex.fromNumber(p256.publicKey.y, { size: 32 }),
            r: Hex.fromNumber(p256.signature.r, { size: 32 }),
            s: Hex.fromNumber(p256.signature.s, { size: 32 }),
            type: 'p256',
        };
    }
    if (type === 'webAuthn') {
        const webauthn = envelope;
        const webauthnData = Hex.concat(webauthn.metadata.authenticatorData, Hex.fromString(webauthn.metadata.clientDataJSON));
        return {
            pubKeyX: Hex.fromNumber(webauthn.publicKey.x, { size: 32 }),
            pubKeyY: Hex.fromNumber(webauthn.publicKey.y, { size: 32 }),
            r: Hex.fromNumber(webauthn.signature.r, { size: 32 }),
            s: Hex.fromNumber(webauthn.signature.s, { size: 32 }),
            type: 'webAuthn',
            webauthnData,
        };
    }
    if (type === 'keychain') {
        const keychain = envelope;
        return {
            type: 'keychain',
            userAddress: keychain.userAddress,
            signature: toRpc(keychain.inner),
            ...(keychain.keyId ? { keyId: keychain.keyId } : {}),
            ...(keychain.version ? { version: keychain.version } : {}),
        };
    }
    throw new CoercionError({ envelope });
}
function validate(envelope) {
    try {
        assert(envelope);
        return true;
    }
    catch {
        return false;
    }
}
function verify(signature, parameters) {
    const { payload } = parameters;
    const address = (() => {
        if (parameters.address)
            return parameters.address;
        if (parameters.publicKey)
            return Address.fromPublicKey(parameters.publicKey);
        return undefined;
    })();
    if (!address)
        return false;
    const envelope = from(signature);
    if (envelope.type === 'secp256k1') {
        if (!address)
            return false;
        return ox_Secp256k1.verify({
            address,
            payload,
            signature: envelope.signature,
        });
    }
    if (envelope.type === 'p256') {
        const envelopeAddress = Address.fromPublicKey(envelope.publicKey);
        if (!Address.isEqual(envelopeAddress, address))
            return false;
        return ox_P256.verify({
            hash: envelope.prehash,
            publicKey: envelope.publicKey,
            payload,
            signature: envelope.signature,
        });
    }
    if (envelope.type === 'webAuthn') {
        const envelopeAddress = Address.fromPublicKey(envelope.publicKey);
        if (!Address.isEqual(envelopeAddress, address))
            return false;
        return ox_WebAuthnP256.verify({
            challenge: Hex.from(payload),
            metadata: envelope.metadata,
            publicKey: envelope.publicKey,
            signature: envelope.signature,
        });
    }
    throw new VerificationError(`Unable to verify signature envelope of type "${envelope.type}".`);
}
class CoercionError extends Errors.BaseError {
    constructor({ envelope }) {
        super(`Unable to coerce value (\`${Json.stringify(envelope)}\`) to a valid signature envelope.`);
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'SignatureEnvelope.CoercionError'
        });
    }
}
exports.CoercionError = CoercionError;
class MissingPropertiesError extends Errors.BaseError {
    constructor({ envelope, missing, type, }) {
        super(`Signature envelope of type "${type}" is missing required properties: ${missing.map((m) => `\`${m}\``).join(', ')}.\n\nProvided: ${Json.stringify(envelope)}`);
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'SignatureEnvelope.MissingPropertiesError'
        });
    }
}
exports.MissingPropertiesError = MissingPropertiesError;
class InvalidSerializedError extends Errors.BaseError {
    constructor({ reason, serialized, }) {
        super(`Unable to deserialize signature envelope: ${reason}`, {
            metaMessages: [`Serialized: ${serialized}`],
        });
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'SignatureEnvelope.InvalidSerializedError'
        });
    }
}
exports.InvalidSerializedError = InvalidSerializedError;
class VerificationError extends Errors.BaseError {
    constructor() {
        super(...arguments);
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'SignatureEnvelope.VerificationError'
        });
    }
}
exports.VerificationError = VerificationError;
//# sourceMappingURL=SignatureEnvelope.js.map