All files HdKeyCodec.ts

97.1% Statements 67/69
88.24% Branches 30/34
100% Functions 3/3
97.1% Lines 67/69

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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 1741x 1x 1x 1x 1x 1x 1x 1x 1x 1x   1x   59x 66x 25x 41x 25x 16x 1x 15x 2x 13x 2x 11x 2x     2x                                                                       60x   59x       59x 59x 59x 59x 59x 59x 59x   59x   57x 2x     55x 2x           53x 27x     27x 3x       24x 24x       26x 26x     26x 26x                 44x 44x 44x 44x 44x   44x                                                           69x 69x 69x 69x 69x 69x   69x 49x 49x   20x     69x 69x      
import { BufferReader, BufferWriter } from "@node-lightning/bufio";
import { BitcoinErrorCode } from "./BitcoinErrorCode";
import { BitcoinError } from "./BitcoinError";
import { HdKeyType } from "./HdKeyType";
import { Network } from "./Network";
import { HdPrivateKey } from "./HdPrivateKey";
import { Base58Check } from "./Base58Check";
import { PrivateKey } from "./PrivateKey";
import { PublicKey } from "./PublicKey";
import { HdPublicKey } from "./HdPublicKey";
 
export class HdKeyCodec {
    public static decodeVersion(version: number): [Network, HdKeyType, boolean] {
        for (const network of Network.all) {
            if (version === network.xpubVersion) {
                return [network, HdKeyType.x, false];
            } else if (version === network.xprvVersion) {
                return [network, HdKeyType.x, true];
            } else if (version === network.ypubVersion) {
                return [network, HdKeyType.y, false];
            } else if (version === network.yprvVersion) {
                return [network, HdKeyType.y, true];
            } else if (version === network.zpubVersion) {
                return [network, HdKeyType.z, false];
            } else if (version === network.zprvVersion) {
                return [network, HdKeyType.z, true];
            }
        }
        throw new BitcoinError(BitcoinErrorCode.UnkownHdKeyVersion, version.toString());
    }
 
    /**
     * Decodes an extended private key or public key from the
     * serialization format defined in BIP32.
     *
     * For example:
     * xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi
     * xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8
     *
     * @remarks
     *
     * The value uses a prefix of xprv, yprv, or zprv and is Base58Check.
     *
     * ```
     * The format includes:
     * [4 byte]: version
     * [1 byte]: depth
     * [4 byte]: parent fingerprint
     * [4 byte]: number
     * [32 byte]: chaincode
     * [33 byte]: key
     * ```
     *
     * The 33-byte key values uses a 0x00 prefix plus the 32-byte private
     * key.
     *
     * @param input encoded input
     *
     * @throws {@link BitcoinError} throws when there is an invalid
     * encoding, bad checksum, or if you attempt to decode a public key.
     *
     * @returns an instance of the extended private key or public key
     */
    public static decode(input: string): HdPrivateKey | HdPublicKey {
        const buf = Base58Check.decode(input);
 
        Iif (buf.length !== 78) {
            throw new BitcoinError(BitcoinErrorCode.InvalidHdEncoding, input);
        }
 
        const r = new BufferReader(buf);
        const version: number = r.readUInt32BE();
        const depth = r.readUInt8();
        const parentFingerprint = r.readBytes(4);
        const childNum = r.readUInt32BE();
        const chaincode = r.readBytes(32);
        const rawkey = r.readBytes(33);
 
        const [network, type, isPrivate] = HdKeyCodec.decodeVersion(version);
 
        if (depth === 0 && !parentFingerprint.equals(Buffer.alloc(4))) {
            throw new BitcoinError(BitcoinErrorCode.InvalidHdEncoding, input);
        }
 
        if (depth === 0 && childNum !== 0) {
            throw new BitcoinError(BitcoinErrorCode.InvalidHdEncoding, input);
        }
 
        let key: HdPrivateKey | HdPublicKey;
 
        // private key
        if (isPrivate) {
            key = new HdPrivateKey();
 
            // validate correct prefix
            if (rawkey[0] !== 0x00) {
                throw new BitcoinError(BitcoinErrorCode.InvalidHdEncoding, input);
            }
 
            // construct and validate private key
            Eif (key instanceof HdPrivateKey) {
                key.privateKey = new PrivateKey(rawkey.slice(1), network);
            }
        }
        // public key
        else Eif (!isPrivate) {
            key = new HdPublicKey();
 
            // construct and validate public key
            Eif (key instanceof HdPublicKey) {
                key.publicKey = new PublicKey(rawkey, network);
            }
        }
        // unknown key type
        else {
            throw new BitcoinError(BitcoinErrorCode.UnkownHdKeyVersion, input);
        }
 
        // apply the rest of the values
        key.type = type;
        key.depth = depth;
        key.parentFingerprint = parentFingerprint;
        key.number = childNum;
        key.chainCode = chaincode;
 
        return key;
    }
 
    /**
     * Encodes either a {@link HdPrivateKey} or {@link HdPublicKey}
     * according to BIP32.
     *
     * For example:
     * xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi
     * xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8
     *
     * @remarks
     * The value uses a prefix of xprv, yprv, or zprv and is Base58Check
     * encoded.
     *
     * The format includes:
     * ```
     * [4 byte]: version
     * [1 byte]: depth
     * [4 byte]: parent fingerprint
     * [4 byte]: number
     * [32 byte]: chaincode
     * [33 byte]: key
     * ```
     *
     * For private keys, the 33-byte key value is prefixed with 0x00.
     *
     * @returns Base58Check encoded extended key
     */
    public static encode(key: HdPrivateKey | HdPublicKey) {
        const w = new BufferWriter(Buffer.alloc(78));
        w.writeUInt32BE(key.version);
        w.writeUInt8(key.depth);
        w.writeBytes(key.parentFingerprint);
        w.writeUInt32BE(key.number);
        w.writeBytes(key.chainCode);
 
        if (key instanceof HdPrivateKey) {
            w.writeUInt8(0);
            w.writeBytes(key.privateKey.toBuffer());
        } else {
            w.writeBytes(key.publicKey.toBuffer());
        }
 
        const buf = w.toBuffer();
        return Base58Check.encode(buf);
    }
}