UNPKG

signify-ts

Version:

Signing at the edge for KERI, ACDC, and KERIA

510 lines (446 loc) 17.8 kB
import { EmptyMaterialError } from './kering'; import { b, b64ToInt, d, intToB64, readInt } from './core'; import { Buffer } from 'buffer'; import { decodeBase64Url, encodeBase64Url } from './base64'; export class IndexerCodex { Ed25519_Sig: string = 'A'; // Ed25519 sig appears same in both lists if any. Ed25519_Crt_Sig: string = 'B'; // Ed25519 sig appears in current list only. ECDSA_256k1_Sig: string = 'C'; // ECDSA secp256k1 sig appears same in both lists if any. ECDSA_256k1_Crt_Sig: string = 'D'; // ECDSA secp256k1 sig appears in current list. ECDSA_256r1_Sig: string = 'E'; // ECDSA secp256r1 sig appears same in both lists if any. ECDSA_256r1_Crt_Sig: string = 'F'; // ECDSA secp256r1 sig appears in current list. Ed448_Sig: string = '0A'; // Ed448 signature appears in both lists. Ed448_Crt_Sig: string = '0B'; // Ed448 signature appears in current list only. Ed25519_Big_Sig: string = '2A'; // Ed25519 sig appears in both lists. Ed25519_Big_Crt_Sig: string = '2B'; // Ed25519 sig appears in current list only. ECDSA_256k1_Big_Sig: string = '2C'; // ECDSA secp256k1 sig appears in both lists. ECDSA_256k1_Big_Crt_Sig: string = '2D'; // ECDSA secp256k1 sig appears in current list only. ECDSA_256r1_Big_Sig: string = '2E'; // ECDSA secp256r1 sig appears in both lists. ECDSA_256r1_Big_Crt_Sig: string = '2F'; // ECDSA secp256r1 sig appears in current list only. Ed448_Big_Sig: string = '3A'; // Ed448 signature appears in both lists. Ed448_Big_Crt_Sig: string = '3B'; // Ed448 signature appears in current list only. } export const IdrDex = new IndexerCodex(); export class IndexedSigCodex { Ed25519_Sig: string = 'A'; // Ed25519 sig appears same in both lists if any. Ed25519_Crt_Sig: string = 'B'; // Ed25519 sig appears in current list only. ECDSA_256k1_Sig: string = 'C'; // ECDSA secp256k1 sig appears same in both lists if any. ECDSA_256k1_Crt_Sig: string = 'D'; // ECDSA secp256k1 sig appears in current list. ECDSA_256r1_Sig: string = 'E'; // ECDSA secp256r1 sig appears same in both lists if any. ECDSA_256r1_Crt_Sig: string = 'F'; // ECDSA secp256r1 sig appears in current list. Ed448_Sig: string = '0A'; // Ed448 signature appears in both lists. Ed448_Crt_Sig: string = '0B'; // Ed448 signature appears in current list only. Ed25519_Big_Sig: string = '2A'; // Ed25519 sig appears in both lists. Ed25519_Big_Crt_Sig: string = '2B'; // Ed25519 sig appears in current list only. ECDSA_256k1_Big_Sig: string = '2C'; // ECDSA secp256k1 sig appears in both lists. ECDSA_256k1_Big_Crt_Sig: string = '2D'; // ECDSA secp256k1 sig appears in current list only. ECDSA_256r1_Big_Sig: string = '2E'; // ECDSA secp256r1 sig appears in both lists. ECDSA_256r1_Big_Crt_Sig: string = '2F'; // ECDSA secp256r1 sig appears in current list only. Ed448_Big_Sig: string = '3A'; // Ed448 signature appears in both lists. Ed448_Big_Crt_Sig: string = '3B'; // Ed448 signature appears in current list only. has(prop: string): boolean { const m = new Map( Array.from(Object.entries(this), (v) => [v[1], v[0]]) ); return m.has(prop); } } export const IdxSigDex = new IndexedSigCodex(); export class IndexedCurrentSigCodex { Ed25519_Crt_Sig: string = 'B'; // Ed25519 sig appears in current list only. ECDSA_256k1_Crt_Sig: string = 'D'; // ECDSA secp256k1 sig appears in current list only. ECDSA_256r1_Crt_Sig: string = 'F'; // ECDSA secp256r1 sig appears in current list. Ed448_Crt_Sig: string = '0B'; // Ed448 signature appears in current list only. Ed25519_Big_Crt_Sig: string = '2B'; // Ed25519 sig appears in current list only. ECDSA_256k1_Big_Crt_Sig: string = '2D'; // ECDSA secp256k1 sig appears in current list only. ECDSA_256r1_Big_Crt_Sig: string = '2F'; // ECDSA secp256r1 sig appears in current list only. Ed448_Big_Crt_Sig: string = '3B'; // Ed448 signature appears in current list only. has(prop: string): boolean { const m = new Map( Array.from(Object.entries(this), (v) => [v[1], v[0]]) ); return m.has(prop); } } export const IdxCrtSigDex = new IndexedCurrentSigCodex(); export class IndexedBothSigCodex { Ed25519_Sig: string = 'A'; // Ed25519 sig appears same in both lists if any. ECDSA_256k1_Sig: string = 'C'; // ECDSA secp256k1 sig appears same in both lists if any. Ed448_Sig: string = '0A'; // Ed448 signature appears in both lists. Ed25519_Big_Sig: string = '2A'; // Ed25519 sig appears in both listsy. ECDSA_256k1_Big_Sig: string = '2C'; // ECDSA secp256k1 sig appears in both lists. Ed448_Big_Sig: string = '3A'; // Ed448 signature appears in both lists. has(prop: string): boolean { const m = new Map( Array.from(Object.entries(this), (v) => [v[1], v[0]]) ); return m.has(prop); } } export const IdxBthSigDex = new IndexedBothSigCodex(); export class Xizage { public hs: number; public ss: number; public os: number; public fs?: number; public ls: number; constructor(hs: number, ss: number, os: number, fs?: number, ls?: number) { this.hs = hs; this.ss = ss; this.os = os; this.fs = fs; this.ls = ls!; } } export interface IndexerArgs { raw?: Uint8Array | undefined; code?: string | undefined; index?: number; ondex?: number; qb64b?: Uint8Array | undefined; qb64?: string | undefined; qb2?: Uint8Array | undefined; } export class Indexer { public Codex = IdrDex; 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', 2], ['2', 2], ['3', 2], ['4', 2], ]); static Sizes = new Map( Object.entries({ A: new Xizage(1, 1, 0, 88, 0), B: new Xizage(1, 1, 0, 88, 0), C: new Xizage(1, 1, 0, 88, 0), D: new Xizage(1, 1, 0, 88, 0), E: new Xizage(1, 1, 0, 88, 0), F: new Xizage(1, 1, 0, 88, 0), '0A': new Xizage(2, 2, 1, 156, 0), '0B': new Xizage(2, 2, 1, 156, 0), '2A': new Xizage(2, 4, 2, 92, 0), '2B': new Xizage(2, 4, 2, 92, 0), '2C': new Xizage(2, 4, 2, 92, 0), '2D': new Xizage(2, 4, 2, 92, 0), '2E': new Xizage(2, 4, 2, 92, 0), '2F': new Xizage(2, 4, 2, 92, 0), '3A': new Xizage(2, 6, 3, 160, 0), '3B': new Xizage(2, 6, 3, 160, 0), '0z': new Xizage(2, 2, 0, undefined, 0), '1z': new Xizage(2, 2, 1, 76, 1), '4z': new Xizage(2, 6, 3, 80, 1), }) ); private _code: string = ''; private _index: number = -1; private _ondex: number | undefined; private _raw: Uint8Array = new Uint8Array(0); constructor({ raw = undefined, code = IdrDex.Ed25519_Sig, index = 0, ondex = undefined, qb64b = undefined, qb64 = undefined, qb2 = undefined, }: IndexerArgs) { if (raw != undefined) { if (code == undefined) { throw new EmptyMaterialError( `Improper initialization need either (raw and code) or qb64b or qb64 or qb2.` ); } if (!Indexer.Sizes.has(code)) { throw new Error(`Unsupported code=${code}.`); } const xizage = Indexer.Sizes.get(code)!; const os = xizage.os; const fs = xizage.fs; const cs = xizage.hs + xizage.ss; const ms = xizage.ss - xizage.os; if (!Number.isInteger(index) || index < 0 || index > 64 ** ms - 1) { throw new Error(`Invalid index=${index} for code=${code}.`); } if ( ondex != undefined && xizage.os != 0 && !(ondex >= 0 && ondex <= 64 ** os - 1) ) { throw new Error(`Invalid ondex=${ondex} for code=${code}.`); } if (IdxCrtSigDex.has(code) && ondex != undefined) { throw new Error(`Non None ondex=${ondex} for code=${code}.`); } if (IdxBthSigDex.has(code)) { if (ondex == undefined) { ondex = index; } else { if (ondex != index && os == 0) { throw new Error( `Non matching ondex=${ondex} and index=${index} for code=${code}.` ); } } } if (fs == undefined) { throw new Error('variable length unsupported'); } // TODO: Don't support this code // if not fs: # compute fs from index // if cs % 4: // raise InvalidCodeSizeError(f"Whole code size not multiple of 4 for " // f"variable length material. cs={cs}.") // if os != 0: // raise InvalidCodeSizeError(f"Non-zero other index size for " // f"variable length material. os={os}.") // fs = (index * 4) + cs const rawsize = Math.floor(((fs - cs) * 3) / 4); raw = raw.slice(0, rawsize); if (raw.length != rawsize) { throw new Error( `Not enougth raw bytes for code=${code} and index=${index} ,expected ${rawsize} got ${raw.length}.` ); } this._code = code; this._index = index; this._ondex = ondex; this._raw = raw; } else if (qb64b != undefined) { const qb64 = d(qb64b); this._exfil(qb64); } else if (qb64 != undefined) { this._exfil(qb64); } else if (qb2 != undefined) { this._bexfil(qb2); } else { throw new EmptyMaterialError( `Improper initialization need either (raw and code and index) or qb64b or qb64 or qb2.` ); } } private _bexfil(qb2: Uint8Array) { throw new Error(`qb2 not yet supported: ${qb2}`); } public static _rawSize(code: string) { const xizage = Indexer.Sizes.get(code)!; return Math.floor(xizage.fs! - ((xizage.hs + xizage.ss) * 3) / 4); } get code(): string { return this._code; } get raw(): Uint8Array { return this._raw; } get index(): number { return this._index; } get ondex(): number | undefined { return this._ondex; } get qb64(): string { return this._infil(); } get qb64b() { return b(this.qb64); } private _infil(): string { const code = this.code; const index = this.index; const ondex = this.ondex; const raw = this.raw; const ps = (3 - (raw.length % 3)) % 3; const xizage = Indexer.Sizes.get(code)!; const cs = xizage.hs + xizage.ss; const ms = xizage.ss - xizage.os; // TODO: don't support this code // if not fs: # compute fs from index // if cs % 4: // raise InvalidCodeSizeError(f"Whole code size not multiple of 4 for " // f"variable length material. cs={cs}.") // if os != 0: // raise InvalidCodeSizeError(f"Non-zero other index size for " // f"variable length material. os={os}.") // fs = (index * 4) + cs if (index < 0 || index > 64 ** ms - 1) { throw new Error(`Invalid index=${index} for code=${code}.`); } if ( ondex != undefined && xizage.os != 0 && !(ondex >= 0 && ondex <= 64 ** xizage.os - 1) ) { throw new Error( `Invalid ondex=${ondex} for os=${xizage.os} and code=${code}.` ); } const both = `${code}${intToB64(index, ms)}${intToB64( ondex == undefined ? 0 : ondex, xizage.os )}`; if (both.length != cs) { throw new Error( `Mismatch code size = ${cs} with table = ${both.length}.` ); } if (cs % 4 != ps - xizage.ls) { throw new Error( `Invalid code=${both} for converted raw pad size=${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]; } const full = both + encodeBase64Url(Buffer.from(bytes)).slice(ps - xizage.ls); if (full.length != xizage.fs) { throw new Error(`Invalid code=${both} for raw size=${raw.length}.`); } return full; } _exfil(qb64: string) { if (qb64.length == 0) { throw new Error('Empty Material'); } const first = qb64[0]; if (!Array.from(Indexer.Hards.keys()).includes(first)) { throw new Error(`Unexpected code ${first}`); } const hs = Indexer.Hards.get(first)!; if (qb64.length < hs) { throw new Error(`Need ${hs - qb64.length} more characters.`); } const hard = qb64.slice(0, hs); if (!Array.from(Indexer.Sizes.keys()).includes(hard)) { throw new Error(`Unsupported code ${hard}`); } const xizage = Indexer.Sizes.get(hard)!; const cs = xizage.hs + xizage.ss; // both hard + soft code size const ms = xizage.ss - xizage.os; if (qb64.length < cs) { throw new Error(`Need ${cs - qb64.length} more characters.`); } const sindex = qb64.slice(hs, hs + ms); const index = b64ToInt(sindex); const sondex = qb64.slice(hs + ms, hs + ms + xizage.os); let ondex; if (IdxCrtSigDex.has(hard)) { ondex = xizage.os != 0 ? b64ToInt(sondex) : undefined; if (ondex != 0 && ondex != undefined) { throw new Error(`Invalid ondex=${ondex} for code=${hard}.`); } else { ondex = undefined; } } else { ondex = xizage.os != 0 ? b64ToInt(sondex) : index; } if (xizage.fs == undefined) { throw new Error('variable length not supported'); } // TODO: support variable length // if not fs: # compute fs from index which means variable length // if cs % 4: // raise ValidationError(f"Whole code size not multiple of 4 for " // f"variable length material. cs={cs}.") // if os != 0: // raise ValidationError(f"Non-zero other index size for " // f"variable length material. os={os}.") // fs = (index * 4) + cs if (qb64.length < xizage.fs) { throw new Error(`Need ${xizage.fs - qb64.length} more chars.`); } qb64 = qb64.slice(0, xizage.fs); const ps = cs % 4; const pbs = 2 * ps != 0 ? ps : xizage.ls; 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.slice(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.slice(ps); // strip off ps prepad paw bytes } else { const base = qb64.slice(cs); const paw = decodeBase64Url(base); const li = readInt(paw.slice(0, xizage!.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.slice(xizage!.ls); } if (raw.length != Math.floor(((qb64.length - cs) * 3) / 4)) { throw new Error(`Improperly qualified material = ${qb64}`); } this._code = hard; this._index = index; this._ondex = ondex; this._raw = new Uint8Array(raw); // must be bytes for crpto opts and immutable not bytearray } }