UNPKG

signify-ts

Version:

Signing at the edge for KERI, ACDC, and KERIA

522 lines (467 loc) 18.5 kB
import { EmptyMaterialError } from './kering'; import { intToB64, readInt } from './core'; import { b, d } from './core'; import { Buffer } from 'buffer'; import { decodeBase64Url, encodeBase64Url } from './base64'; export class Codex { has(prop: string): boolean { const m = new Map( Array.from(Object.entries(this), (v) => [v[1], v[0]]) ); return m.has(prop); } } export class MatterCodex extends Codex { Ed25519_Seed: string = 'A'; // Ed25519 256 bit random seed for private key Ed25519N: string = 'B'; // Ed25519 verification key non-transferable, basic derivation. X25519: string = 'C'; // X25519 public encryption key, converted from Ed25519 or Ed25519N. Ed25519: string = 'D'; // Ed25519 verification key basic derivation Blake3_256: string = 'E'; // Blake3 256 bit digest self-addressing derivation. SHA3_256: string = 'H'; // SHA3 256 bit digest self-addressing derivation. SHA2_256: string = 'I'; // SHA2 256 bit digest self-addressing derivation. ECDSA_256k1_Seed: string = 'J'; // ECDSA secp256k1 256 bit random Seed for private key X25519_Private: string = 'O'; // X25519 private decryption key converted from Ed25519 X25519_Cipher_Seed: string = 'P'; // X25519 124 char b64 Cipher of 44 char qb64 Seed ECDSA_256r1_Seed: string = 'Q'; // ECDSA secp256r1 256 bit random Seed for private key Salt_128: string = '0A'; // 128 bit random salt or 128 bit number (see Huge) Ed25519_Sig: string = '0B'; // Ed25519 signature. ECDSA_256k1_Sig: string = '0C'; // ECDSA secp256k1 signature. ECDSA_256r1_Sig: string = '0I'; // ECDSA secp256r1 signature. StrB64_L0: string = '4A'; // String Base64 Only Lead Size 0 StrB64_L1: string = '5A'; // String Base64 Only Lead Size 1 StrB64_L2: string = '6A'; // String Base64 Only Lead Size 2 ECDSA_256k1N: string = '1AAA'; // ECDSA secp256k1 verification key non-transferable, basic derivation. ECDSA_256k1: string = '1AAB'; // ECDSA public verification or encryption key, basic derivation X25519_Cipher_Salt: string = '1AAH'; // X25519 100 char b64 Cipher of 24 char qb64 Salt ECDSA_256r1N: string = '1AAI'; // ECDSA secp256r1 verification key non-transferable, basic derivation. ECDSA_256r1: string = '1AAJ'; // ECDSA secp256r1 verification or encryption key, basic derivation StrB64_Big_L0: string = '7AAA'; // String Base64 Only Big Lead Size 0 StrB64_Big_L1: string = '8AAA'; // String Base64 Only Big Lead Size 1 StrB64_Big_L2: string = '9AAA'; // String Base64 Only Big Lead Size 2 } export const MtrDex = new MatterCodex(); export class NonTransCodex extends Codex { Ed25519N: string = 'B'; // Ed25519 verification key non-transferable, basic derivation. ECDSA_256k1N: string = '1AAA'; // ECDSA secp256k1 verification key non-transferable, basic derivation. Ed448N: string = '1AAC'; // Ed448 non-transferable prefix public signing verification key. Basic derivation. ECDSA_256r1N: string = '1AAI'; // ECDSA secp256r1 verification key non-transferable, basic derivation. } export const NonTransDex = new NonTransCodex(); export class DigiCodex extends Codex { Blake3_256: string = 'E'; // Blake3 256 bit digest self-addressing derivation. Blake2b_256: string = 'F'; // Blake2b 256 bit digest self-addressing derivation. Blake2s_256: string = 'G'; // Blake2s 256 bit digest self-addressing derivation. SHA3_256: string = 'H'; // SHA3 256 bit digest self-addressing derivation. SHA2_256: string = 'I'; // SHA2 256 bit digest self-addressing derivation. Blake3_512: string = '0D'; // Blake3 512 bit digest self-addressing derivation. Blake2b_512: string = '0E'; // Blake2b 512 bit digest self-addressing derivation. SHA3_512: string = '0F'; // SHA3 512 bit digest self-addressing derivation. SHA2_512: string = '0G'; // SHA2 512 bit digest self-addressing derivation. } export const DigiDex = new DigiCodex(); export class NumCodex extends Codex { Short: string = 'M'; // Short 2 byte b2 number Long: string = '0H'; // Long 4 byte b2 number Big: string = 'N'; // Big 8 byte b2 number Huge: string = '0A'; // Huge 16 byte b2 number (same as Salt_128) } export const NumDex = new NumCodex(); export class BexCodex extends Codex { StrB64_L0: string = '4A'; // String Base64 Only Leader Size 0 StrB64_L1: string = '5A'; // String Base64 Only Leader Size 1 StrB64_L2: string = '6A'; // String Base64 Only Leader Size 2 StrB64_Big_L0: string = '7AAA'; // String Base64 Only Big Leader Size 0 StrB64_Big_L1: string = '8AAA'; // String Base64 Only Big Leader Size 1 StrB64_Big_L2: string = '9AAA'; // String Base64 Only Big Leader Size 2 } export const BexDex = new BexCodex(); class SmallVarRawSizeCodex extends Codex { Lead0: string = '4'; // First Selector Character for all ls == 0 codes Lead1: string = '5'; // First Selector Character for all ls == 1 codes Lead2: string = '6'; // First Selector Character for all ls == 2 codes } export const SmallVrzDex = new SmallVarRawSizeCodex(); class LargeVarRawSizeCodex extends Codex { Lead0_Big: string = '7'; // First Selector Character for all ls == 0 codes Lead1_Big: string = '8'; // First Selector Character for all ls == 1 codes Lead2_Big: string = '9'; // First Selector Character for all ls == 2 codes } export const LargeVrzDex = new LargeVarRawSizeCodex(); export class Sizage { public hs: number; public ss: number; public ls?: number; public fs?: number; constructor(hs: number, ss: number, fs?: number, ls?: number) { this.hs = hs; this.ss = ss; this.fs = fs; this.ls = ls!; } } export interface MatterArgs { raw?: Uint8Array | undefined; code?: string; qb64b?: Uint8Array | undefined; qb64?: string; qb2?: Uint8Array | undefined; rize?: number; } export class Matter { static Sizes = new Map( Object.entries({ A: new Sizage(1, 0, 44, 0), B: new Sizage(1, 0, 44, 0), C: new Sizage(1, 0, 44, 0), D: new Sizage(1, 0, 44, 0), E: new Sizage(1, 0, 44, 0), F: new Sizage(1, 0, 44, 0), G: new Sizage(1, 0, 44, 0), H: new Sizage(1, 0, 44, 0), I: new Sizage(1, 0, 44, 0), J: new Sizage(1, 0, 44, 0), K: new Sizage(1, 0, 76, 0), L: new Sizage(1, 0, 76, 0), M: new Sizage(1, 0, 4, 0), N: new Sizage(1, 0, 12, 0), O: new Sizage(1, 0, 44, 0), P: new Sizage(1, 0, 124, 0), Q: new Sizage(1, 0, 44, 0), '0A': new Sizage(2, 0, 24, 0), '0B': new Sizage(2, 0, 88, 0), '0C': new Sizage(2, 0, 88, 0), '0D': new Sizage(2, 0, 88, 0), '0E': new Sizage(2, 0, 88, 0), '0F': new Sizage(2, 0, 88, 0), '0G': new Sizage(2, 0, 88, 0), '0H': new Sizage(2, 0, 8, 0), '0I': new Sizage(2, 0, 88, 0), '1AAA': new Sizage(4, 0, 48, 0), '1AAB': new Sizage(4, 0, 48, 0), '1AAC': new Sizage(4, 0, 80, 0), '1AAD': new Sizage(4, 0, 80, 0), '1AAE': new Sizage(4, 0, 56, 0), '1AAF': new Sizage(4, 0, 8, 0), '1AAG': new Sizage(4, 0, 36, 0), '1AAH': new Sizage(4, 0, 100, 0), '1AAI': new Sizage(4, 0, 48, 0), '1AAJ': new Sizage(4, 0, 48, 0), '2AAA': new Sizage(4, 0, 8, 1), '3AAA': new Sizage(4, 0, 8, 2), '4A': new Sizage(2, 2, undefined, 0), '5A': new Sizage(2, 2, undefined, 1), '6A': new Sizage(2, 2, undefined, 2), '7AAA': new Sizage(4, 4, undefined, 0), '8AAA': new Sizage(4, 4, undefined, 1), '9AAA': new Sizage(4, 4, undefined, 2), '4B': new Sizage(2, 2, undefined, 0), '5B': new Sizage(2, 2, undefined, 1), '6B': new Sizage(2, 2, undefined, 2), '7AAB': new Sizage(4, 4, undefined, 0), '8AAB': new Sizage(4, 4, undefined, 1), '9AAB': new Sizage(4, 4, undefined, 2), }) ); static Hards = new Map<string, number>([ ['A', 1], ['B', 1], ['C', 1], ['D', 1], ['E', 1], ['F', 1], ['G', 1], ['H', 1], ['I', 1], ['J', 1], ['K', 1], ['L', 1], ['M', 1], ['N', 1], ['O', 1], ['P', 1], ['Q', 1], ['R', 1], ['S', 1], ['T', 1], ['U', 1], ['V', 1], ['W', 1], ['X', 1], ['Y', 1], ['Z', 1], ['a', 1], ['b', 1], ['c', 1], ['d', 1], ['e', 1], ['f', 1], ['g', 1], ['h', 1], ['i', 1], ['j', 1], ['k', 1], ['l', 1], ['m', 1], ['n', 1], ['o', 1], ['p', 1], ['q', 1], ['r', 1], ['s', 1], ['t', 1], ['u', 1], ['v', 1], ['w', 1], ['x', 1], ['y', 1], ['z', 1], ['0', 2], ['1', 4], ['2', 4], ['3', 4], ['4', 2], ['5', 2], ['6', 2], ['7', 4], ['8', 4], ['9', 4], ]); private _code: string = ''; private _size: number = -1; private _raw: Uint8Array = new Uint8Array(0); constructor({ raw, code = MtrDex.Ed25519N, qb64b, qb64, qb2, rize, }: MatterArgs) { let size = -1; if (raw != undefined) { if (code.length == 0) { throw new Error( 'Improper initialization need either (raw and code) or qb64b or qb64 or qb2.' ); } if (SmallVrzDex.has(code[0]) || LargeVrzDex.has(code[0])) { if (rize !== undefined) { if (rize < 0) throw new Error( `missing var raw size for code=${code}` ); } else { rize = raw.length; } const ls = (3 - (rize % 3)) % 3; // calc actual lead (pad) size size = Math.floor((rize + ls) / 3); // calculate value of size in triplets if (SmallVrzDex.has(code[0])) { if (size <= 64 ** 2 - 1) { const hs = 2; const s = Object.values(SmallVrzDex)[ls]; code = `${s}${code.substring(1, hs)}`; } else if (size <= 64 ** 4 - 1) { const hs = 4; const s = Object.values(LargeVrzDex)[ls]; code = `${s}${'AAAA'.substring(0, hs - 2)}${code[1]}`; } else { throw new Error( `Unsupported raw size for code=${code}` ); } } else { if (size <= 64 ** 4 - 1) { const hs = 4; const s = Object.values(LargeVrzDex)[ls]; code = `${s}${code.substring(1, hs)}`; } else { throw new Error( `Unsupported raw size for code=${code}` ); } } } else { const sizage = Matter.Sizes.get(code); if (sizage!.fs == -1) { // invalid throw new Error(`Unsupported variable size code=${code}`); } rize = Matter._rawSize(code); } raw = raw.slice(0, rize); // copy only exact size from raw stream if (raw.length != rize) { // forbids shorter throw new Error( `Not enougth raw bytes for code=${code} expected ${rize} got ${raw.length}.` ); } this._code = code; // hard value part of code this._size = size; // soft value part of code in int this._raw = raw; // crypto ops require bytes not bytearray } else if (qb64 !== undefined) { this._exfil(qb64); } else if (qb64b !== undefined) { const qb64 = d(qb64b); this._exfil(qb64); } else if (qb2 !== undefined) { this._bexfil(qb2); } else { throw new EmptyMaterialError('EmptyMaterialError'); } } get code(): string { return this._code; } get size() { return this._size; } get raw(): Uint8Array { return this._raw; } get qb64() { return this._infil(); } get qb64b() { return b(this.qb64); } get transferable(): boolean { return !NonTransDex.has(this.code); } get digestive(): boolean { return DigiDex.has(this.code); } static _rawSize(code: string) { const sizage = this.Sizes.get(code); // get sizes const cs = sizage!.hs + sizage!.ss; // both hard + soft code size if (sizage!.fs === -1) { throw Error(`Non-fixed raw size code ${code}.`); } return Math.floor(((sizage!.fs! - cs) * 3) / 4) - sizage!.ls!; } static _leadSize(code: string) { const sizage = this.Sizes.get(code); return sizage!.ls; } get both() { const sizage = Matter.Sizes.get(this.code); return `${this.code}${intToB64(this.size, sizage!.ss)}`; } private _infil() { const code = this.code; const size = this.size; const raw = this.raw; const ps = (3 - (raw.length % 3)) % 3; // pad size chars or lead size bytes const sizage = Matter.Sizes.get(code); if (sizage!.fs === undefined) { // Variable size code, NOT SUPPORTED const cs = sizage!.hs + sizage!.ss; if (cs % 4) { throw new Error( `Whole code size not multiple of 4 for variable length material. cs=${cs}` ); } if (size < 0 || size > 64 ** sizage!.ss - 1) { throw new Error(`Invalid size=${size} for code=${code}.`); } const both = `${code}${intToB64(size, sizage!.ss)}`; if (both.length % 4 !== ps - sizage!.ls!) { throw new Error( `Invalid code=${both} for converted raw pad size=${ps}.` ); } const bytes = new Uint8Array(sizage!.ls! + raw.length); for (let i = 0; i < sizage!.ls!; i++) { bytes[i] = 0; } for (let i = 0; i < raw.length; i++) { const odx = i + ps; bytes[odx] = raw[i]; } return both + encodeBase64Url(Buffer.from(bytes)); } else { const both = code; const cs = both.length; if (cs % 4 != ps - sizage!.ls!) { // adjusted pad given lead bytes throw new Error( `Invalid code=${both} for converted raw pad size=${ps}, ${raw.length}.` ); } // prepad, convert, and replace upfront // when fixed and ls != 0 then cs % 4 is zero and ps==ls // otherwise fixed and ls == 0 then cs % 4 == ps const bytes = new Uint8Array(ps + raw.length); for (let i = 0; i < ps; i++) { bytes[i] = 0; } for (let i = 0; i < raw.length; i++) { const odx = i + ps; bytes[odx] = raw[i]; } return both + encodeBase64Url(Buffer.from(bytes)).slice(cs % 4); } } private _exfil(qb64: string) { if (qb64.length == 0) { throw new Error('Empty Material'); } const first = qb64[0]; if (!Array.from(Matter.Hards.keys()).includes(first)) { throw new Error(`Unexpected code ${first}`); } const hs = Matter.Hards.get(first); if (qb64.length < hs!) { throw new Error(`Shortage Error`); } const hard = qb64.slice(0, hs); if (!Array.from(Matter.Sizes.keys()).includes(hard)) { throw new Error(`Unsupported code ${hard}`); } const sizage = Matter.Sizes.get(hard); const cs = sizage!.hs + sizage!.ss; let size = -1; if (sizage!.fs == -1) { // Variable size code, Not supported throw new Error('Variable size codes not supported yet'); } else { size = sizage!.fs!; } if (qb64.length < sizage!.fs!) { throw new Error(`Need ${sizage!.fs! - qb64.length} more chars.`); } qb64 = qb64.slice(0, sizage!.fs); const ps = cs % 4; const pbs = 2 * (ps == 0 ? sizage!.ls! : ps); let raw; if (ps != 0) { const base = new Array(ps + 1).join('A') + qb64.slice(cs); const paw = decodeBase64Url(base); // decode base to leave prepadded raw const pi = readInt(paw.subarray(0, ps)); // prepad as int if (pi & (2 ** pbs - 1)) { // masked pad bits non-zero throw new Error( `Non zeroed prepad bits = {pi & (2 ** pbs - 1 ):<06b} in {qb64b[cs:cs+1]}.` ); } raw = paw.subarray(ps); // strip off ps prepad paw bytes } else { const base = qb64.slice(cs); const paw = decodeBase64Url(base); const li = readInt(paw.subarray(0, sizage!.ls)); if (li != 0) { if (li == 1) { throw new Error(`Non zeroed lead byte = 0x{li:02x}.`); } else { throw new Error(`Non zeroed lead bytes = 0x{li:04x}`); } } raw = paw.subarray(sizage!.ls); } this._code = hard; // hard only this._size = size; this._raw = Uint8Array.from(raw); // ensure bytes so immutable and for crypto ops } private _bexfil(qb2: Uint8Array) { throw new Error(`qb2 not yet supported: ${qb2}`); } }