"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Secp256k1 = void 0;
const secp256k1 = __importStar(require("@noble/secp256k1"));
const secp256k1_1 = __importDefault(require("secp256k1"));
const encoder_js_1 = require("../utils/encoder.js");
const sha2_1 = require("multiformats/hashes/sha2");
const dwn_error_js_1 = require("../core/dwn-error.js");
const node_crypto_1 = require("node:crypto");
// @ts-ignore
if (!globalThis.crypto) {
    globalThis.crypto = node_crypto_1.webcrypto;
}
/**
 * Class containing SECP256K1 related utility methods.
 */
class Secp256k1 {
    /**
     * Validates the given JWK is a SECP256K1 key.
     * @throws {Error} if fails validation.
     */
    static validateKey(jwk) {
        if (jwk.kty !== 'EC' || jwk.crv !== 'secp256k1') {
            throw new dwn_error_js_1.DwnError(dwn_error_js_1.DwnErrorCode.Secp256k1KeyNotValid, 'Invalid SECP256K1 JWK: `kty` MUST be `EC`. `crv` MUST be `secp256k1`');
        }
    }
    /**
     * Converts a public key in bytes into a JWK.
     */
    static publicKeyToJwk(publicKeyBytes) {
        return __awaiter(this, void 0, void 0, function* () {
            // ensure public key is in uncompressed format so we can convert it into both x and y value
            let uncompressedPublicKeyBytes;
            if (publicKeyBytes.byteLength === 33) {
                // this means given key is compressed
                const publicKeyHex = secp256k1.etc.bytesToHex(publicKeyBytes);
                const curvePoints = secp256k1.ProjectivePoint.fromHex(publicKeyHex);
                uncompressedPublicKeyBytes = curvePoints.toRawBytes(false); // isCompressed = false
            }
            else {
                uncompressedPublicKeyBytes = publicKeyBytes;
            }
            // the first byte is a header that indicates whether the key is uncompressed (0x04 if uncompressed), we can safely ignore
            // bytes 1 - 32 represent X
            // bytes 33 - 64 represent Y
            // skip the first byte because it's used as a header to indicate whether the key is uncompressed
            const x = encoder_js_1.Encoder.bytesToBase64Url(uncompressedPublicKeyBytes.subarray(1, 33));
            const y = encoder_js_1.Encoder.bytesToBase64Url(uncompressedPublicKeyBytes.subarray(33, 65));
            const publicJwk = {
                alg: 'ES256K',
                kty: 'EC',
                crv: 'secp256k1',
                x,
                y
            };
            return publicJwk;
        });
    }
    /**
     * Converts a private key in bytes into a JWK.
     */
    static privateKeyToJwk(privateKeyBytes) {
        return __awaiter(this, void 0, void 0, function* () {
            const publicKeyBytes = yield Secp256k1.getPublicKey(privateKeyBytes);
            const jwk = yield Secp256k1.publicKeyToJwk(publicKeyBytes);
            jwk.d = encoder_js_1.Encoder.bytesToBase64Url(privateKeyBytes);
            return jwk;
        });
    }
    /**
     * Creates a uncompressed key in raw bytes from the given SECP256K1 JWK.
     */
    static publicJwkToBytes(publicJwk) {
        const x = encoder_js_1.Encoder.base64UrlToBytes(publicJwk.x);
        const y = encoder_js_1.Encoder.base64UrlToBytes(publicJwk.y);
        // leading byte of 0x04 indicates that the public key is uncompressed
        const publicKey = new Uint8Array([0x04, ...x, ...y]);
        return publicKey;
    }
    /**
     * Creates a private key in raw bytes from the given SECP256K1 JWK.
     */
    static privateJwkToBytes(privateJwk) {
        const privateKey = encoder_js_1.Encoder.base64UrlToBytes(privateJwk.d);
        return privateKey;
    }
    /**
     * Signs the provided content using the provided JWK.
     */
    static sign(content, privateJwk) {
        return __awaiter(this, void 0, void 0, function* () {
            Secp256k1.validateKey(privateJwk);
            // the underlying lib expects us to hash the content ourselves:
            // https://github.com/paulmillr/noble-secp256k1/blob/97aa518b9c12563544ea87eba471b32ecf179916/index.ts#L1160
            const hashedContent = yield sha2_1.sha256.encode(content);
            const hashedContentHex = secp256k1.etc.bytesToHex(hashedContent);
            const privateKeyBytes = Secp256k1.privateJwkToBytes(privateJwk);
            const privateKeyHex = secp256k1.etc.bytesToHex(privateKeyBytes);
            return (yield secp256k1.signAsync(hashedContentHex, privateKeyHex)).toCompactRawBytes();
        });
    }
    /**
     * Verifies a signature against the provided payload hash and public key.
     * @returns a boolean indicating whether the signature is valid.
     */
    static verify(content, signature, publicJwk) {
        return __awaiter(this, void 0, void 0, function* () {
            Secp256k1.validateKey(publicJwk);
            const publicKeyBytes = Secp256k1.publicJwkToBytes(publicJwk);
            const hashedContent = yield sha2_1.sha256.encode(content);
            return secp256k1.verify(signature, hashedContent, publicKeyBytes);
        });
    }
    /**
     * Generates a random key pair in JWK format.
     */
    static generateKeyPair() {
        return __awaiter(this, void 0, void 0, function* () {
            const privateKeyBytes = secp256k1.utils.randomPrivateKey();
            const publicKeyBytes = secp256k1.getPublicKey(privateKeyBytes, false); // `false` = uncompressed
            const d = encoder_js_1.Encoder.bytesToBase64Url(privateKeyBytes);
            const publicJwk = yield Secp256k1.publicKeyToJwk(publicKeyBytes);
            const privateJwk = Object.assign(Object.assign({}, publicJwk), { d });
            return { publicJwk, privateJwk };
        });
    }
    /**
     * Generates key pair in raw bytes, where the `publicKey` is uncompressed.
     */
    static generateKeyPairRaw() {
        return __awaiter(this, void 0, void 0, function* () {
            const privateKey = secp256k1.utils.randomPrivateKey();
            const publicKey = secp256k1.getPublicKey(privateKey, false); // `false` = uncompressed
            return { publicKey, privateKey };
        });
    }
    /**
     * Gets the uncompressed public key of the given private key.
     */
    static getPublicKey(privateKey) {
        return __awaiter(this, void 0, void 0, function* () {
            const publicKey = secp256k1.getPublicKey(privateKey, false); // `false` = uncompressed
            return publicKey;
        });
    }
    /**
     * Derives a hierarchical deterministic public key.
     * @param key Either a private or an uncompressed public key used to derive the descendant public key.
     * @returns uncompressed public key
     */
    static derivePublicKey(key, relativePath) {
        return __awaiter(this, void 0, void 0, function* () {
            Secp256k1.validateKeyDerivationPath(relativePath);
            let currentPublicKey;
            if (key.length === 32) {
                // private key is always 32 bytes
                currentPublicKey = secp256k1.getPublicKey(key, false); // `false` = uncompressed
            }
            else {
                currentPublicKey = key;
            }
            for (const segment of relativePath) {
                const hash = yield sha2_1.sha256.encode(encoder_js_1.Encoder.stringToBytes(segment));
                currentPublicKey = Secp256k1.deriveChildPublicKey(currentPublicKey, hash);
            }
            return currentPublicKey;
        });
    }
    /**
     * Derives a hierarchical deterministic private key.
     */
    static derivePrivateKey(privateKey, relativePath) {
        return __awaiter(this, void 0, void 0, function* () {
            Secp256k1.validateKeyDerivationPath(relativePath);
            let currentPrivateKey = privateKey;
            for (const segment of relativePath) {
                const hash = yield sha2_1.sha256.encode(encoder_js_1.Encoder.stringToBytes(segment));
                currentPrivateKey = Secp256k1.deriveChildPrivateKey(currentPrivateKey, hash);
            }
            return currentPrivateKey;
        });
    }
    /**
     * Derives a child public key using the given tweak input.
     */
    static deriveChildPublicKey(uncompressedPublicKey, tweakInput) {
        // underlying library requires Buffer as input
        const compressedPublicKey = false;
        const publicKeyBuffer = Buffer.from(uncompressedPublicKey);
        const tweakBuffer = Buffer.from(tweakInput);
        const derivedPublicKey = secp256k1_1.default.publicKeyTweakAdd(publicKeyBuffer, tweakBuffer, compressedPublicKey);
        return derivedPublicKey;
    }
    /**
     * Derives a child private key using the given tweak input.
     */
    static deriveChildPrivateKey(privateKey, tweakInput) {
        // NOTE: passing in private key to v5.0.0 of `secp256k1.privateKeyTweakAdd()` has the side effect of modifying the input private key bytes.
        // `secp256k1.publicKeyTweakAdd()` does not have this side effect.
        // before there is a fix for it (we can also investigate and submit a PR), cloning the private key to workaround is a MUST
        // also underlying library requires Buffer as input
        const privateKeyBuffer = Buffer.from(privateKey);
        const tweakBuffer = Buffer.from(tweakInput);
        const derivedPrivateKey = secp256k1_1.default.privateKeyTweakAdd(privateKeyBuffer, tweakBuffer);
        return derivedPrivateKey;
    }
    /**
     * Parses the given key derivation path.
     * @returns Path segments if successfully validate the derivation path.
     * @throws {DwnError} with `DwnErrorCode.HdKeyDerivationPathInvalid` if derivation path fails validation.
     */
    static validateKeyDerivationPath(pathSegments) {
        if (pathSegments.includes('')) {
            throw new dwn_error_js_1.DwnError(dwn_error_js_1.DwnErrorCode.HdKeyDerivationPathInvalid, `Invalid key derivation path: ${pathSegments}`);
        }
    }
}
exports.Secp256k1 = Secp256k1;
//# sourceMappingURL=secp256k1.js.map