mirror of
https://github.com/swc-project/swc.git
synced 2024-12-26 23:27:56 +03:00
150 lines
4.6 KiB
TypeScript
150 lines
4.6 KiB
TypeScript
|
// Loaded from https://raw.githubusercontent.com/nats-io/nkeys.js/v1.0.0-7/src/codec.ts
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Copyright 2018-2020 The NATS Authors
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
import { crc16 } from "./crc16.ts";
|
||
|
import { NKeysError, NKeysErrorCode, Prefix, Prefixes } from "./nkeys.ts";
|
||
|
import { base32 } from "./base32.ts";
|
||
|
|
||
|
/**
|
||
|
* @ignore
|
||
|
*/
|
||
|
export interface SeedDecode {
|
||
|
prefix: Prefix;
|
||
|
buf: Uint8Array;
|
||
|
}
|
||
|
/**
|
||
|
* @ignore
|
||
|
*/
|
||
|
export class Codec {
|
||
|
static encode(prefix: Prefix, src: Uint8Array): Uint8Array {
|
||
|
if (!src || !(src instanceof Uint8Array)) {
|
||
|
throw new NKeysError(NKeysErrorCode.SerializationError);
|
||
|
}
|
||
|
|
||
|
if (!Prefixes.isValidPrefix(prefix)) {
|
||
|
throw new NKeysError(NKeysErrorCode.InvalidPrefixByte);
|
||
|
}
|
||
|
|
||
|
return Codec._encode(false, prefix, src);
|
||
|
}
|
||
|
|
||
|
static encodeSeed(role: Prefix, src: Uint8Array): Uint8Array {
|
||
|
if (!src) {
|
||
|
throw new NKeysError(NKeysErrorCode.ApiError);
|
||
|
}
|
||
|
|
||
|
if (!Prefixes.isValidPublicPrefix(role)) {
|
||
|
throw new NKeysError(NKeysErrorCode.InvalidPrefixByte);
|
||
|
}
|
||
|
|
||
|
if (src.byteLength !== 32) {
|
||
|
throw new NKeysError(NKeysErrorCode.InvalidSeedLen);
|
||
|
}
|
||
|
return Codec._encode(true, role, src);
|
||
|
}
|
||
|
|
||
|
static decode(expected: Prefix, src: Uint8Array): Uint8Array {
|
||
|
if (!Prefixes.isValidPrefix(expected)) {
|
||
|
throw new NKeysError(NKeysErrorCode.InvalidPrefixByte);
|
||
|
}
|
||
|
const raw = Codec._decode(src);
|
||
|
if (raw[0] !== expected) {
|
||
|
throw new NKeysError(NKeysErrorCode.InvalidPrefixByte);
|
||
|
}
|
||
|
return raw.slice(1);
|
||
|
}
|
||
|
|
||
|
static decodeSeed(src: Uint8Array): SeedDecode {
|
||
|
const raw = Codec._decode(src);
|
||
|
const prefix = Codec._decodePrefix(raw);
|
||
|
if (prefix[0] != Prefix.Seed) {
|
||
|
throw new NKeysError(NKeysErrorCode.InvalidSeed);
|
||
|
}
|
||
|
if (!Prefixes.isValidPublicPrefix(prefix[1])) {
|
||
|
throw new NKeysError(NKeysErrorCode.InvalidPrefixByte);
|
||
|
}
|
||
|
return ({ buf: raw.slice(2), prefix: prefix[1] });
|
||
|
}
|
||
|
|
||
|
// unsafe encode no prefix/role validation
|
||
|
static _encode(seed: boolean, role: Prefix, payload: Uint8Array): Uint8Array {
|
||
|
// offsets for this token
|
||
|
const payloadOffset = seed ? 2 : 1;
|
||
|
const payloadLen = payload.byteLength;
|
||
|
const checkLen = 2;
|
||
|
const cap = payloadOffset + payloadLen + checkLen;
|
||
|
const checkOffset = payloadOffset + payloadLen;
|
||
|
|
||
|
const raw = new Uint8Array(cap);
|
||
|
// make the prefixes human readable when encoded
|
||
|
if (seed) {
|
||
|
const encodedPrefix = Codec._encodePrefix(Prefix.Seed, role);
|
||
|
raw.set(encodedPrefix);
|
||
|
} else {
|
||
|
raw[0] = role;
|
||
|
}
|
||
|
raw.set(payload, payloadOffset);
|
||
|
|
||
|
//calculate the checksum write it LE
|
||
|
const checksum = crc16.checksum(raw.slice(0, checkOffset));
|
||
|
const dv = new DataView(raw.buffer);
|
||
|
dv.setUint16(checkOffset, checksum, true);
|
||
|
|
||
|
return base32.encode(raw);
|
||
|
}
|
||
|
|
||
|
// unsafe decode - no prefix/role validation
|
||
|
static _decode(src: Uint8Array): Uint8Array {
|
||
|
if (src.byteLength < 4) {
|
||
|
throw new NKeysError(NKeysErrorCode.InvalidEncoding);
|
||
|
}
|
||
|
let raw: Uint8Array;
|
||
|
try {
|
||
|
raw = base32.decode(src);
|
||
|
} catch (ex) {
|
||
|
throw new NKeysError(NKeysErrorCode.InvalidEncoding, ex);
|
||
|
}
|
||
|
|
||
|
const checkOffset = raw.byteLength - 2;
|
||
|
const dv = new DataView(raw.buffer);
|
||
|
const checksum = dv.getUint16(checkOffset, true);
|
||
|
|
||
|
const payload = raw.slice(0, checkOffset);
|
||
|
if (!crc16.validate(payload, checksum)) {
|
||
|
throw new NKeysError(NKeysErrorCode.InvalidChecksum);
|
||
|
}
|
||
|
return payload;
|
||
|
}
|
||
|
|
||
|
static _encodePrefix(kind: Prefix, role: Prefix): Uint8Array {
|
||
|
// In order to make this human printable for both bytes, we need to do a little
|
||
|
// bit manipulation to setup for base32 encoding which takes 5 bits at a time.
|
||
|
const b1 = kind | (role >> 5);
|
||
|
const b2 = (role & 31) << 3; // 31 = 00011111
|
||
|
return new Uint8Array([b1, b2]);
|
||
|
}
|
||
|
|
||
|
static _decodePrefix(raw: Uint8Array): Uint8Array {
|
||
|
// Need to do the reverse from the printable representation to
|
||
|
// get back to internal representation.
|
||
|
const b1 = raw[0] & 248; // 248 = 11111000
|
||
|
const b2 = (raw[0] & 7) << 5 | ((raw[1] & 248) >> 3); // 7 = 00000111
|
||
|
return new Uint8Array([b1, b2]);
|
||
|
}
|
||
|
}
|