All files / src/chain public-key.ts

83.82% Statements 57/68
81.08% Branches 30/37
92.3% Functions 12/13
83.58% Lines 56/67

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157                      1x 1x                 334x 26x   308x 78x   230x     230x 33x 33x     33x 33x 33x 33x 197x   197x 197x 197x               5x 5x 1x 1x 1x 1x 1x 1x 1x 1x   4x         315x 315x               29x       11x 11x         22x 22x 22x 22x 5x   17x 14x   3x               8x 2x   6x         73x         13x 13x         11x         17x 17x 29x 17x                     14x 14x 14x                          
import {ABIDecoder} from '../serializer/decoder'
import {ABIEncoder} from '../serializer/encoder'
import {ABISerializableObject} from '../serializer/serializable'
 
import {Base58} from '../base58'
import {isInstanceOf} from '../utils'
 
import {Bytes, KeyType} from '../'
 
export type PublicKeyType = PublicKey | string | {type: string; compressed: Uint8Array}
 
export class PublicKey implements ABISerializableObject {
    static abiName = 'public_key'
 
    /** Type, e.g. `K1` */
    type: KeyType
    /** Compressed public key point. */
    data: Bytes
 
    /** Create PublicKey object from representing types. */
    static from(value: PublicKeyType) {
        if (isInstanceOf(value, PublicKey)) {
            return value
        }
        if (typeof value === 'object' && value.type && value.compressed) {
            return new PublicKey(KeyType.from(value.type), new Bytes(value.compressed))
        }
        Iif (typeof value !== 'string') {
            throw new Error('Invalid public key')
        }
        if (value.startsWith('PUB_')) {
            const parts = value.split('_')
            Iif (parts.length !== 3) {
                throw new Error('Invalid public key string')
            }
            const type = KeyType.from(parts[1])
            const size = type === KeyType.K1 || type === KeyType.R1 ? 33 : undefined
            const data = Base58.decodeRipemd160Check(parts[2], size, type)
            return new PublicKey(type, data)
        } else if (value.startsWith('EOS') || value.length >= 50) {
            // Legacy EOS key; leading zero bytes shorten the encoding, e.g. the null key
            const encoded = value.startsWith('EOS') ? value.slice(3) : value.slice(-50)
            const data = Base58.decodeRipemd160Check(encoded, 33)
            return new PublicKey(KeyType.K1, data)
        } else {
            Ethrow new Error('Invalid public key string')
        }
    }
 
    /** @internal */
    static fromABI(decoder: ABIDecoder) {
        const type = KeyType.from(decoder.readByte())
        if (type == KeyType.WA) {
            const startPos = decoder.getPosition()
            decoder.advance(33) // key_data
            decoder.advance(1) // user presence
            decoder.advance(decoder.readVaruint32()) // rpid
            const len = decoder.getPosition() - startPos
            decoder.setPosition(startPos)
            const data = Bytes.from(decoder.readArray(len))
            return new PublicKey(KeyType.WA, data)
        }
        return new PublicKey(type, new Bytes(decoder.readArray(33)))
    }
 
    /** @internal */
    constructor(type: KeyType, data: Bytes) {
        this.type = type
        this.data = data
    }
 
    /**
     * Returns the core 33-byte compressed public key data as a Uint8Array.
     * This is suitable for cryptographic operations like verification.
     */
    getCompressedKeyBytes(): Uint8Array {
        return this.type === KeyType.WA ? this.data.array.slice(0, 33) : this.data.array
    }
 
    equals(other: PublicKeyType) {
        const otherKey = PublicKey.from(other)
        return this.type === otherKey.type && this.data.equals(otherKey.data)
    }
 
    /** Compare with another key using the ordering nodeos enforces on authority keys. */
    compare(other: PublicKeyType): number {
        const otherKey = PublicKey.from(other)
        const type = KeyType.indexFor(this.type)
        const otherType = KeyType.indexFor(otherKey.type)
        if (type !== otherType) {
            return type < otherType ? -1 : 1
        }
        if (this.type === KeyType.WA) {
            return compareWebAuthn(this.data.array, otherKey.data.array)
        }
        return compareBytes(this.data.array, otherKey.data.array)
    }
 
    /**
     * Return Antelope/EOSIO legacy (`EOS<base58data>`) formatted key.
     * @throws If the key type isn't `K1`
     */
    toLegacyString(prefix = 'EOS') {
        if (this.type !== KeyType.K1) {
            throw new Error('Unable to create legacy formatted string for non-K1 key')
        }
        return `${prefix}${Base58.encodeRipemd160Check(this.data)}`
    }
 
    /** Return key in modern Antelope/EOSIO format (`PUB_<type>_<base58data>`) */
    toString() {
        return `PUB_${this.type}_${Base58.encodeRipemd160Check(this.data, this.type)}`
    }
 
    /** @internal */
    toABI(encoder: ABIEncoder) {
        encoder.writeByte(KeyType.indexFor(this.type))
        encoder.writeArray(this.data.array)
    }
 
    /** @internal */
    toJSON() {
        return this.toString()
    }
}
 
function compareBytes(a: Uint8Array, b: Uint8Array): number {
    const length = Math.min(a.length, b.length)
    for (let i = 0; i < length; i++) {
        if (a[i] !== b[i]) {
            return a[i] < b[i] ? -1 : 1
        }
    }
    Iif (a.length === b.length) {
        return 0
    }
    return a.length < b.length ? -1 : 1
}
 
function compareWebAuthn(a: Uint8Array, b: Uint8Array): number {
    // fc compares (key, user presence, rpid) as a tuple, the rpid as a string.
    const prefix = compareBytes(a.subarray(0, 34), b.subarray(0, 34))
    if (prefix !== 0) {
        return prefix
    }
    return compareBytes(readRpid(a), readRpid(b))
}
 
function readRpid(data: Uint8Array): Uint8Array {
    // Skip the varuint32 that length-prefixes the rpid on the wire.
    let offset = 34
    while (data[offset] & 0x80) {
        offset++
    }
    return data.subarray(offset + 1)
}