signify-ts
Version:
Signing at the edge for KERI, ACDC, and KERIA
510 lines (446 loc) • 17.8 kB
text/typescript
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
}
}