Home Reference Source

lib/ecc/src/PublicKey.js

import BigInteger from "bigi";
import {Point, getCurveByName} from "ecurve";
const secp256k1 = getCurveByName("secp256k1");
import {encode, decode} from "bs58";
import {sha256, sha512, ripemd160} from "./hash";
import {ChainConfig} from "bitsharesjs-ws";
import assert from "assert";
import deepEqual from "deep-equal";
const Buffer = require("safe-buffer").Buffer;
const {G, n} = secp256k1;

class PublicKey {
    /** @param {Point} public key */
    constructor(Q) {
        this.Q = Q;
    }

    static fromBinary(bin) {
        return PublicKey.fromBuffer(Buffer.from(bin, "binary"));
    }

    static fromBuffer(buffer) {
        if (
            buffer.toString("hex") ===
            "000000000000000000000000000000000000000000000000000000000000000000"
        )
            return new PublicKey(null);
        return new PublicKey(Point.decodeFrom(secp256k1, buffer));
    }

    toBuffer(compressed = this.Q ? this.Q.compressed : null) {
        if (this.Q === null)
            return Buffer.from(
                "000000000000000000000000000000000000000000000000000000000000000000",
                "hex"
            );
        return this.Q.getEncoded(compressed);
    }

    static fromPoint(point) {
        return new PublicKey(point);
    }

    toUncompressed() {
        var buf = this.Q.getEncoded(false);
        var point = Point.decodeFrom(secp256k1, buf);
        return PublicKey.fromPoint(point);
    }

    /** bts::blockchain::address (unique but not a full public key) */
    toBlockchainAddress() {
        var pub_buf = this.toBuffer();
        var pub_sha = sha512(pub_buf);
        return ripemd160(pub_sha);
    }

    /** Alias for {@link toPublicKeyString} */
    toString(address_prefix = ChainConfig.address_prefix) {
        return this.toPublicKeyString(address_prefix);
    }

    /**
        Full public key
        {return} string
    */
    toPublicKeyString(address_prefix = ChainConfig.address_prefix) {
        var pub_buf = this.toBuffer();
        var checksum = ripemd160(pub_buf);
        var addy = Buffer.concat([pub_buf, checksum.slice(0, 4)]);
        return address_prefix + encode(addy);
    }

    /**
        @arg {string} public_key - like GPHXyz...
        @arg {string} address_prefix - like GPH
        @return PublicKey or `null` (if the public_key string is invalid)
    */
    static fromPublicKeyString(
        public_key,
        address_prefix = ChainConfig.address_prefix
    ) {
        try {
            return PublicKey.fromStringOrThrow(public_key, address_prefix);
        } catch (e) {
            return null;
        }
    }

    /**
        @arg {string} public_key - like GPHXyz...
        @arg {string} address_prefix - like GPH
        @throws {Error} if public key is invalid
        @return PublicKey
    */
    static fromStringOrThrow(
        public_key,
        address_prefix = ChainConfig.address_prefix
    ) {
        if (public_key.Q === null)
            public_key =
                address_prefix + "1111111111111111111111111111111114T1Anm"; // null key
        var prefix = public_key.slice(0, address_prefix.length);
        assert.equal(
            address_prefix,
            prefix,
            `Expecting key to begin with ${address_prefix}, instead got ${prefix}`
        );
        public_key = public_key.slice(address_prefix.length);

        public_key = Buffer.from(decode(public_key), "binary");
        var checksum = public_key.slice(-4);
        public_key = public_key.slice(0, -4);
        var new_checksum = ripemd160(public_key);
        new_checksum = new_checksum.slice(0, 4);
        var isEqual = deepEqual(checksum, new_checksum); //, 'Invalid checksum'
        if (!isEqual) {
            throw new Error("Checksum did not match");
        }
        return PublicKey.fromBuffer(public_key);
    }

    toAddressString(address_prefix = ChainConfig.address_prefix) {
        var pub_buf = this.toBuffer();
        var pub_sha = sha512(pub_buf);
        var addy = ripemd160(pub_sha);
        var checksum = ripemd160(addy);
        addy = Buffer.concat([addy, checksum.slice(0, 4)]);
        return address_prefix + encode(addy);
    }

    toPtsAddy() {
        var pub_buf = this.toBuffer();
        var pub_sha = sha256(pub_buf);
        var addy = ripemd160(pub_sha);
        addy = Buffer.concat([Buffer.from([0x38]), addy]); //version 56(decimal)

        var checksum = sha256(addy);
        checksum = sha256(checksum);

        addy = Buffer.concat([addy, checksum.slice(0, 4)]);
        return encode(addy);
    }

    child(offset) {
        assert(Buffer.isBuffer(offset), "Buffer required: offset");
        assert.equal(offset.length, 32, "offset length");

        offset = Buffer.concat([this.toBuffer(), offset]);
        offset = sha256(offset);

        let c = BigInteger.fromBuffer(offset);

        if (c.compareTo(n) >= 0)
            throw new Error("Child offset went out of bounds, try again");

        let cG = G.multiply(c);
        let Qprime = this.Q.add(cG);

        if (secp256k1.isInfinity(Qprime))
            throw new Error(
                "Child offset derived to an invalid key, try again"
            );

        return PublicKey.fromPoint(Qprime);
    }

    /* <HEX> */

    toByteBuffer() {
        var b = new ByteBuffer(
            ByteBuffer.DEFAULT_CAPACITY,
            ByteBuffer.LITTLE_ENDIAN
        );
        this.appendByteBuffer(b);
        return b.copy(0, b.offset);
    }

    static fromHex(hex) {
        return PublicKey.fromBuffer(Buffer.from(hex, "hex"));
    }

    toHex() {
        return this.toBuffer().toString("hex");
    }

    static fromPublicKeyStringHex(hex) {
        return PublicKey.fromPublicKeyString(Buffer.from(hex, "hex"));
    }

    /* </HEX> */
}

export default PublicKey;