signify-ts
Version:
Signing at the edge for KERI, ACDC, and KERIA
408 lines (407 loc) • 16.4 kB
JavaScript
import { EmptyMaterialError } from "./kering.js";
import { b, b64ToInt, d, intToB64, readInt } from "./core.js";
import { decodeBase64Url, encodeBase64Url } from "./base64.js";
export class IndexerCodex {
constructor() {
this.Ed25519_Sig = 'A'; // Ed25519 sig appears same in both lists if any.
this.Ed25519_Crt_Sig = 'B'; // Ed25519 sig appears in current list only.
this.ECDSA_256k1_Sig = 'C'; // ECDSA secp256k1 sig appears same in both lists if any.
this.ECDSA_256k1_Crt_Sig = 'D'; // ECDSA secp256k1 sig appears in current list.
this.ECDSA_256r1_Sig = 'E'; // ECDSA secp256r1 sig appears same in both lists if any.
this.ECDSA_256r1_Crt_Sig = 'F'; // ECDSA secp256r1 sig appears in current list.
this.Ed448_Sig = '0A'; // Ed448 signature appears in both lists.
this.Ed448_Crt_Sig = '0B'; // Ed448 signature appears in current list only.
this.Ed25519_Big_Sig = '2A'; // Ed25519 sig appears in both lists.
this.Ed25519_Big_Crt_Sig = '2B'; // Ed25519 sig appears in current list only.
this.ECDSA_256k1_Big_Sig = '2C'; // ECDSA secp256k1 sig appears in both lists.
this.ECDSA_256k1_Big_Crt_Sig = '2D'; // ECDSA secp256k1 sig appears in current list only.
this.ECDSA_256r1_Big_Sig = '2E'; // ECDSA secp256r1 sig appears in both lists.
this.ECDSA_256r1_Big_Crt_Sig = '2F'; // ECDSA secp256r1 sig appears in current list only.
this.Ed448_Big_Sig = '3A'; // Ed448 signature appears in both lists.
this.Ed448_Big_Crt_Sig = '3B'; // Ed448 signature appears in current list only.
}
}
export const IdrDex = new IndexerCodex();
export class IndexedSigCodex {
constructor() {
this.Ed25519_Sig = 'A'; // Ed25519 sig appears same in both lists if any.
this.Ed25519_Crt_Sig = 'B'; // Ed25519 sig appears in current list only.
this.ECDSA_256k1_Sig = 'C'; // ECDSA secp256k1 sig appears same in both lists if any.
this.ECDSA_256k1_Crt_Sig = 'D'; // ECDSA secp256k1 sig appears in current list.
this.ECDSA_256r1_Sig = 'E'; // ECDSA secp256r1 sig appears same in both lists if any.
this.ECDSA_256r1_Crt_Sig = 'F'; // ECDSA secp256r1 sig appears in current list.
this.Ed448_Sig = '0A'; // Ed448 signature appears in both lists.
this.Ed448_Crt_Sig = '0B'; // Ed448 signature appears in current list only.
this.Ed25519_Big_Sig = '2A'; // Ed25519 sig appears in both lists.
this.Ed25519_Big_Crt_Sig = '2B'; // Ed25519 sig appears in current list only.
this.ECDSA_256k1_Big_Sig = '2C'; // ECDSA secp256k1 sig appears in both lists.
this.ECDSA_256k1_Big_Crt_Sig = '2D'; // ECDSA secp256k1 sig appears in current list only.
this.ECDSA_256r1_Big_Sig = '2E'; // ECDSA secp256r1 sig appears in both lists.
this.ECDSA_256r1_Big_Crt_Sig = '2F'; // ECDSA secp256r1 sig appears in current list only.
this.Ed448_Big_Sig = '3A'; // Ed448 signature appears in both lists.
this.Ed448_Big_Crt_Sig = '3B'; // Ed448 signature appears in current list only.
}
has(prop) {
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 {
constructor() {
this.Ed25519_Crt_Sig = 'B'; // Ed25519 sig appears in current list only.
this.ECDSA_256k1_Crt_Sig = 'D'; // ECDSA secp256k1 sig appears in current list only.
this.ECDSA_256r1_Crt_Sig = 'F'; // ECDSA secp256r1 sig appears in current list.
this.Ed448_Crt_Sig = '0B'; // Ed448 signature appears in current list only.
this.Ed25519_Big_Crt_Sig = '2B'; // Ed25519 sig appears in current list only.
this.ECDSA_256k1_Big_Crt_Sig = '2D'; // ECDSA secp256k1 sig appears in current list only.
this.ECDSA_256r1_Big_Crt_Sig = '2F'; // ECDSA secp256r1 sig appears in current list only.
this.Ed448_Big_Crt_Sig = '3B'; // Ed448 signature appears in current list only.
}
has(prop) {
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 {
constructor() {
this.Ed25519_Sig = 'A'; // Ed25519 sig appears same in both lists if any.
this.ECDSA_256k1_Sig = 'C'; // ECDSA secp256k1 sig appears same in both lists if any.
this.Ed448_Sig = '0A'; // Ed448 signature appears in both lists.
this.Ed25519_Big_Sig = '2A'; // Ed25519 sig appears in both listsy.
this.ECDSA_256k1_Big_Sig = '2C'; // ECDSA secp256k1 sig appears in both lists.
this.Ed448_Big_Sig = '3A'; // Ed448 signature appears in both lists.
}
has(prop) {
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 {
constructor(hs, ss, os, fs, ls) {
this.hs = hs;
this.ss = ss;
this.os = os;
this.fs = fs;
this.ls = ls;
}
}
export class Indexer {
constructor({ raw = undefined, code = IdrDex.Ed25519_Sig, index = 0, ondex = undefined, qb64b = undefined, qb64 = undefined, qb2 = undefined, }) {
this.Codex = IdrDex;
this._code = '';
this._index = -1;
this._raw = new Uint8Array(0);
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.`);
}
}
_bexfil(qb2) {
throw new Error(`qb2 not yet supported: ${qb2}`);
}
static _rawSize(code) {
const xizage = Indexer.Sizes.get(code);
return Math.floor(xizage.fs - ((xizage.hs + xizage.ss) * 3) / 4);
}
get code() {
return this._code;
}
get raw() {
return this._raw;
}
get index() {
return this._index;
}
get ondex() {
return this._ondex;
}
get qb64() {
return this._infil();
}
get qb64b() {
return b(this.qb64);
}
_infil() {
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(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) {
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
}
}
Indexer.Hards = new Map([
['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],
]);
Indexer.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),
}));