@iden3/js-iden3-core
Version:
Low level API to create and manipulate iden3 Claims.
597 lines (595 loc) • 23.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClaimOptions = exports.Claim = exports.Flags = exports.MerklizedRootPosition = exports.MerklizedFlag = exports.IdPosition = exports.SubjectFlag = exports.ErrSlotOverflow = exports.SlotName = void 0;
const schemaHash_1 = require("./schemaHash");
const elemBytes_1 = require("./elemBytes");
const constants_1 = require("./constants");
const id_1 = require("./id");
const utils_1 = require("./utils");
const js_crypto_1 = require("@iden3/js-crypto");
/*
Claim structure
Index:
i_0: [ 128 bits ] claim schema
[ 32 bits ] option flags
[3] Subject:
000: A.1 Self
001: invalid
010: A.2.i OtherIden Index
011: A.2.v OtherIden Value
100: B.i Object Index
101: B.v Object Value
[1] Expiration: bool
[1] Updatable: bool
[3] Merklized: data is merklized root is stored in the:
000: none
001: C.i Root Index (root located in i_2)
010: C.v Root Value (root located in v_2)
[24] 0
[ 32 bits ] version (optional?)
[ 61 bits ] 0 - reserved for future use
i_1: [ 248 bits] identity (case b) (optional)
[ 5 bits ] 0
i_2: [ 253 bits] 0
i_3: [ 253 bits] 0
Value:
v_0: [ 64 bits ] revocation nonce
[ 64 bits ] expiration date (optional)
[ 125 bits] 0 - reserved
v_1: [ 248 bits] identity (case c) (optional)
[ 5 bits ] 0
v_2: [ 253 bits] 0
v_3: [ 253 bits] 0
*/
var SlotName;
(function (SlotName) {
SlotName["IndexA"] = "IndexA";
SlotName["IndexB"] = "IndexB";
SlotName["ValueA"] = "ValueA";
SlotName["ValueB"] = "ValueB";
})(SlotName = exports.SlotName || (exports.SlotName = {}));
// ErrSlotOverflow means some ElemBytes overflows Q Field. And wraps the name
// of overflowed slot.
class ErrSlotOverflow extends Error {
constructor(msg) {
super(`Slot ${msg} not in field (too large)`);
Object.setPrototypeOf(this, ErrSlotOverflow.prototype);
}
}
exports.ErrSlotOverflow = ErrSlotOverflow;
// subjectFlag for the time being describes the location of Id (in index or value
// slots or nowhere at all).
//
// Values subjectFlagInvalid presents for backward compatibility and for now means nothing.
var SubjectFlag;
(function (SubjectFlag) {
SubjectFlag[SubjectFlag["Self"] = 0] = "Self";
SubjectFlag[SubjectFlag["Invalid"] = 1] = "Invalid";
SubjectFlag[SubjectFlag["OtherIdenIndex"] = 2] = "OtherIdenIndex";
SubjectFlag[SubjectFlag["OtherIdenValue"] = 3] = "OtherIdenValue";
})(SubjectFlag = exports.SubjectFlag || (exports.SubjectFlag = {}));
var IdPosition;
(function (IdPosition) {
IdPosition[IdPosition["None"] = 0] = "None";
IdPosition[IdPosition["Index"] = 1] = "Index";
IdPosition[IdPosition["Value"] = 2] = "Value";
})(IdPosition = exports.IdPosition || (exports.IdPosition = {}));
// merklizedFlag for the time being describes the location of root (in index or value
// slots or nowhere at all).
//
// Values merklizedFlagIndex indicates that root is located in index[2] slots.
// Values merklizedFlagValue indicates that root is located in value[2] slots.
var MerklizedFlag;
(function (MerklizedFlag) {
MerklizedFlag[MerklizedFlag["None"] = 0] = "None";
MerklizedFlag[MerklizedFlag["Index"] = 32] = "Index";
MerklizedFlag[MerklizedFlag["Value"] = 64] = "Value";
MerklizedFlag[MerklizedFlag["Invalid"] = 128] = "Invalid";
})(MerklizedFlag = exports.MerklizedFlag || (exports.MerklizedFlag = {}));
var MerklizedRootPosition;
(function (MerklizedRootPosition) {
MerklizedRootPosition[MerklizedRootPosition["None"] = 0] = "None";
MerklizedRootPosition[MerklizedRootPosition["Index"] = 1] = "Index";
MerklizedRootPosition[MerklizedRootPosition["Value"] = 2] = "Value";
})(MerklizedRootPosition = exports.MerklizedRootPosition || (exports.MerklizedRootPosition = {}));
var Flags;
(function (Flags) {
Flags[Flags["ByteIdx"] = 16] = "ByteIdx";
Flags[Flags["ExpirationBitIdx"] = 3] = "ExpirationBitIdx";
Flags[Flags["UpdatableBitIdx"] = 4] = "UpdatableBitIdx";
})(Flags = exports.Flags || (exports.Flags = {}));
class Claim {
constructor() {
this._index = [];
this._value = [];
for (let i = 0; i < constants_1.Constants.ELEM_BYTES_LENGTH; i++) {
this._index[i] = new elemBytes_1.ElemBytes();
this._value[i] = new elemBytes_1.ElemBytes();
}
}
// NewClaim creates new Claim with specified SchemaHash and any number of
// options. Using options you can specify any field in claim.
static newClaim(sh, ...args) {
const c = new Claim();
c.setSchemaHash(sh);
for (let i = 0; i < args.length; i++) {
const fn = args[i];
fn(c);
}
return c;
}
// GetSchemaHash return copy of claim's schema hash.
getSchemaHash() {
return new schemaHash_1.SchemaHash(this._index[0].bytes.slice(0, constants_1.Constants.SCHEMA.HASH_LENGTH));
}
get value() {
return this._value;
}
set value(value) {
this._value = value;
}
get index() {
return this._index;
}
set index(value) {
this._index = value;
}
// SetSchemaHash updates claim's schema hash.
setSchemaHash(sh) {
this._index[0] = new elemBytes_1.ElemBytes(Uint8Array.from([...sh.bytes, ...new Array(constants_1.Constants.SCHEMA.HASH_LENGTH).fill(0)]));
}
setSubject(s) {
// clean first 3 bits
this._index[0].bytes[Flags.ByteIdx] &= 0b11111000;
this._index[0].bytes[Flags.ByteIdx] |= s;
}
getSubject() {
let sbj = this._index[0].bytes[Flags.ByteIdx];
// clean all except first 3 bits
sbj &= 0b00000111;
return sbj;
}
setFlagExpiration(val) {
if (val) {
this._index[0].bytes[Flags.ByteIdx] |= 0b1 << Flags.ExpirationBitIdx;
}
else {
this._index[0].bytes[Flags.ByteIdx] &= ~(0b1 << Flags.ExpirationBitIdx);
}
}
getFlagExpiration() {
const mask = 0b1 << Flags.ExpirationBitIdx;
return (this._index[0].bytes[Flags.ByteIdx] & mask) > 0;
}
// GetIDPosition returns the position at which the Id is stored.
getIdPosition() {
switch (this.getSubject()) {
case SubjectFlag.Self:
return IdPosition.None;
case SubjectFlag.OtherIdenIndex:
return IdPosition.Index;
case SubjectFlag.OtherIdenValue:
return IdPosition.Value;
default:
throw constants_1.Constants.ERRORS.INVALID_SUBJECT_POSITION;
}
}
// SetValueDataInts sets data to value slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
setValueDataInts(slotA, slotB) {
this._value[2] = this.setSlotInt(slotA, SlotName.ValueA);
this._value[3] = this.setSlotInt(slotB, SlotName.ValueB);
}
// SetValueDataBytes sets data to value slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
setValueDataBytes(slotA, slotB) {
this._value[2] = this.setSlotBytes(slotA, SlotName.ValueA);
this._value[3] = this.setSlotBytes(slotB, SlotName.ValueB);
}
// SetValueData sets data to value slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
setValueData(slotA, slotB) {
const slotsAsInts = [slotA.toBigInt(), slotB.toBigInt()];
if (!(0, utils_1.checkBigIntArrayInField)(slotsAsInts)) {
throw constants_1.Constants.ERRORS.DATA_OVERFLOW;
}
this._value[2] = slotA;
this._value[3] = slotB;
}
// SetIndexDataInts sets data to index slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
setIndexDataInts(slotA, slotB) {
this._index[2] = this.setSlotInt(slotA, SlotName.IndexA);
this._index[3] = this.setSlotInt(slotB, SlotName.IndexB);
}
// SetIndexDataBytes sets data to index slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
setIndexDataBytes(slotA, slotB) {
this._index[2] = this.setSlotBytes(slotA, SlotName.IndexA);
this._index[3] = this.setSlotBytes(slotB, SlotName.IndexB);
}
setSlotBytes(value, slotName) {
const slot = new elemBytes_1.ElemBytes(value);
if (!(0, utils_1.checkBigIntInField)(slot.toBigInt())) {
throw new ErrSlotOverflow(slotName);
}
return slot;
}
setFlagMerklized(s) {
let f;
switch (s) {
case MerklizedRootPosition.Index:
f = MerklizedFlag.Index;
break;
case MerklizedRootPosition.Value:
f = MerklizedFlag.Value;
break;
default:
f = MerklizedFlag.None;
}
// clean last 3 bits
this.index[0].bytes[Flags.ByteIdx] &= 0b00011111;
this.index[0].bytes[Flags.ByteIdx] |= f;
}
getMerklized() {
let mt = this.index[0].bytes[Flags.ByteIdx];
// clean all except last 3 bits
mt &= 0b11100000;
return mt;
}
// GetMerklizedPosition returns the position at which the Merklized flag is stored.
getMerklizedPosition() {
switch (this.getMerklized()) {
case MerklizedFlag.None:
return MerklizedRootPosition.None;
case MerklizedFlag.Index:
return MerklizedRootPosition.Index;
case MerklizedFlag.Value:
return MerklizedRootPosition.Value;
default:
throw constants_1.Constants.ERRORS.INCORRECT_MERKLIZED_POSITION;
}
}
setSlotInt(value, slotName) {
if (!value) {
value = BigInt(0);
}
if (!(0, utils_1.checkBigIntInField)(value)) {
throw new ErrSlotOverflow(slotName);
}
return new elemBytes_1.ElemBytes().setBigInt(value);
}
// SetIndexData sets data to index slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
setIndexData(slotA, slotB) {
const slotsAsInts = [slotA.toBigInt(), slotB.toBigInt()];
if (!(0, utils_1.checkBigIntArrayInField)(slotsAsInts)) {
throw constants_1.Constants.ERRORS.DATA_OVERFLOW;
}
this._index[2] = slotA;
this._index[3] = slotB;
}
resetExpirationDate() {
this.setFlagExpiration(false);
const bytes = Array.from({ length: constants_1.Constants.NONCE_BYTES_LENGTH }, () => 0);
const arr = Array.from(this._value[0].bytes);
arr.splice(constants_1.Constants.NONCE_BYTES_LENGTH, constants_1.Constants.NONCE_BYTES_LENGTH, ...bytes);
this._value[0] = new elemBytes_1.ElemBytes(Uint8Array.from(arr));
}
// GetExpirationDate returns expiration date and flag. Flag is true if
// expiration date is present, false if null.
getExpirationDate() {
if (this.getFlagExpiration()) {
const unixTimestamp = (0, utils_1.getUint64)(this._value[0].bytes.slice(8, 16));
return (0, utils_1.getDateFromUnixTimestamp)(Number(unixTimestamp));
}
return null;
}
// SetExpirationDate sets expiration date to dt
setExpirationDate(dt) {
this.setFlagExpiration(true);
const bytes = (0, utils_1.putUint64)(BigInt((0, utils_1.getUnixTimestamp)(dt)));
const arr = Array.from(this._value[0].bytes);
arr.splice(constants_1.Constants.NONCE_BYTES_LENGTH, constants_1.Constants.NONCE_BYTES_LENGTH, ...bytes);
this._value[0] = new elemBytes_1.ElemBytes(Uint8Array.from(arr));
}
// GetRevocationNonce returns revocation nonce
getRevocationNonce() {
return (0, utils_1.getUint64)(this._value[0].bytes.slice(0, 8));
}
// SetRevocationNonce sets claim's revocation nonce
setRevocationNonce(nonce) {
const bytes = (0, utils_1.putUint64)(nonce);
if (bytes.length > constants_1.Constants.NONCE_BYTES_LENGTH) {
throw new Error('Nonce length is not valid');
}
const arr = Array.from(this._value[0].bytes);
arr.splice(0, constants_1.Constants.NONCE_BYTES_LENGTH, ...bytes);
this._value[0] = new elemBytes_1.ElemBytes(Uint8Array.from(arr));
}
getValueId() {
return id_1.Id.fromBytes(this._value[1].bytes.slice(0, -1));
}
// SetValueId sets id to value. Removes id from index if any.
setValueId(id) {
this.resetIndexId();
this.setSubject(SubjectFlag.OtherIdenValue);
const arr = Array.from(this._index[1].bytes);
arr.splice(0, id.bytes.length, ...id.bytes);
this._value[1] = new elemBytes_1.ElemBytes(Uint8Array.from(arr));
}
resetIndexId() {
this._index[1] = new elemBytes_1.ElemBytes(new Uint8Array(constants_1.Constants.BYTES_LENGTH).fill(0));
}
resetValueId() {
this._value[1] = new elemBytes_1.ElemBytes(new Uint8Array(constants_1.Constants.BYTES_LENGTH).fill(0));
}
getIndexId() {
return id_1.Id.fromBytes(this._index[1].bytes.slice(0, -1));
}
// SetIndexId sets id to index. Removes id from value if any.
setIndexId(id) {
this.resetValueId();
this.setSubject(SubjectFlag.OtherIdenIndex);
const arr = Array.from(this._index[1].bytes);
arr.splice(0, id.bytes.length, ...id.bytes);
this._index[1] = new elemBytes_1.ElemBytes(Uint8Array.from(arr));
}
// SetVersion sets claim's version
setVersion(ver) {
const bytes = (0, utils_1.putUint32)(ver);
this._index[0].bytes[20] = bytes[0];
this._index[0].bytes[21] = bytes[1];
this._index[0].bytes[22] = bytes[2];
this._index[0].bytes[23] = bytes[3];
}
// GetVersion returns claim's version
getVersion() {
return (0, utils_1.getUint32)(this._index[0].bytes.slice(20, 24));
}
// SetFlagUpdatable sets claim's flag `updatable`
setFlagUpdatable(val) {
if (val) {
this._index[0].bytes[Flags.ByteIdx] |= 0b1 << Flags.UpdatableBitIdx;
}
else {
this._index[0].bytes[Flags.ByteIdx] &= ~(0b1 << Flags.UpdatableBitIdx);
}
}
// HIndex calculates the hash of the Index of the Claim
hIndex() {
return js_crypto_1.poseidon.hash(elemBytes_1.ElemBytes.elemBytesToInts(this._index));
}
// GetFlagUpdatable returns claim's flag `updatable`
getFlagUpdatable() {
const mask = 0b1 << Flags.UpdatableBitIdx;
return (this._index[0].bytes[Flags.ByteIdx] & mask) > 0;
}
// HValue calculates the hash of the Value of the Claim
hValue() {
return js_crypto_1.poseidon.hash(elemBytes_1.ElemBytes.elemBytesToInts(this._value));
}
// HiHv returns the HIndex and HValue of the Claim
hiHv() {
return { hi: this.hIndex(), hv: this.hValue() };
}
// SetIndexMerklizedRoot sets merklized root to index. Removes root from value[2] if any.
setIndexMerklizedRoot(r) {
this.resetValueMerklizedRoot();
this.setFlagMerklized(MerklizedRootPosition.Index);
this.index[2] = this.setSlotInt(r, SlotName.IndexA);
}
resetIndexMerklizedRoot() {
this._index[2] = new elemBytes_1.ElemBytes(new Uint8Array(constants_1.Constants.BYTES_LENGTH).fill(0));
}
// SetValueMerklizedRoot sets merklized root to value. Removes root from index[2] if any.
setValueMerklizedRoot(r) {
this.resetIndexMerklizedRoot();
this.setFlagMerklized(MerklizedRootPosition.Value);
this.value[2] = this.setSlotInt(r, SlotName.ValueA);
}
resetValueMerklizedRoot() {
this._value[2] = new elemBytes_1.ElemBytes(new Uint8Array(constants_1.Constants.BYTES_LENGTH).fill(0));
}
// GetMerklizedRoot returns merklized root from claim's index of value.
// Returns error ErrNoMerklizedRoot if MerklizedRoot is not set.
getMerklizedRoot() {
switch (this.getMerklized()) {
case MerklizedFlag.Index:
return this.index[2].toBigInt();
case MerklizedFlag.Value:
return this.value[2].toBigInt();
default:
throw constants_1.Constants.ERRORS.NO_MERKLIZED_ROOT;
}
}
// resetId deletes Id from index and from value.
resetId() {
this.resetIndexId();
this.resetValueId();
this.setSubject(SubjectFlag.Self);
}
// GetId returns Id from claim's index of value.
// Returns error ErrNoId if Id is not set.
getId() {
switch (this.getSubject()) {
case SubjectFlag.OtherIdenIndex:
return this.getIndexId();
case SubjectFlag.OtherIdenValue:
return this.getValueId();
default:
throw constants_1.Constants.ERRORS.NO_ID;
}
}
// RawSlots returns raw bytes of claim's index and value
rawSlots() {
return {
index: this._index,
value: this._value
};
}
// RawSlotsAsInts returns slots as []bigint
rawSlotsAsInts() {
return [...elemBytes_1.ElemBytes.elemBytesToInts(this._index), ...elemBytes_1.ElemBytes.elemBytesToInts(this._value)];
}
clone() {
return JSON.parse(JSON.stringify(this));
}
marshalJson() {
return this.rawSlotsAsInts().map((b) => b.toString());
}
unMarshalJson(b) {
const ints = JSON.parse(b).map((s) => BigInt(s));
if (ints.length !== this._index.length + this._value.length) {
throw new Error("invalid number of claim's slots");
}
this._index = [];
this._value = [];
for (let i = 0, j = constants_1.Constants.ELEM_BYTES_LENGTH; i < ints.length / 2; i++, j++) {
this._index[i] = new elemBytes_1.ElemBytes();
this._index[i].setBigInt(ints[i]);
this._value[i] = new elemBytes_1.ElemBytes();
this._value[i].setBigInt(ints[j]);
}
return this;
}
marshalBinary() {
const getBytes = (src) => src.reduce((acc, cur) => {
return [...acc, ...cur.bytes];
}, []);
return Uint8Array.from(getBytes(this._index).concat(getBytes(this._value)));
}
// Hex returns hex representation of binary claim
hex() {
const b = this.marshalBinary();
return js_crypto_1.Hex.encodeString(b);
}
fromHex(hex) {
const b = js_crypto_1.Hex.decodeString(hex);
this.unMarshalBinary(b);
return this;
}
unMarshalBinary(data) {
const wantLen = 2 * constants_1.Constants.ELEM_BYTES_LENGTH * constants_1.Constants.BYTES_LENGTH;
if (data.length !== wantLen) {
throw new Error('unexpected length of input data');
}
this._index = [];
this._value = [];
for (let i = 0, j = constants_1.Constants.ELEM_BYTES_LENGTH; i < constants_1.Constants.ELEM_BYTES_LENGTH; i++, j++) {
this._index[i] = new elemBytes_1.ElemBytes(data.slice(i * constants_1.Constants.BYTES_LENGTH, (i + 1) * constants_1.Constants.BYTES_LENGTH));
this._value[i] = new elemBytes_1.ElemBytes(data.slice(j * constants_1.Constants.BYTES_LENGTH, (j + 1) * constants_1.Constants.BYTES_LENGTH));
}
}
}
exports.Claim = Claim;
class ClaimOptions {
// WithFlagUpdatable sets claim's flag `updatable`
static withFlagUpdatable(val) {
return (c) => c.setFlagUpdatable(val);
}
// WithVersion sets claim's version
static withVersion(ver) {
return (c) => c.setVersion(ver);
}
// WithIndexId sets Id to claim's index
static withIndexId(id) {
return (c) => c.setIndexId(id);
}
// WithValueId sets Id to claim's value
static withValueId(id) {
return (c) => c.setValueId(id);
}
// WithFlagMerklized sets claim's flag `merklized`
static withFlagMerklized(p) {
return (c) => c.setFlagMerklized(p);
}
// WithId sets Id to claim's index or value depending on `pos`.
static withId(id, pos) {
return (c) => {
switch (pos) {
case IdPosition.Index:
c.setIndexId(id);
break;
case IdPosition.Value:
c.setValueId(id);
break;
default:
throw constants_1.Constants.ERRORS.INCORRECT_ID_POSITION;
}
};
}
// WithRevocationNonce sets claim's revocation nonce.
static withRevocationNonce(nonce) {
return (c) => c.setRevocationNonce(nonce);
}
// WithExpirationDate sets claim's expiration date to `dt`.
static withExpirationDate(dt) {
return (c) => c.setExpirationDate(dt);
}
// WithIndexData sets data to index slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
static withIndexData(slotA, slotB) {
return (c) => c.setIndexData(slotA, slotB);
}
// WithIndexDataBytes sets data to index slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
static withIndexDataBytes(slotA, slotB) {
return (c) => c.setIndexDataBytes(slotA, slotB);
}
// WithIndexDataInts sets data to index slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
static withIndexDataInts(slotA, slotB) {
return (c) => c.setIndexDataInts(slotA, slotB);
}
// WithValueData sets data to value slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
static withValueData(slotA, slotB) {
return (c) => c.setValueData(slotA, slotB);
}
// WithValueDataBytes sets data to value slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
static withValueDataBytes(slotA, slotB) {
return (c) => c.setValueDataBytes(slotA, slotB);
}
// WithValueDataInts sets data to value slots A & B.
// Returns ErrSlotOverflow if slotA or slotB value are too big.
static withValueDataInts(slotA, slotB) {
return (c) => c.setValueDataInts(slotA, slotB);
}
// WithIndexMerklizedRoot sets root to index i_2
// Returns ErrSlotOverflow if root value are too big.
static withIndexMerklizedRoot(r) {
return (c) => {
c.setFlagMerklized(MerklizedRootPosition.Index);
c.index[2] = c.setSlotInt(r, SlotName.IndexA);
};
}
// WithValueMerklizedRoot sets root to value v_2
// Returns ErrSlotOverflow if root value are too big.
static withValueMerklizedRoot(r) {
return (c) => {
c.setFlagMerklized(MerklizedRootPosition.Value);
c.value[2] = c.setSlotInt(r, SlotName.ValueA);
};
}
// WithMerklizedRoot sets root to value v_2 or index i_2
// Returns ErrSlotOverflow if root value are too big.
static withMerklizedRoot(r, pos) {
return (c) => {
switch (pos) {
case MerklizedRootPosition.Index:
c.setFlagMerklized(MerklizedRootPosition.Index);
c.index[2] = c.setSlotInt(r, SlotName.IndexA);
break;
case MerklizedRootPosition.Value:
c.setFlagMerklized(MerklizedRootPosition.Value);
c.value[2] = c.setSlotInt(r, SlotName.ValueA);
break;
default:
throw constants_1.Constants.ERRORS.INCORRECT_MERKLIZED_POSITION;
}
};
}
}
exports.ClaimOptions = ClaimOptions;