mirror of
https://github.com/iden3/js-iden3-core.git
synced 2026-01-10 06:18:02 -05:00
make test pass
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
"semi": true,
|
||||
"trailingComma": "none",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
"printWidth": 100
|
||||
}
|
||||
|
||||
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Jest Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeArgs": ["--inspect-brk", "${workspaceRoot}/node_modules/.bin/jest", "--runInBand"],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"port": 9229
|
||||
}
|
||||
]
|
||||
}
|
||||
23
.vscode/settings.json
vendored
Normal file
23
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"cSpell.words": ["Iden"],
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.activeBackground": "#ab307e",
|
||||
"activityBar.background": "#ab307e",
|
||||
"activityBar.foreground": "#e7e7e7",
|
||||
"activityBar.inactiveForeground": "#e7e7e799",
|
||||
"activityBarBadge.background": "#25320e",
|
||||
"activityBarBadge.foreground": "#e7e7e7",
|
||||
"commandCenter.border": "#e7e7e799",
|
||||
"sash.hoverBorder": "#ab307e",
|
||||
"statusBar.background": "#832561",
|
||||
"statusBar.foreground": "#e7e7e7",
|
||||
"statusBarItem.hoverBackground": "#ab307e",
|
||||
"statusBarItem.remoteBackground": "#832561",
|
||||
"statusBarItem.remoteForeground": "#e7e7e7",
|
||||
"titleBar.activeBackground": "#832561",
|
||||
"titleBar.activeForeground": "#e7e7e7",
|
||||
"titleBar.inactiveBackground": "#83256199",
|
||||
"titleBar.inactiveForeground": "#e7e7e799"
|
||||
},
|
||||
"peacock.color": "#832561"
|
||||
}
|
||||
46
index.html
Normal file
46
index.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible"
|
||||
content="IE=edge">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<script type="module"
|
||||
src="./dist/esm/index.mjs"></script>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
hello
|
||||
</body>
|
||||
<script>
|
||||
// const claim = newClaim(new SchemaHash(), withFlagUpdatable(true));
|
||||
// const { index, value } = claim.rawSlots();
|
||||
// const indexHash = poseidonHash([
|
||||
// index[0].toBigInt(),
|
||||
// index[1].toBigInt(),
|
||||
// index[2].toBigInt(),
|
||||
// index[3].toBigInt()
|
||||
// ]).then((hash) => {
|
||||
// console.log(hash);
|
||||
// console.assert('19905260441950906049955646784794273651462264973332746773406911374272567544299' == hash.toString());
|
||||
// });
|
||||
// const valueHash = await poseidonHash([
|
||||
// value[0].toBigInt(),
|
||||
// value[1].toBigInt(),
|
||||
// value[2].toBigInt(),
|
||||
// value[3].toBigInt()
|
||||
// ]);
|
||||
|
||||
// expect().toEqual(
|
||||
// indexHash.toString()
|
||||
// );
|
||||
|
||||
// expect('2351654555892372227640888372176282444150254868378439619268573230312091195718').toEqual(
|
||||
// valueHash.toString()
|
||||
// );
|
||||
</script>
|
||||
|
||||
</html>
|
||||
1795
package-lock.json
generated
1795
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@js-iden3-core",
|
||||
"name": "@iden3/js-iden3-core",
|
||||
"version": "0.0.0-development",
|
||||
"description": "Low level API to create and manipulate iden3 Claims.",
|
||||
"exports": {
|
||||
@@ -20,11 +20,12 @@
|
||||
"dist/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist",
|
||||
"clean": "rimraf ./dist",
|
||||
"build": "npm run clean && npm run build:esm && npm run build:cjs",
|
||||
"build:esm": "tsc -p ./configs/tsconfig.esm.json && mv dist/esm/index.js dist/esm/index.mjs",
|
||||
"build:cjs": "tsc -p ./configs/tsconfig.cjs.json",
|
||||
"test": "jest --coverage",
|
||||
"test:coverage": "jest --coverage",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"semantic-release": "semantic-release",
|
||||
"lint": "eslint --fix --ext .js,.ts src/**",
|
||||
@@ -40,27 +41,18 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/snyk-labs/modern-npm-package.git"
|
||||
"url": "https://github.com/iden3/js-iden3-core.git"
|
||||
},
|
||||
"keywords": [
|
||||
"npm",
|
||||
"javascript",
|
||||
"typescript",
|
||||
"esm",
|
||||
"cjs",
|
||||
"nodejs",
|
||||
"commonjs",
|
||||
"ecmascript",
|
||||
"beginner",
|
||||
"example",
|
||||
"demonstration"
|
||||
"iden3",
|
||||
"core"
|
||||
],
|
||||
"author": "Snyk Labs",
|
||||
"author": "D.Kolezhniuk",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/snyk-labs/modern-npm-package/issues"
|
||||
"url": "https://github.com/iden3/js-iden3-core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/snyk-labs/modern-npm-package#readme",
|
||||
"homepage": "https://github.com/iden3/js-iden3-core#readme",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
@@ -68,9 +60,17 @@
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"jest": "^29.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^3.2.3",
|
||||
"semantic-release": "^19.0.3",
|
||||
"terser": "^5.15.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"base58-js": "^1.0.4",
|
||||
"circomlibjs": "^0.1.7",
|
||||
"cross-sha256": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
522
src/claim.ts
Normal file
522
src/claim.ts
Normal file
@@ -0,0 +1,522 @@
|
||||
import { SchemaHash } from './schemaHash';
|
||||
import { ElemBytes } from './elemBytes';
|
||||
import { Constants } from './constants';
|
||||
import { Id } from './id';
|
||||
import {
|
||||
checkBigIntArrayInField,
|
||||
checkBigIntInField,
|
||||
getDateFromUnixTimestamp,
|
||||
getUint32,
|
||||
getUint64,
|
||||
getUnixTimestamp,
|
||||
poseidonHash,
|
||||
putUint32,
|
||||
putUint64 as getBytesFromUint64
|
||||
} from './util';
|
||||
|
||||
/*
|
||||
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
|
||||
[27] 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
|
||||
*/
|
||||
|
||||
// Option provides the ability to set different Claim's fields on construction
|
||||
export type Option = (c: Claim) => void;
|
||||
|
||||
// WithFlagUpdatable sets claim's flag `updatable`
|
||||
export function withFlagUpdatable(val: boolean): Option {
|
||||
return (c: Claim) => c.setFlagUpdatable(val);
|
||||
}
|
||||
|
||||
// WithVersion sets claim's version
|
||||
export function withVersion(ver: number): Option {
|
||||
return (c: Claim) => c.setVersion(ver);
|
||||
}
|
||||
|
||||
// WithIndexId sets Id to claim's index
|
||||
export function withIndexId(id: Id): Option {
|
||||
return (c: Claim) => c.setIndexId(id);
|
||||
}
|
||||
|
||||
// WithValueId sets Id to claim's value
|
||||
export function withValueId(id: Id): Option {
|
||||
return (c: Claim) => c.setValueId(id);
|
||||
}
|
||||
|
||||
// WithId sets Id to claim's index or value depending on `pos`.
|
||||
export function withId(id: Id, pos: IdPosition): Option {
|
||||
return (c: Claim) => {
|
||||
switch (pos) {
|
||||
case IdPosition.Index:
|
||||
c.setIndexId(id);
|
||||
break;
|
||||
case IdPosition.Value:
|
||||
c.setValueId(id);
|
||||
break;
|
||||
default:
|
||||
throw new Error(Constants.ERRORS.INCORRECT_ID_POSITION);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// WithRevocationNonce sets claim's revocation nonce.
|
||||
export function withRevocationNonce(nonce: number): Option {
|
||||
return (c: Claim) => c.setRevocationNonce(nonce);
|
||||
}
|
||||
|
||||
// WithExpirationDate sets claim's expiration date to `dt`.
|
||||
export function withExpirationDate(dt: Date): Option {
|
||||
return (c: Claim) => c.setExpirationDate(dt);
|
||||
}
|
||||
|
||||
// WithIndexData sets data to index slots A & B.
|
||||
// Returns ErrSlotOverflow if slotA or slotB value are too big.
|
||||
export function withIndexData(slotA: ElemBytes, slotB: ElemBytes): Option {
|
||||
return (c: Claim) => c.setIndexData(slotA, slotB);
|
||||
}
|
||||
|
||||
// WithIndexDataBytes sets data to index slots A & B.
|
||||
// Returns ErrSlotOverflow if slotA or slotB value are too big.
|
||||
export function withIndexDataBytes(slotA: Uint8Array | null, slotB: Uint8Array | null): Option {
|
||||
return (c: Claim) => c.setIndexDataBytes(slotA, slotB);
|
||||
}
|
||||
|
||||
// WithIndexDataInts sets data to index slots A & B.
|
||||
// Returns ErrSlotOverflow if slotA or slotB value are too big.
|
||||
export function withIndexDataInts(slotA: bigint | null, slotB: bigint | null): Option {
|
||||
return (c: Claim) => c.setIndexDataInts(slotA, slotB);
|
||||
}
|
||||
|
||||
// WithValueData sets data to value slots A & B.
|
||||
// Returns ErrSlotOverflow if slotA or slotB value are too big.
|
||||
export function withValueData(slotA: ElemBytes, slotB: ElemBytes): Option {
|
||||
return (c: Claim) => c.setValueData(slotA, slotB);
|
||||
}
|
||||
|
||||
// WithValueDataBytes sets data to value slots A & B.
|
||||
// Returns ErrSlotOverflow if slotA or slotB value are too big.
|
||||
export function withValueDataBytes(slotA: Uint8Array, slotB: Uint8Array): Option {
|
||||
return (c: Claim) => c.setValueDataBytes(slotA, slotB);
|
||||
}
|
||||
|
||||
// WithValueDataInts sets data to value slots A & B.
|
||||
// Returns ErrSlotOverflow if slotA or slotB value are too big.
|
||||
export function withValueDataInts(slotA: bigint | null, slotB: bigint | null): Option {
|
||||
return (c: Claim) => c.setValueDataInts(slotA, slotB);
|
||||
}
|
||||
|
||||
// NewClaim creates new Claim with specified SchemaHash and any number of
|
||||
// options. Using options you can specify any field in claim.
|
||||
export function newClaim(sh: SchemaHash, ...args: Option[]): Claim {
|
||||
const c = new Claim();
|
||||
c.setSchemaHash(sh);
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const fn = args[i];
|
||||
fn(c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
export enum SlotName {
|
||||
IndexA = 'IndexA',
|
||||
IndexB = 'IndexB',
|
||||
ValueA = 'ValueA',
|
||||
ValueB = 'ValueB'
|
||||
}
|
||||
|
||||
// ErrSlotOverflow means some ElemBytes overflows Q Field. And wraps the name
|
||||
// of overflowed slot.
|
||||
export class ErrSlotOverflow extends Error {
|
||||
constructor(msg: string) {
|
||||
super(`Slot ${msg} not in field (too large)`);
|
||||
Object.setPrototypeOf(this, ErrSlotOverflow.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
export enum SubjectFlag {
|
||||
Self = 0b0,
|
||||
Invalid = 0b1,
|
||||
OtherIdenIndex = 0b10,
|
||||
OtherIdenValue = 0b11
|
||||
}
|
||||
|
||||
export enum IdPosition {
|
||||
None = 0,
|
||||
Index = 1,
|
||||
Value = 2
|
||||
}
|
||||
|
||||
export enum Flags {
|
||||
ByteIdx = 16,
|
||||
ExpirationBitIdx = 3,
|
||||
UpdatableBitIdx = 4
|
||||
}
|
||||
|
||||
export class Claim {
|
||||
private _index: ElemBytes[] = [];
|
||||
private _value: ElemBytes[] = [];
|
||||
|
||||
constructor() {
|
||||
for (let i = 0; i < Constants.ELEM_BYTES_LENGTH; i++) {
|
||||
this._index[i] = new ElemBytes();
|
||||
this._value[i] = new ElemBytes();
|
||||
}
|
||||
}
|
||||
|
||||
// GetSchemaHash return copy of claim's schema hash.
|
||||
getSchemaHash(): SchemaHash {
|
||||
return new SchemaHash(this._index[0].bytes.slice(0, Constants.SCHEMA.HASH_LENGTH));
|
||||
}
|
||||
|
||||
get value(): ElemBytes[] {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value: ElemBytes[]) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
get index(): ElemBytes[] {
|
||||
return this._index;
|
||||
}
|
||||
|
||||
set index(value: ElemBytes[]) {
|
||||
this._index = value;
|
||||
}
|
||||
|
||||
// SetSchemaHash updates claim's schema hash.
|
||||
setSchemaHash(sh: SchemaHash) {
|
||||
this._index[0] = new ElemBytes(
|
||||
Uint8Array.from([...sh.bytes, ...new Array(Constants.SCHEMA.HASH_LENGTH).fill(0)])
|
||||
);
|
||||
}
|
||||
|
||||
setSubject(s: SubjectFlag) {
|
||||
// clean first 3 bits
|
||||
this._index[0].bytes[Flags.ByteIdx] &= 0b11111000;
|
||||
this._index[0].bytes[Flags.ByteIdx] |= s;
|
||||
}
|
||||
|
||||
private getSubject(): SubjectFlag {
|
||||
let sbj = this._index[0].bytes[Flags.ByteIdx];
|
||||
// clean all except first 3 bits
|
||||
sbj &= 0b00000111;
|
||||
return sbj as SubjectFlag;
|
||||
}
|
||||
|
||||
private setFlagExpiration(val: boolean) {
|
||||
if (val) {
|
||||
this._index[0].bytes[Flags.ByteIdx] |= 0b1 << Flags.ExpirationBitIdx;
|
||||
} else {
|
||||
this._index[0].bytes[Flags.ByteIdx] &= ~(0b1 << Flags.ExpirationBitIdx);
|
||||
}
|
||||
}
|
||||
|
||||
private getFlagExpiration(): boolean {
|
||||
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(): IdPosition {
|
||||
switch (this.getSubject()) {
|
||||
case SubjectFlag.Self:
|
||||
return IdPosition.None;
|
||||
case SubjectFlag.OtherIdenIndex:
|
||||
return IdPosition.Index;
|
||||
case SubjectFlag.OtherIdenValue:
|
||||
return IdPosition.Value;
|
||||
default:
|
||||
throw new Error(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: bigint | null, slotB: bigint | null): void {
|
||||
this.setSlotInt(this._value[2], slotA, SlotName.ValueA);
|
||||
this.setSlotInt(this._value[3], slotB, SlotName.ValueB);
|
||||
}
|
||||
// SetValueDataBytes sets data to value slots A & B.
|
||||
// Returns ErrSlotOverflow if slotA or slotB value are too big.
|
||||
setValueDataBytes(slotA: Uint8Array, slotB: Uint8Array): void {
|
||||
this.setSlotBytes(this._value[2], slotA, SlotName.ValueA);
|
||||
this.setSlotBytes(this._value[3], slotB, SlotName.ValueB);
|
||||
}
|
||||
// SetValueData sets data to value slots A & B.
|
||||
// Returns ErrSlotOverflow if slotA or slotB value are too big.
|
||||
setValueData(slotA: ElemBytes, slotB: ElemBytes): void {
|
||||
const slotsAsInts: bigint[] = [slotA.toBigInt(), slotB.toBigInt()];
|
||||
if (!checkBigIntArrayInField(slotsAsInts)) {
|
||||
throw new Error(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: bigint | null, slotB: bigint | null): void {
|
||||
this.setSlotInt(this._index[2], slotA, SlotName.IndexA);
|
||||
this.setSlotInt(this._index[3], slotB, SlotName.IndexB);
|
||||
}
|
||||
// SetIndexDataBytes sets data to index slots A & B.
|
||||
// Returns ErrSlotOverflow if slotA or slotB value are too big.
|
||||
setIndexDataBytes(slotA: Uint8Array | null, slotB: Uint8Array | null): void {
|
||||
this.setSlotBytes(this._index[2], slotA, SlotName.IndexA);
|
||||
this.setSlotBytes(this._index[3], slotB, SlotName.IndexB);
|
||||
}
|
||||
private setSlotBytes(slot: ElemBytes, value: Uint8Array | null, slotName: SlotName) {
|
||||
slot = new ElemBytes(value);
|
||||
if (!checkBigIntInField(slot.toBigInt())) {
|
||||
throw new ErrSlotOverflow(slotName);
|
||||
}
|
||||
}
|
||||
|
||||
private setSlotInt(slot: ElemBytes, value: bigint | null, slotName: SlotName) {
|
||||
if (!value) {
|
||||
value = BigInt(0);
|
||||
}
|
||||
if (!checkBigIntInField(value)) {
|
||||
throw new ErrSlotOverflow(slotName);
|
||||
}
|
||||
slot.setBigInt(value);
|
||||
}
|
||||
// SetIndexData sets data to index slots A & B.
|
||||
// Returns ErrSlotOverflow if slotA or slotB value are too big.
|
||||
setIndexData(slotA: ElemBytes, slotB: ElemBytes) {
|
||||
const slotsAsInts: bigint[] = [slotA.toBigInt(), slotB.toBigInt()];
|
||||
if (!checkBigIntArrayInField(slotsAsInts)) {
|
||||
throw new Error(Constants.ERRORS.DATA_OVERFLOW);
|
||||
}
|
||||
this._index[2] = slotA;
|
||||
this._index[3] = slotB;
|
||||
}
|
||||
|
||||
resetExpirationDate(): void {
|
||||
this.setFlagExpiration(false);
|
||||
const bytes = Array.from({ length: Constants.NONCE_BYTES_LENGTH }, () => 0);
|
||||
const arr = Array.from(this._value[0].bytes);
|
||||
arr.splice(Constants.NONCE_BYTES_LENGTH, Constants.NONCE_BYTES_LENGTH, ...bytes);
|
||||
this._value[0] = new ElemBytes(Uint8Array.from(arr));
|
||||
}
|
||||
|
||||
// GetExpirationDate returns expiration date and flag. Flag is true if
|
||||
// expiration date is present, false if null.
|
||||
getExpirationDate(): Date | null {
|
||||
if (this.getFlagExpiration()) {
|
||||
const unixTimestamp = getUint64(this._value[0].bytes.slice(8, 16));
|
||||
return getDateFromUnixTimestamp(unixTimestamp);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// SetExpirationDate sets expiration date to dt
|
||||
setExpirationDate(dt: Date) {
|
||||
this.setFlagExpiration(true);
|
||||
const bytes = getBytesFromUint64(getUnixTimestamp(dt));
|
||||
const arr = Array.from(this._value[0].bytes);
|
||||
arr.splice(Constants.NONCE_BYTES_LENGTH, Constants.NONCE_BYTES_LENGTH, ...bytes);
|
||||
this._value[0] = new ElemBytes(Uint8Array.from(arr));
|
||||
}
|
||||
|
||||
// GetRevocationNonce returns revocation nonce
|
||||
getRevocationNonce(): number {
|
||||
return getUint64(this._value[0].bytes.slice(0, 8));
|
||||
}
|
||||
// SetRevocationNonce sets claim's revocation nonce
|
||||
setRevocationNonce(nonce: number): void {
|
||||
const bytes = getBytesFromUint64(nonce);
|
||||
if (bytes.length > Constants.NONCE_BYTES_LENGTH) {
|
||||
throw new Error('Nonce length is not valid');
|
||||
}
|
||||
const arr = Array.from(this._value[0].bytes);
|
||||
arr.splice(0, Constants.NONCE_BYTES_LENGTH, ...bytes);
|
||||
this._value[0] = new ElemBytes(Uint8Array.from(arr));
|
||||
}
|
||||
|
||||
getValueId(): Id {
|
||||
return Id.fromBytes(this._value[1].bytes);
|
||||
}
|
||||
|
||||
// SetValueId sets id to value. Removes id from index if any.
|
||||
setValueId(id: Id): void {
|
||||
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(Uint8Array.from(arr));
|
||||
}
|
||||
|
||||
private resetIndexId() {
|
||||
this._index[1] = new ElemBytes(new Uint8Array(Constants.BYTES_LENGTH).fill(0));
|
||||
}
|
||||
|
||||
private resetValueId(): void {
|
||||
this._value[1] = new ElemBytes(new Uint8Array(Constants.BYTES_LENGTH).fill(0));
|
||||
}
|
||||
|
||||
getIndexId(): Id {
|
||||
return Id.fromBytes(this._index[1].bytes);
|
||||
}
|
||||
|
||||
// SetIndexId sets id to index. Removes id from value if any.
|
||||
setIndexId(id: Id): void {
|
||||
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(Uint8Array.from(arr));
|
||||
}
|
||||
// SetVersion sets claim's version
|
||||
setVersion(ver: number) {
|
||||
const bytes = 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(): number {
|
||||
return getUint32(this._index[0].bytes.slice(20, 24));
|
||||
}
|
||||
// SetFlagUpdatable sets claim's flag `updatable`
|
||||
setFlagUpdatable(val: boolean) {
|
||||
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
|
||||
async hIndex(): Promise<bigint> {
|
||||
return await poseidonHash(ElemBytes.elemBytesToInts(this._index));
|
||||
}
|
||||
|
||||
// GetFlagUpdatable returns claim's flag `updatable`
|
||||
getFlagUpdatable(): boolean {
|
||||
const mask = 0b1 << Flags.UpdatableBitIdx;
|
||||
return (this._index[0].bytes[Flags.ByteIdx] & mask) > 0;
|
||||
}
|
||||
|
||||
// HValue calculates the hash of the Value of the Claim
|
||||
async hValue(): Promise<bigint> {
|
||||
return await poseidonHash(ElemBytes.elemBytesToInts(this._value));
|
||||
}
|
||||
|
||||
// HiHv returns the HIndex and HValue of the Claim
|
||||
async hiHv(): Promise<{ hi: bigint; hv: bigint }> {
|
||||
return { hi: await this.hIndex(), hv: await this.hValue() };
|
||||
}
|
||||
|
||||
// resetId deletes Id from index and from value.
|
||||
resetId(): void {
|
||||
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(): Id {
|
||||
switch (this.getSubject()) {
|
||||
case SubjectFlag.OtherIdenIndex:
|
||||
return this.getIndexId();
|
||||
case SubjectFlag.OtherIdenValue:
|
||||
return this.getValueId();
|
||||
default:
|
||||
throw new Error(Constants.ERRORS.NO_ID);
|
||||
}
|
||||
}
|
||||
// RawSlots returns raw bytes of claim's index and value
|
||||
rawSlots(): { index: ElemBytes[]; value: ElemBytes[] } {
|
||||
return {
|
||||
index: this._index,
|
||||
value: this._value
|
||||
};
|
||||
}
|
||||
// RawSlotsAsInts returns slots as []bigint
|
||||
rawSlotsAsInts(): bigint[] {
|
||||
return [...ElemBytes.elemBytesToInts(this._index), ...ElemBytes.elemBytesToInts(this._value)];
|
||||
}
|
||||
|
||||
clone(): Claim {
|
||||
return JSON.parse(JSON.stringify(this));
|
||||
}
|
||||
|
||||
marshalJson(): string {
|
||||
return JSON.stringify(this.rawSlotsAsInts().map((b) => b.toString()));
|
||||
}
|
||||
|
||||
unMarshalJson(b: string): Claim {
|
||||
const ints: bigint[] = JSON.parse(b).map((s: string) => 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.ELEM_BYTES_LENGTH; i < ints.length / 2; i++, j++) {
|
||||
this._index[i] = new ElemBytes();
|
||||
this._index[i].setBigInt(ints[i]);
|
||||
this._value[i] = new ElemBytes();
|
||||
this._value[i].setBigInt(ints[j]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
marshalBinary(): Uint8Array {
|
||||
const getBytes = (src: ElemBytes[]) =>
|
||||
src.reduce((acc: number[], cur: ElemBytes) => {
|
||||
return [...acc, ...cur.bytes];
|
||||
}, []);
|
||||
return Uint8Array.from(getBytes(this._index).concat(getBytes(this._value)));
|
||||
}
|
||||
|
||||
unMarshalBinary(data: Uint8Array): void {
|
||||
const wantLen = 2 * Constants.ELEM_BYTES_LENGTH * 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.ELEM_BYTES_LENGTH; i < Constants.ELEM_BYTES_LENGTH; i++, j++) {
|
||||
this._index[i] = new ElemBytes(
|
||||
data.slice(i * Constants.BYTES_LENGTH, (i + 1) * Constants.BYTES_LENGTH)
|
||||
);
|
||||
this._value[i] = new ElemBytes(
|
||||
data.slice(j * Constants.BYTES_LENGTH, (j + 1) * Constants.BYTES_LENGTH)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/constants.ts
Normal file
31
src/constants.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export const Constants = Object.freeze({
|
||||
ERRORS: {
|
||||
// ErrDataOverflow means that given *big.Int value does not fit in Field Q
|
||||
// e.g. greater than Q constant:
|
||||
// Q constant: 21888242871839275222246405745257275088548364400416034343698204186575808495617
|
||||
DATA_OVERFLOW: 'data does not fits SNARK size',
|
||||
// ErrIncorrectIDPosition means that passed position is not one of predefined:
|
||||
// IDPositionIndex or IDPositionValue
|
||||
INCORRECT_ID_POSITION: 'incorrect ID position',
|
||||
// throws when ID not found in the Claim.
|
||||
NO_ID: 'ID is not set',
|
||||
// throws when subject position flags sets in invalid value.
|
||||
INVALID_SUBJECT_POSITION: 'invalid subject position'
|
||||
},
|
||||
SCHEMA: {
|
||||
HASH_LENGTH: 16
|
||||
},
|
||||
BYTES_LENGTH: 32,
|
||||
ELEM_BYTES_LENGTH: 4,
|
||||
NONCE_BYTES_LENGTH: 8,
|
||||
Q: BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617'),
|
||||
ID: {
|
||||
TYPE_DEFAULT: Uint8Array.from([0x00, 0x00]),
|
||||
TYPE_READONLY: Uint8Array.from([0b00000000, 0b00000001]),
|
||||
ID_LENGTH: 31
|
||||
},
|
||||
DID: {
|
||||
DID_SCHEMA: 'did',
|
||||
DID_METHOD_IDEN3: 'iden3'
|
||||
}
|
||||
});
|
||||
230
src/did.ts
Normal file
230
src/did.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
import { Id } from './id';
|
||||
import { Constants } from './constants';
|
||||
|
||||
export enum Blockchain {
|
||||
Ethereum = 'eth',
|
||||
Polygon = 'polygon',
|
||||
Unknown = 'unknown',
|
||||
NoChain = ''
|
||||
}
|
||||
|
||||
export enum NetworkId {
|
||||
Main = 'main',
|
||||
Mumbai = 'mumbai',
|
||||
Goerli = 'goerli',
|
||||
Unknown = 'unknown',
|
||||
NoNetwork = ''
|
||||
}
|
||||
|
||||
export enum DidMethod {
|
||||
Iden3 = 'iden3'
|
||||
}
|
||||
export const DIDMethodByte: { [key: string]: number } = {
|
||||
[DidMethod.Iden3]: 0b00000001
|
||||
};
|
||||
|
||||
// DIDNetworkFlag is a structure to represent DID blockchain and network id
|
||||
export class DIDNetworkFlag {
|
||||
constructor(public readonly blockchain: Blockchain, public readonly networkId: NetworkId) {}
|
||||
|
||||
toString(): string {
|
||||
return `${this.blockchain || '_'}:${this.networkId || '_'}`;
|
||||
}
|
||||
|
||||
static fromString(s: string): DIDNetworkFlag {
|
||||
const [blockchain, networkId] = s.split(':');
|
||||
return new DIDNetworkFlag(
|
||||
blockchain.replace('_', '') as Blockchain,
|
||||
networkId.replace('_', '') as NetworkId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DIDMethodNetwork is map for did methods and their blockchain networks
|
||||
export const DIDMethodNetwork: {
|
||||
[k: string]: { [k: string]: number };
|
||||
} = {
|
||||
[DidMethod.Iden3]: {
|
||||
'_:_': 0b00000000,
|
||||
'polygon:main': 0b00010000 | 0b00000001,
|
||||
'polygon:mumbai': 0b00010000 | 0b00000010,
|
||||
'ethereum:main': 0b00100000 | 0b00000001,
|
||||
'ethereum:goerli': 0b00100000 | 0b00000010
|
||||
}
|
||||
};
|
||||
|
||||
// BuildDIDType builds bytes type from chain and network
|
||||
export function buildDIDType(
|
||||
method: string,
|
||||
blockchain: Blockchain,
|
||||
network: NetworkId
|
||||
): Uint8Array {
|
||||
const fb = DIDMethodByte[method];
|
||||
if (!fb) {
|
||||
throw new Error(`method ${method} is not defined in core lib`);
|
||||
}
|
||||
const methodFn = DIDMethodNetwork[method];
|
||||
if (!methodFn) {
|
||||
throw new Error(`method ${method} is not defined in core lib`);
|
||||
}
|
||||
const sb: number | undefined = methodFn[new DIDNetworkFlag(blockchain, network).toString()];
|
||||
if (!sb) {
|
||||
throw new Error(`blockchain ${blockchain} and network ${network} is not defined in core lib`);
|
||||
}
|
||||
return Uint8Array.from([fb, sb]);
|
||||
}
|
||||
|
||||
// FindNetworkIDForDIDMethodByValue finds network by byte value
|
||||
export function findNetworkIDForDIDMethodByValue(method: string, byteNumber: number): NetworkId {
|
||||
const methodMap = DIDMethodNetwork[method];
|
||||
if (!methodMap) {
|
||||
throw new Error(`did method ${method} is not defined in core lib`);
|
||||
}
|
||||
for (const [key, value] of Object.entries(methodMap)) {
|
||||
if (value === byteNumber) {
|
||||
return DIDNetworkFlag.fromString(key).networkId;
|
||||
}
|
||||
}
|
||||
return NetworkId.Unknown;
|
||||
}
|
||||
|
||||
// findBlockchainForDIDMethodByValue finds blockchain type by byte value
|
||||
export function findBlockchainForDIDMethodByValue(
|
||||
method: DidMethod,
|
||||
byteNumber: number
|
||||
): Blockchain {
|
||||
const methodMap = DIDMethodNetwork[method];
|
||||
if (!methodMap) {
|
||||
throw new Error(`did method ${method} is not defined in core lib`);
|
||||
}
|
||||
for (const [key, value] of Object.entries(methodMap)) {
|
||||
if (value === byteNumber) {
|
||||
return DIDNetworkFlag.fromString(key).blockchain;
|
||||
}
|
||||
}
|
||||
return Blockchain.Unknown;
|
||||
}
|
||||
|
||||
// findDIDMethodByValue finds did method by its byte value
|
||||
export function findDIDMethodByValue(byteNumber: number): DidMethod {
|
||||
for (const [key, value] of Object.entries(DIDMethodByte)) {
|
||||
if (value === byteNumber) {
|
||||
return key as DidMethod;
|
||||
}
|
||||
}
|
||||
throw new Error(`bytes ${byteNumber} are not defined in core lib as valid did method`);
|
||||
}
|
||||
|
||||
export type DIDOptions = (did: DID) => void;
|
||||
// WithNetwork sets Blockchain and NetworkID (eth:main)
|
||||
export function withNetwork(blockchain: Blockchain, network: NetworkId): DIDOptions {
|
||||
return (did: DID) => {
|
||||
did.networkId = network;
|
||||
did.blockchain = blockchain;
|
||||
};
|
||||
}
|
||||
|
||||
// DID Decentralized Identifiers (DIDs)
|
||||
// https://w3c.github.io/did-core/#did-syntax
|
||||
export class DID {
|
||||
public method: DidMethod = DidMethod.Iden3;
|
||||
public id: Id = new Id(new Uint8Array(2), new Uint8Array(27));
|
||||
public blockchain: Blockchain = Blockchain.Unknown;
|
||||
public networkId: NetworkId = NetworkId.Unknown;
|
||||
|
||||
// toString did as a string
|
||||
toString(): string {
|
||||
if (this.blockchain == '') {
|
||||
return [Constants.DID.DID_SCHEMA, DidMethod.Iden3, this.id.string()].join(':');
|
||||
}
|
||||
return [
|
||||
Constants.DID.DID_SCHEMA,
|
||||
DidMethod.Iden3,
|
||||
this.blockchain,
|
||||
this.networkId,
|
||||
this.id.string()
|
||||
]
|
||||
.filter((i) => !!i)
|
||||
.join(':');
|
||||
}
|
||||
// ParseDIDFromID returns did from ID
|
||||
static parseFromId(id: Id): DID {
|
||||
const did = new DID();
|
||||
did.id = id;
|
||||
const typ = id.type();
|
||||
did.method = findDIDMethodByValue(typ[0]);
|
||||
did.blockchain = findBlockchainForDIDMethodByValue(did.method, typ[1]);
|
||||
did.networkId = findNetworkIDForDIDMethodByValue(did.method, typ[1]);
|
||||
return did;
|
||||
}
|
||||
|
||||
// ParseDID method parse string and extract DID if string is valid Iden3 identifier
|
||||
static parse(s: string): DID {
|
||||
const args = s.split(':');
|
||||
const did = new DID();
|
||||
|
||||
did.method = args[1] as DidMethod;
|
||||
|
||||
switch (args.length) {
|
||||
case 5:
|
||||
// validate id
|
||||
did.id = Id.fromString(args[4]);
|
||||
did.blockchain = args[2] as Blockchain;
|
||||
did.networkId = args[3] as NetworkId;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// validate readonly id
|
||||
did.id = Id.fromString(args[2]);
|
||||
did.blockchain = Blockchain.NoChain;
|
||||
did.networkId = NetworkId.NoNetwork;
|
||||
break;
|
||||
}
|
||||
|
||||
// check did method defined in core lib
|
||||
const methodByte = DIDMethodByte[did.method];
|
||||
if (!methodByte) {
|
||||
throw new Error(`DIDMethodByte: did method ${did.method} is not defined in core lib`);
|
||||
}
|
||||
|
||||
// check did network defined in core lib for did method
|
||||
const method = DIDMethodNetwork[did.method];
|
||||
if (!method) {
|
||||
throw new Error(`DIDMethodNetwork: did method ${did.method} is not defined in core lib`);
|
||||
}
|
||||
const byte: number = method[new DIDNetworkFlag(did.blockchain, did.networkId).toString()];
|
||||
|
||||
if (!byte?.toString()) {
|
||||
throw new Error(
|
||||
`blockchain network "${did.blockchain} ${did.networkId}" is not defined for ${did.method} did method`
|
||||
);
|
||||
}
|
||||
|
||||
// check id contains did network and method
|
||||
const d = DID.parseFromId(did.id);
|
||||
|
||||
if (d.method !== did.method) {
|
||||
throw new Error(
|
||||
`did method of core identity ${did.method} differs from given did method ${did.method}`
|
||||
);
|
||||
}
|
||||
if (d.networkId !== did.networkId) {
|
||||
throw new Error(
|
||||
`network method of core identity ${d.networkId} differs from given did network specific id ${did.networkId}`
|
||||
);
|
||||
}
|
||||
if (d.blockchain !== did.blockchain) {
|
||||
throw new Error(
|
||||
`blockchain network of core identity ${d.blockchain} differs from given did blockchain network ${did.blockchain}`
|
||||
);
|
||||
}
|
||||
return did;
|
||||
}
|
||||
|
||||
static newDID(didStr: string, ...args: DIDOptions[]): DID {
|
||||
const did = new DID();
|
||||
did.id = Id.fromString(didStr);
|
||||
args.filter((opt) => !!opt).forEach((arg) => arg(did));
|
||||
return did;
|
||||
}
|
||||
}
|
||||
126
src/elemBytes.ts
Normal file
126
src/elemBytes.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { Constants } from './constants';
|
||||
import { sha256 } from 'cross-sha256';
|
||||
import { checkBigIntInField, fromLittleEndian, toLittleEndian } from './util';
|
||||
import { Hex } from './hex';
|
||||
export class BytesHelper {
|
||||
static intToBytes(int: bigint): Uint8Array {
|
||||
return BytesHelper.intToNBytes(int, Constants.BYTES_LENGTH);
|
||||
}
|
||||
|
||||
static intToNBytes(int: bigint, n: number): Uint8Array {
|
||||
return Uint8Array.from(toLittleEndian(int, n));
|
||||
}
|
||||
|
||||
static checkChecksum(bytes: Uint8Array): boolean {
|
||||
const { typ, genesis, checksum } = BytesHelper.decomposeBytes(bytes);
|
||||
if (!checksum.length || JSON.stringify(Uint8Array.from([0, 0])) === JSON.stringify(checksum)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const c = BytesHelper.calculateChecksum(typ, genesis);
|
||||
return JSON.stringify(c) === JSON.stringify(checksum);
|
||||
}
|
||||
|
||||
static decomposeBytes(b: Uint8Array): {
|
||||
typ: Uint8Array;
|
||||
genesis: Uint8Array;
|
||||
checksum: Uint8Array;
|
||||
} {
|
||||
const offset = 2;
|
||||
const len = b.length - offset;
|
||||
return {
|
||||
typ: b.slice(0, offset),
|
||||
genesis: b.slice(offset, len),
|
||||
checksum: b.slice(-offset)
|
||||
};
|
||||
}
|
||||
|
||||
static calculateChecksum(typ: Uint8Array, genesis: Uint8Array): Uint8Array {
|
||||
const toChecksum = [...typ, ...genesis];
|
||||
const s: number = toChecksum.reduce((acc, cur) => acc + cur, 0);
|
||||
const checksum = [s >> 8, s & 0xff];
|
||||
return Uint8Array.from(checksum.reverse());
|
||||
}
|
||||
|
||||
static hashBytes(str: string): Uint8Array {
|
||||
const hash = new sha256().update(str).digest();
|
||||
return new Uint8Array(hash);
|
||||
}
|
||||
|
||||
static hexToBytes(str: string): Uint8Array {
|
||||
const buffer = Buffer.from(str, 'hex');
|
||||
return Uint8Array.from(buffer);
|
||||
}
|
||||
|
||||
static bytesToHex(bytes: Uint8Array) {
|
||||
const hex: string[] = [];
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
const current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];
|
||||
hex.push((current >>> 4).toString(16));
|
||||
hex.push((current & 0xf).toString(16));
|
||||
}
|
||||
return hex.join('');
|
||||
}
|
||||
|
||||
static bytesToInt(bytes: Uint8Array): bigint {
|
||||
return fromLittleEndian(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
export class ElemBytes {
|
||||
private _bytes = new Uint8Array(Constants.BYTES_LENGTH);
|
||||
|
||||
constructor(bytes?: Uint8Array | null) {
|
||||
if (bytes) {
|
||||
this._bytes = bytes;
|
||||
}
|
||||
if (this._bytes.length !== Constants.BYTES_LENGTH) {
|
||||
throw new Error('Invalid bytes length');
|
||||
}
|
||||
}
|
||||
|
||||
get bytes(): Uint8Array {
|
||||
return this._bytes;
|
||||
}
|
||||
|
||||
set bytes(value: Uint8Array) {
|
||||
this._bytes = value;
|
||||
}
|
||||
|
||||
toBigInt(): bigint {
|
||||
return BytesHelper.bytesToInt(this._bytes);
|
||||
}
|
||||
|
||||
setBigInt(n: bigint): ElemBytes {
|
||||
if (!checkBigIntInField(n)) {
|
||||
throw new Error(Constants.ERRORS.DATA_OVERFLOW);
|
||||
}
|
||||
this._bytes = BytesHelper.intToBytes(n);
|
||||
return this;
|
||||
}
|
||||
|
||||
slotFromHex(hex: string): ElemBytes {
|
||||
const bytes = Hex.decodeString(hex);
|
||||
if (bytes.length !== Constants.BYTES_LENGTH) {
|
||||
throw new Error('Invalid bytes length');
|
||||
}
|
||||
this._bytes.set(bytes, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
hex(): string {
|
||||
return Hex.encodeString(this._bytes);
|
||||
}
|
||||
|
||||
// ElemBytesToInts converts slice of ElemBytes to slice of *big.Int
|
||||
static elemBytesToInts(elements: ElemBytes[]): bigint[] {
|
||||
const result: bigint[] = [];
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = elements[i];
|
||||
result.push(element.toBigInt());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
55
src/hex.ts
Normal file
55
src/hex.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
export class Hex {
|
||||
static readonly HEX_TABLE = '0123456789abcdef';
|
||||
static textEncoder = new TextEncoder();
|
||||
|
||||
static encodeLength(n: number): number {
|
||||
return n * 2;
|
||||
}
|
||||
|
||||
static encode(src: Uint8Array): Uint8Array {
|
||||
const dst = new Uint8Array(Hex.encodeLength(src.length));
|
||||
let j = 0;
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
dst[j] = Hex.HEX_TABLE[src[i] >> 4].charCodeAt(0);
|
||||
dst[j + 1] = Hex.HEX_TABLE[src[i] & 0x0f].charCodeAt(0);
|
||||
j += 2;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
static decodeString(s: string): Uint8Array {
|
||||
return Hex.decode(s);
|
||||
}
|
||||
static fromHexChar(c: number): number {
|
||||
if ('0'.charCodeAt(0) <= c && c <= '9'.charCodeAt(0)) {
|
||||
return c - '0'.charCodeAt(0);
|
||||
} else if ('a'.charCodeAt(0) <= c && c <= 'f'.charCodeAt(0)) {
|
||||
return c - 'a'.charCodeAt(0) + 10;
|
||||
}
|
||||
if ('A'.charCodeAt(0) <= c && c <= 'F'.charCodeAt(0)) {
|
||||
return c - 'A'.charCodeAt(0) + 10;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid byte char ${c}`);
|
||||
}
|
||||
|
||||
private static decode(src: string): Uint8Array {
|
||||
let i = 0;
|
||||
let j = 1;
|
||||
const dst: number[] = [];
|
||||
for (; j < src.length; j += 2) {
|
||||
const a = Hex.fromHexChar(src[j - 1].charCodeAt(0));
|
||||
const b = Hex.fromHexChar(src[j].charCodeAt(0));
|
||||
dst[i] = (a << 4) | b;
|
||||
i++;
|
||||
}
|
||||
if (src.length % 2 == 1) {
|
||||
throw new Error('Invalid hex string');
|
||||
}
|
||||
return Uint8Array.from(dst);
|
||||
}
|
||||
|
||||
static encodeString(b: Uint8Array): string {
|
||||
return new TextDecoder().decode(Hex.encode(b));
|
||||
}
|
||||
}
|
||||
97
src/id.ts
Normal file
97
src/id.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Constants } from './constants';
|
||||
const base58Js = require('base58-js');
|
||||
import { fromLittleEndian, poseidonHash } from './util';
|
||||
import { BytesHelper } from './elemBytes';
|
||||
|
||||
// ID is a byte array with
|
||||
// [ type | root_genesis | checksum ]
|
||||
// [2 bytes | 27 bytes | 2 bytes ]
|
||||
// where the root_genesis are the first 28 bytes from the hash root_genesis
|
||||
|
||||
export class Id {
|
||||
private _bytes: Uint8Array;
|
||||
private readonly _checksum: Uint8Array;
|
||||
|
||||
constructor(typ: Uint8Array, genesis: Uint8Array) {
|
||||
this._checksum = BytesHelper.calculateChecksum(typ, genesis);
|
||||
this._bytes = Uint8Array.from([...typ, ...genesis, ...this._checksum]);
|
||||
}
|
||||
|
||||
private static getFromBytes(bytes: Uint8Array): Id {
|
||||
const { typ, genesis }: { typ: Uint8Array; genesis: Uint8Array } =
|
||||
BytesHelper.decomposeBytes(bytes);
|
||||
return new Id(typ, genesis);
|
||||
}
|
||||
|
||||
checksum(): Uint8Array {
|
||||
return this._checksum;
|
||||
}
|
||||
|
||||
string(): string {
|
||||
return base58Js.binary_to_base58(this._bytes);
|
||||
}
|
||||
|
||||
get bytes(): Uint8Array {
|
||||
return this._bytes;
|
||||
}
|
||||
|
||||
set bytes(b: Uint8Array) {
|
||||
this._bytes = b;
|
||||
}
|
||||
|
||||
type(): Uint8Array {
|
||||
return this._bytes.slice(0, 2);
|
||||
}
|
||||
|
||||
bigInt(): bigint {
|
||||
return fromLittleEndian(this._bytes);
|
||||
}
|
||||
|
||||
equal(id: Id): boolean {
|
||||
return JSON.stringify(this._bytes) === JSON.stringify(id.bytes);
|
||||
}
|
||||
|
||||
marshal(): Uint8Array {
|
||||
return new TextEncoder().encode(this.string());
|
||||
}
|
||||
|
||||
static unMarshal(b: Uint8Array): Id {
|
||||
return Id.fromString(new TextDecoder().decode(b));
|
||||
}
|
||||
|
||||
static fromBytes(b: Uint8Array): Id {
|
||||
const bytes = b ?? Uint8Array.from([]);
|
||||
if (bytes.length !== Constants.ID.ID_LENGTH) {
|
||||
throw new Error('fromBytes error: byte array incorrect length');
|
||||
}
|
||||
|
||||
if (bytes.every((i: number) => i === 0)) {
|
||||
throw new Error('fromBytes error: byte array empty');
|
||||
}
|
||||
|
||||
const id = Id.getFromBytes(bytes);
|
||||
|
||||
if (!BytesHelper.checkChecksum(bytes)) {
|
||||
throw new Error('fromBytes error: checksum error');
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static fromString(s: string): Id {
|
||||
const bytes = base58Js.base58_to_binary(s);
|
||||
return Id.fromBytes(bytes);
|
||||
}
|
||||
|
||||
static idFromInt(bigInt: bigint): Id {
|
||||
const b = BytesHelper.intToBytes(bigInt);
|
||||
return Id.fromBytes(b);
|
||||
}
|
||||
|
||||
static async profileId(id: Id, nonce: bigint): Promise<Id> {
|
||||
const bigIntHash = await poseidonHash([id.bigInt(), nonce]);
|
||||
const { typ } = BytesHelper.decomposeBytes(id.bytes);
|
||||
const genesis = BytesHelper.intToNBytes(bigIntHash, 27);
|
||||
return new Id(typ, genesis);
|
||||
}
|
||||
}
|
||||
3
src/index.d.ts
vendored
Normal file
3
src/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare module 'circomlibjs' {
|
||||
export function buildPoseidon(): Promise<any>;
|
||||
}
|
||||
17
src/index.ts
17
src/index.ts
@@ -1,9 +1,8 @@
|
||||
export function helloWorld() {
|
||||
const message = 'Hello World from my example modern npm package!';
|
||||
return message;
|
||||
}
|
||||
|
||||
export function goodBye() {
|
||||
const message = 'Goodbye from my example modern npm package!';
|
||||
return message;
|
||||
}
|
||||
export * from './claim';
|
||||
export * from './constants';
|
||||
export * from './did';
|
||||
export * from './elemBytes';
|
||||
export * from './hex';
|
||||
export * from './id';
|
||||
export * from './schemaHash';
|
||||
export * from './util';
|
||||
|
||||
67
src/schemaHash.ts
Normal file
67
src/schemaHash.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Hex } from './hex';
|
||||
import { Constants } from './constants';
|
||||
import { BytesHelper } from './elemBytes';
|
||||
|
||||
export class SchemaHash {
|
||||
private _bytes: Uint8Array = new Uint8Array(Constants.SCHEMA.HASH_LENGTH);
|
||||
/**
|
||||
* Constructor
|
||||
* @param bytes
|
||||
*/
|
||||
constructor(bytes?: Uint8Array) {
|
||||
if (bytes) {
|
||||
this._bytes = bytes;
|
||||
}
|
||||
if (this.bytes.length !== Constants.SCHEMA.HASH_LENGTH) {
|
||||
throw new Error(`Schema hash must be ${Constants.SCHEMA.HASH_LENGTH} bytes long`);
|
||||
}
|
||||
}
|
||||
|
||||
get bytes(): Uint8Array {
|
||||
return this._bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* MarshalText returns HEX representation of SchemaHash.
|
||||
* @returns {Uint8Array} 32 bytes//
|
||||
*/
|
||||
marshalTextBytes(): Uint8Array {
|
||||
return Hex.encode(this.bytes);
|
||||
}
|
||||
|
||||
marshalText(): string {
|
||||
return new TextDecoder().decode(this.marshalTextBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* NewSchemaHashFromHex creates new SchemaHash from hex string
|
||||
* @param s
|
||||
* @returns {SchemaHash}
|
||||
*/
|
||||
static newSchemaHashFromHex(s: string): SchemaHash {
|
||||
const schemaEncodedBytes = Hex.decodeString(s);
|
||||
|
||||
if (schemaEncodedBytes.length !== Constants.SCHEMA.HASH_LENGTH) {
|
||||
throw new Error(`invalid schema hash length: ${schemaEncodedBytes.length}`);
|
||||
}
|
||||
|
||||
return new SchemaHash(schemaEncodedBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* NewSchemaHashFromInt creates new SchemaHash from big.Int
|
||||
* @param i
|
||||
* @returns
|
||||
*/
|
||||
static newSchemaHashFromInt(i: bigint): SchemaHash {
|
||||
return new SchemaHash(BytesHelper.intToBytes(i));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert SchemaHash to big.Int
|
||||
* @returns {bigint}
|
||||
*/
|
||||
bigInt(): bigint {
|
||||
return BytesHelper.bytesToInt(this.bytes);
|
||||
}
|
||||
}
|
||||
78
src/util.ts
Normal file
78
src/util.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Constants } from './constants';
|
||||
const buildPoseidon = require('circomlibjs');
|
||||
|
||||
export function fromLittleEndian(bytes: Uint8Array): bigint {
|
||||
const n256 = BigInt(256);
|
||||
let result = BigInt(0);
|
||||
let base = BigInt(1);
|
||||
bytes.forEach((byte) => {
|
||||
result += base * BigInt(byte);
|
||||
base = base * n256;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function fromBigEndian(bytes: Uint8Array): bigint {
|
||||
return fromLittleEndian(bytes.reverse());
|
||||
}
|
||||
|
||||
export function toLittleEndian(bigNumber: bigint, len = 31): Uint8Array {
|
||||
const n256 = BigInt(256);
|
||||
const result = new Uint8Array(len);
|
||||
let i = 0;
|
||||
while (bigNumber > BigInt(0)) {
|
||||
result[i] = Number(bigNumber % n256);
|
||||
bigNumber = bigNumber / n256;
|
||||
i += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toBigEndian(bigNumber: bigint): Uint8Array {
|
||||
return toLittleEndian(bigNumber).reverse();
|
||||
}
|
||||
|
||||
export function putUint32(n: number): Uint8Array {
|
||||
const buf = new ArrayBuffer(4);
|
||||
const view = new DataView(buf);
|
||||
view.setUint32(0, n, true);
|
||||
return new Uint8Array(buf);
|
||||
}
|
||||
|
||||
export function getUint32(arr: Uint8Array): number {
|
||||
const buf = arr.buffer.slice(arr.byteOffset, arr.byteOffset + arr.byteLength);
|
||||
return new DataView(buf).getUint32(0, true);
|
||||
}
|
||||
|
||||
export function putUint64(n: number): Uint8Array {
|
||||
const buf = new ArrayBuffer(8);
|
||||
const view = new DataView(buf);
|
||||
view.setBigUint64(0, BigInt(n), true);
|
||||
return new Uint8Array(buf);
|
||||
}
|
||||
|
||||
export function getUint64(arr: Uint8Array): number {
|
||||
const buf = arr.buffer.slice(arr.byteOffset, arr.byteOffset + arr.byteLength);
|
||||
return Number(new DataView(buf).getBigUint64(0, true));
|
||||
}
|
||||
|
||||
export function getUnixTimestamp(d: Date): number {
|
||||
return Math.floor(d.getTime() / 1000);
|
||||
}
|
||||
export function getDateFromUnixTimestamp(n: number): Date {
|
||||
return new Date(n * 1000);
|
||||
}
|
||||
|
||||
// checkBigIntInField checks if given *big.Int fits in a Field Q element
|
||||
export function checkBigIntInField(a: bigint): boolean {
|
||||
return a < Constants.Q;
|
||||
}
|
||||
|
||||
export function checkBigIntArrayInField(arr: bigint[]): boolean {
|
||||
return arr.every((n) => checkBigIntInField(n));
|
||||
}
|
||||
|
||||
export async function poseidonHash(input: number[] | bigint[] | Uint8Array): Promise<bigint> {
|
||||
const poseidon = await buildPoseidon();
|
||||
return poseidon.F.toObject(poseidon(input));
|
||||
}
|
||||
375
tests/claim.test.ts
Normal file
375
tests/claim.test.ts
Normal file
@@ -0,0 +1,375 @@
|
||||
import { IdPosition, SubjectFlag } from './../src/claim';
|
||||
import { BytesHelper, ElemBytes } from './../src/elemBytes';
|
||||
import {
|
||||
Claim,
|
||||
newClaim,
|
||||
withExpirationDate,
|
||||
withFlagUpdatable,
|
||||
withIndexData,
|
||||
withIndexDataBytes,
|
||||
withIndexDataInts,
|
||||
withRevocationNonce,
|
||||
withValueData,
|
||||
withValueDataInts,
|
||||
withVersion
|
||||
} from '../src/claim';
|
||||
import { SchemaHash } from '../src/schemaHash';
|
||||
import { poseidonHash } from '../src/util';
|
||||
import { Hex } from '../src/hex';
|
||||
import { Constants } from '../src/constants';
|
||||
import { Id } from '../src/id';
|
||||
describe('claim test', () => {
|
||||
it('new claim', () => {
|
||||
const claim = newClaim(new SchemaHash(), withFlagUpdatable(true));
|
||||
expect(claim.value.length).toEqual(4);
|
||||
for (let i = 1; i < 4; i++) {
|
||||
expect(claim.index[i].bytes.every((b) => b === 0)).toBeTruthy();
|
||||
}
|
||||
for (let i = 0; i < 32; i++) {
|
||||
if (i === 16) {
|
||||
expect(claim.index[0].bytes[i]).toEqual(0b10000);
|
||||
} else {
|
||||
expect(claim.index[0].bytes[i]).toEqual(0);
|
||||
}
|
||||
}
|
||||
|
||||
const dt = claim.getExpirationDate();
|
||||
expect(dt).toBeNull();
|
||||
});
|
||||
|
||||
it('raw slots', async () => {
|
||||
const claim = newClaim(new SchemaHash(), withFlagUpdatable(true));
|
||||
const { index, value } = claim.rawSlots();
|
||||
const indexHash = await poseidonHash([
|
||||
index[0].toBigInt(),
|
||||
index[1].toBigInt(),
|
||||
index[2].toBigInt(),
|
||||
index[3].toBigInt()
|
||||
]);
|
||||
const valueHash = await poseidonHash([
|
||||
value[0].toBigInt(),
|
||||
value[1].toBigInt(),
|
||||
value[2].toBigInt(),
|
||||
value[3].toBigInt()
|
||||
]);
|
||||
|
||||
expect('19905260441950906049955646784794273651462264973332746773406911374272567544299').toEqual(
|
||||
indexHash.toString()
|
||||
);
|
||||
|
||||
expect('2351654555892372227640888372176282444150254868378439619268573230312091195718').toEqual(
|
||||
valueHash.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it('getSchemaHash', async () => {
|
||||
const sc = new SchemaHash(
|
||||
Uint8Array.from(Array.from({ length: 16 }, () => Math.floor(Math.random() * 16)))
|
||||
);
|
||||
expect(sc.bytes.length).toEqual(16);
|
||||
const claim = newClaim(sc);
|
||||
expect(sc.bytes).toEqual(claim.index[0].bytes.slice(0, sc.bytes.length));
|
||||
const shFromClaim = claim.getSchemaHash();
|
||||
const shFromClaimHexBytes = shFromClaim.marshalText();
|
||||
expect(Hex.encodeString(sc.bytes)).toEqual(shFromClaimHexBytes);
|
||||
});
|
||||
|
||||
it('getFlagUpdatable', async () => {
|
||||
const sc = new SchemaHash();
|
||||
let claim = newClaim(sc);
|
||||
expect(claim.getFlagUpdatable()).toBeFalsy();
|
||||
|
||||
claim.setFlagUpdatable(true);
|
||||
expect(claim.getFlagUpdatable()).toBeTruthy();
|
||||
|
||||
claim.setFlagUpdatable(false);
|
||||
expect(claim.getFlagUpdatable()).toBeFalsy();
|
||||
|
||||
claim = newClaim(sc, withFlagUpdatable(true));
|
||||
expect(claim.getFlagUpdatable()).toBeTruthy();
|
||||
|
||||
claim = newClaim(sc, withFlagUpdatable(false));
|
||||
expect(claim.getFlagUpdatable()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('getVersion', async () => {
|
||||
const sc = new SchemaHash();
|
||||
const maxUint32 = Math.pow(2, 32) - 1;
|
||||
const claim = newClaim(sc, withVersion(maxUint32));
|
||||
expect(maxUint32).toEqual(claim.getVersion());
|
||||
const randomInt = Math.floor(Math.random() * maxUint32);
|
||||
claim.setVersion(randomInt);
|
||||
expect(randomInt).toEqual(claim.getVersion());
|
||||
});
|
||||
|
||||
it('getRevocationNonce', () => {
|
||||
const sc = new SchemaHash();
|
||||
const nonce = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
const claim = newClaim(sc, withRevocationNonce(nonce));
|
||||
expect(nonce).toEqual(claim.getRevocationNonce());
|
||||
const nonce2 = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
claim.setRevocationNonce(nonce2);
|
||||
expect(nonce2).toEqual(claim.getRevocationNonce());
|
||||
});
|
||||
|
||||
it('expirationDate', () => {
|
||||
const sh = new SchemaHash();
|
||||
const expDate = new Date();
|
||||
expDate.setSeconds(0, 0);
|
||||
const c1 = newClaim(sh, withExpirationDate(expDate));
|
||||
|
||||
let expDate2 = c1.getExpirationDate();
|
||||
expect(expDate2).not.toBeNull();
|
||||
expect(expDate2?.getTime()).toEqual(expDate.getTime());
|
||||
|
||||
c1.resetExpirationDate();
|
||||
expDate2 = c1.getExpirationDate();
|
||||
expect(expDate2).toBeNull();
|
||||
|
||||
const expDate3 = new Date(expDate);
|
||||
expDate3.setSeconds(expDate3.getSeconds() + 10);
|
||||
expDate2 = c1.getExpirationDate();
|
||||
c1.setExpirationDate(expDate3);
|
||||
expDate2 = c1.getExpirationDate();
|
||||
expect(expDate2?.getTime()).toEqual(expDate3.getTime());
|
||||
});
|
||||
|
||||
it('test int size', () => {
|
||||
const iX = BigInt(
|
||||
'16243864111864693853212588481963275789994876191154110553066821559749894481761'
|
||||
);
|
||||
const iY = BigInt(
|
||||
'7078462697308959301666117070269719819629678436794910510259518359026273676830'
|
||||
);
|
||||
const vX = BigInt(
|
||||
'12448278679517811784508557734102986855579744384337338465055621486538311281772'
|
||||
);
|
||||
const vY = BigInt(
|
||||
'9260608685281348956030279125705000716237952776955782848598673606545494194823'
|
||||
);
|
||||
|
||||
const ixSlot = new ElemBytes().setBigInt(iX);
|
||||
const iySlot = new ElemBytes().setBigInt(iY);
|
||||
const vxSlot = new ElemBytes().setBigInt(vX);
|
||||
const vySlot = new ElemBytes().setBigInt(vY);
|
||||
newClaim(new SchemaHash(), withIndexData(ixSlot, iySlot), withValueData(vxSlot, vySlot));
|
||||
expect(ixSlot.toBigInt().toString()).toEqual(iX.toString());
|
||||
expect(iySlot.toBigInt().toString()).toEqual(iY.toString());
|
||||
expect(vxSlot.toBigInt().toString()).toEqual(vX.toString());
|
||||
expect(vySlot.toBigInt().toString()).toEqual(vY.toString());
|
||||
});
|
||||
|
||||
it('new data slot from int', () => {
|
||||
const ds = new ElemBytes().setBigInt(
|
||||
BigInt('16243864111864693853212588481963275789994876191154110553066821559749894481761')
|
||||
);
|
||||
const expected = new ElemBytes(
|
||||
Uint8Array.from([
|
||||
0x61, 0x27, 0xa0, 0xeb, 0x58, 0x7a, 0x6c, 0x2b, 0x4a, 0xa8, 0xc1, 0x2e, 0xf5, 0x01, 0xb2,
|
||||
0xdb, 0xd0, 0x9c, 0xb1, 0xa5, 0x9c, 0x83, 0x42, 0x57, 0x91, 0xa5, 0x20, 0xbf, 0x86, 0xb3,
|
||||
0xe9, 0x23
|
||||
])
|
||||
);
|
||||
expect(ds.bytes).toEqual(expected.bytes);
|
||||
expect(() =>
|
||||
new ElemBytes().setBigInt(
|
||||
BigInt('9916243864111864693853212588481963275789994876191154110553066821559749894481761')
|
||||
)
|
||||
).toThrow(new Error(Constants.ERRORS.DATA_OVERFLOW));
|
||||
});
|
||||
|
||||
it('index data ints', () => {
|
||||
const expSlot = new ElemBytes();
|
||||
expSlot.setBigInt(BigInt(0));
|
||||
const value = BigInt(64);
|
||||
|
||||
const claim = newClaim(new SchemaHash(), withIndexDataInts(value, null));
|
||||
expect(expSlot.bytes).toEqual(claim.index[3].bytes);
|
||||
|
||||
const claim2 = newClaim(new SchemaHash(), withIndexDataInts(null, value));
|
||||
expect(expSlot.bytes).toEqual(claim2.index[2].bytes);
|
||||
});
|
||||
|
||||
it('value data ints', () => {
|
||||
const expSlot = new ElemBytes();
|
||||
expSlot.setBigInt(BigInt(0));
|
||||
const value = BigInt(64);
|
||||
const claim = newClaim(new SchemaHash(), withValueDataInts(value, null));
|
||||
expect(expSlot.bytes).toEqual(claim.value[3].bytes);
|
||||
|
||||
const claim2 = newClaim(new SchemaHash(), withValueDataInts(null, value));
|
||||
expect(expSlot.bytes).toEqual(claim2.value[2].bytes);
|
||||
});
|
||||
|
||||
it('with index data bytes', () => {
|
||||
const iX = BigInt(
|
||||
'124482786795178117845085577341029868555797443843373384650556214865383112817'
|
||||
);
|
||||
const expSlot = new ElemBytes();
|
||||
expSlot.setBigInt(BigInt(0));
|
||||
|
||||
const claim = newClaim(new SchemaHash(), withIndexDataBytes(BytesHelper.intToBytes(iX), null));
|
||||
expect(expSlot.bytes).toEqual(claim.index[3].bytes);
|
||||
});
|
||||
|
||||
describe('serialization', () => {
|
||||
it('with strings', () => {
|
||||
const input = `[
|
||||
"15163995036539824738096525342132337704181738148399168403057770094395141110111",
|
||||
"3206594817839378626027676511482956481343861686313501795018892230311002175077",
|
||||
"7420031054231607091230846181053275837604749850669737756447914128096832575029",
|
||||
"6843256246667081694694856844555135410358903741435158507252727716055448769466",
|
||||
"18335061644187980192028500482619331449203987338928612566250871337402164885236",
|
||||
"4747739418092571675618239353368909204965774632269590366651599441049750269324",
|
||||
"10060277146294090095035892104009266064127776406104429246320070556972379481946",
|
||||
"5835715034681704899254417398745238273415614452113785384300119694985241103333"
|
||||
]`;
|
||||
|
||||
const expected = new Claim();
|
||||
expected.index = [
|
||||
new ElemBytes().slotFromHex(
|
||||
'5fb90badb37c5821b6d95526a41a9504680b4e7c8b763a1b1d49d4955c848621'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'65f606f6a63b7f3dfd2567c18979e4d60f26686d9bf2fb26c901ff354cde1607'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'35d6042c4160f38ee9e2a9f3fb4ffb0019b454d522b5ffa17604193fb8966710'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'ba53af19779cb2948b6570ffa0b773963c130ad797ddeafe4e3ad29b5125210f'
|
||||
)
|
||||
];
|
||||
expected.value = [
|
||||
new ElemBytes().slotFromHex(
|
||||
'f4b6f44090a32711f3208e4e4b89cb5165ce64002cbd9c2887aa113df2468928'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'8ced323cb76f0d3fac476c9fb03fc9228fbae88fd580663a0454b68312207f0a'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'5a27db029de37ae37a42318813487685929359ca8c5eb94e152dc1af42ea3d16'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'e50be1a6dc1d5768e8537988fddce562e9b948c918bba3e933e5c400cde5e60c'
|
||||
)
|
||||
];
|
||||
const claim = new Claim().unMarshalJson(input);
|
||||
expect(claim).toEqual(expected);
|
||||
|
||||
const result = expected.marshalJson();
|
||||
expect(JSON.parse(input)).toEqual(JSON.parse(result));
|
||||
});
|
||||
|
||||
it('with binary', () => {
|
||||
const binDataStr = [
|
||||
'5fb90badb37c5821b6d95526a41a9504680b4e7c8b763a1b1d49d4955c848621',
|
||||
'65f606f6a63b7f3dfd2567c18979e4d60f26686d9bf2fb26c901ff354cde1607',
|
||||
'35d6042c4160f38ee9e2a9f3fb4ffb0019b454d522b5ffa17604193fb8966710',
|
||||
'ba53af19779cb2948b6570ffa0b773963c130ad797ddeafe4e3ad29b5125210f',
|
||||
'f4b6f44090a32711f3208e4e4b89cb5165ce64002cbd9c2887aa113df2468928',
|
||||
'8ced323cb76f0d3fac476c9fb03fc9228fbae88fd580663a0454b68312207f0a',
|
||||
'5a27db029de37ae37a42318813487685929359ca8c5eb94e152dc1af42ea3d16',
|
||||
'e50be1a6dc1d5768e8537988fddce562e9b948c918bba3e933e5c400cde5e60c'
|
||||
].join('');
|
||||
const binData = Hex.decodeString(binDataStr);
|
||||
|
||||
const want = new Claim();
|
||||
want.index = [
|
||||
new ElemBytes().slotFromHex(
|
||||
'5fb90badb37c5821b6d95526a41a9504680b4e7c8b763a1b1d49d4955c848621'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'65f606f6a63b7f3dfd2567c18979e4d60f26686d9bf2fb26c901ff354cde1607'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'35d6042c4160f38ee9e2a9f3fb4ffb0019b454d522b5ffa17604193fb8966710'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'ba53af19779cb2948b6570ffa0b773963c130ad797ddeafe4e3ad29b5125210f'
|
||||
)
|
||||
];
|
||||
want.value = [
|
||||
new ElemBytes().slotFromHex(
|
||||
'f4b6f44090a32711f3208e4e4b89cb5165ce64002cbd9c2887aa113df2468928'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'8ced323cb76f0d3fac476c9fb03fc9228fbae88fd580663a0454b68312207f0a'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'5a27db029de37ae37a42318813487685929359ca8c5eb94e152dc1af42ea3d16'
|
||||
),
|
||||
new ElemBytes().slotFromHex(
|
||||
'e50be1a6dc1d5768e8537988fddce562e9b948c918bba3e933e5c400cde5e60c'
|
||||
)
|
||||
];
|
||||
|
||||
const result = new Claim();
|
||||
result.unMarshalBinary(binData);
|
||||
expect(want).toEqual(result);
|
||||
|
||||
const marshalResult = want.marshalBinary();
|
||||
expect(binData).toEqual(marshalResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe('work with id', () => {
|
||||
it('id position', () => {
|
||||
const tests = [
|
||||
{
|
||||
name: 'self claim',
|
||||
claim: () => newClaim(new SchemaHash()),
|
||||
expectedPosition: IdPosition.None
|
||||
},
|
||||
{
|
||||
name: 'subject stored in index',
|
||||
claim: () => {
|
||||
const claim = newClaim(new SchemaHash());
|
||||
const genesis32bytes = BytesHelper.hashBytes('genesistest');
|
||||
const genesis = genesis32bytes.slice(0, 27);
|
||||
claim.setIndexId(new Id(Constants.ID.TYPE_DEFAULT, genesis));
|
||||
return claim;
|
||||
},
|
||||
expectedPosition: IdPosition.Index
|
||||
},
|
||||
{
|
||||
name: 'subject stored in value',
|
||||
claim: () => {
|
||||
const claim = newClaim(new SchemaHash());
|
||||
const genesis32bytes = BytesHelper.hashBytes('genesistest');
|
||||
const genesis = genesis32bytes.slice(0, 27);
|
||||
claim.setValueId(new Id(Constants.ID.TYPE_DEFAULT, genesis));
|
||||
return claim;
|
||||
},
|
||||
expectedPosition: IdPosition.Value
|
||||
}
|
||||
];
|
||||
|
||||
tests.forEach((test) => {
|
||||
const c = test.claim();
|
||||
const position = c.getIdPosition();
|
||||
expect(position).toEqual(test.expectedPosition);
|
||||
});
|
||||
});
|
||||
|
||||
it('id position error case', () => {
|
||||
const tests = [
|
||||
{
|
||||
name: 'invalid position',
|
||||
claim: () => {
|
||||
const c = newClaim(new SchemaHash());
|
||||
c.setSubject(SubjectFlag.Invalid);
|
||||
return c;
|
||||
},
|
||||
expectedPosition: IdPosition.None,
|
||||
expectedError: new Error(Constants.ERRORS.INVALID_SUBJECT_POSITION)
|
||||
}
|
||||
];
|
||||
|
||||
tests.forEach((tt) => {
|
||||
const c = tt.claim();
|
||||
expect(() => c.getIdPosition()).toThrow(tt.expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
48
tests/did.test.ts
Normal file
48
tests/did.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { DID, NetworkId, DIDMethodByte, DidMethod, withNetwork, Blockchain } from './../src/did';
|
||||
|
||||
describe('DID tests', () => {
|
||||
const tests = [
|
||||
{
|
||||
description: 'Test readonly did',
|
||||
identifier: 'tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa',
|
||||
did: 'did:iden3:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa',
|
||||
options: () => null
|
||||
},
|
||||
{
|
||||
description: 'Test eth did',
|
||||
identifier: 'zyaYCrj27j7gJfrBboMW49HFRSkQznyy12ABSVzTy',
|
||||
did: 'did:iden3:eth:main:zyaYCrj27j7gJfrBboMW49HFRSkQznyy12ABSVzTy',
|
||||
options: withNetwork(Blockchain.Ethereum, NetworkId.Main)
|
||||
},
|
||||
{
|
||||
description: 'Test polygon did',
|
||||
identifier: 'wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ',
|
||||
did: 'did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ',
|
||||
options: withNetwork(Blockchain.Polygon, NetworkId.Mumbai)
|
||||
}
|
||||
];
|
||||
|
||||
tests.forEach((test) => {
|
||||
it(test.description, () => {
|
||||
const got = DID.newDID(test.identifier, test.options);
|
||||
expect(got.toString()).toEqual(test.did);
|
||||
});
|
||||
});
|
||||
|
||||
it('parse DID', () => {
|
||||
// did
|
||||
let didStr = 'did:iden3:polygon:mumbai:wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ';
|
||||
let did = DID.parse(didStr);
|
||||
|
||||
expect('wyFiV4w71QgWPn6bYLsZoysFay66gKtVa9kfu6yMZ').toEqual(did.id.string());
|
||||
expect(NetworkId.Mumbai).toEqual(did.networkId);
|
||||
expect(Blockchain.Polygon).toEqual(did.blockchain);
|
||||
// readonly did
|
||||
didStr = 'did:iden3:tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa';
|
||||
did = DID.parse(didStr);
|
||||
expect('tN4jDinQUdMuJJo6GbVeKPNTPCJ7txyXTWU4T2tJa').toEqual(did.id.string());
|
||||
expect('').toEqual(did.networkId);
|
||||
expect('').toEqual(did.blockchain);
|
||||
expect([DIDMethodByte[DidMethod.Iden3], 0b0]).toMatchObject(did.id.type());
|
||||
});
|
||||
});
|
||||
84
tests/id.test.ts
Normal file
84
tests/id.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Constants } from './../src/constants';
|
||||
import { Id } from '../src/id';
|
||||
import { Hex } from './../src/hex';
|
||||
import { Blockchain, buildDIDType, DidMethod, NetworkId } from '../src/did';
|
||||
import { BytesHelper } from '../src/elemBytes';
|
||||
describe('id tests', () => {
|
||||
it('id parses', () => {
|
||||
// Generate ID0
|
||||
const typ0 = Hex.decodeString('0000').slice(0, 2);
|
||||
const genesis0 = BytesHelper.hashBytes('genesistest').slice(0, 27);
|
||||
const id0 = new Id(typ0, genesis0);
|
||||
// Check ID0
|
||||
expect('114vgnnCupQMX4wqUBjg5kUya3zMXfPmKc9HNH4m2E').toEqual(id0.string());
|
||||
// Generate ID1
|
||||
const typ1 = Hex.decodeString('0001').slice(0, 2);
|
||||
const genesis1 = BytesHelper.hashBytes('genesistest').slice(0, 27);
|
||||
const id1 = new Id(typ1, genesis1);
|
||||
// Check ID1
|
||||
expect('1GYjyJKqdDyzo927FqJkAdLWB64kV2NVAjaQFHtq4').toEqual(id1.string());
|
||||
|
||||
const emptyChecksum = [0x00, 0x00];
|
||||
expect(id0.bytes.slice(-2)).not.toEqual(emptyChecksum);
|
||||
|
||||
const id0FromBytes = Id.fromBytes(id0.bytes);
|
||||
expect(id0.bytes).toEqual(id0FromBytes.bytes);
|
||||
expect(id0.string()).toEqual(id0FromBytes.string());
|
||||
expect('114vgnnCupQMX4wqUBjg5kUya3zMXfPmKc9HNH4m2E').toEqual(id0FromBytes.string());
|
||||
|
||||
const id1FromBytes = Id.fromBytes(id1.bytes);
|
||||
expect(id1.bytes).toEqual(id1FromBytes.bytes);
|
||||
expect(id1.string()).toEqual(id1FromBytes.string());
|
||||
expect('1GYjyJKqdDyzo927FqJkAdLWB64kV2NVAjaQFHtq4').toEqual(id1FromBytes.string());
|
||||
|
||||
const id0FromString = Id.fromString(id0.string());
|
||||
expect(id0.bytes).toEqual(id0FromString.bytes);
|
||||
expect(id0.string()).toEqual(id0FromString.string());
|
||||
expect('114vgnnCupQMX4wqUBjg5kUya3zMXfPmKc9HNH4m2E').toEqual(id0FromString.string());
|
||||
});
|
||||
|
||||
it('marshal/unmarshal', () => {
|
||||
const id = Id.fromString('11AVZrKNJVqDJoyKrdyaAgEynyBEjksV5z2NjZogFv');
|
||||
const idj = id.marshal();
|
||||
expect('11AVZrKNJVqDJoyKrdyaAgEynyBEjksV5z2NjZogFv').toEqual(Id.unMarshal(idj).string());
|
||||
const idp = Id.unMarshal(idj);
|
||||
expect(id).toEqual(idp);
|
||||
});
|
||||
|
||||
it('Id as DID', () => {
|
||||
const typ = buildDIDType(DidMethod.Iden3, Blockchain.Polygon, NetworkId.Mumbai);
|
||||
const genesis1 = BytesHelper.hashBytes('genesistes1t2').slice(0, 27);
|
||||
const id = new Id(typ, genesis1);
|
||||
expect(() => id).not.toThrow();
|
||||
});
|
||||
|
||||
it('id checksum', () => {
|
||||
const typ = Constants.ID.TYPE_DEFAULT;
|
||||
const genesis = BytesHelper.hashBytes('genesistest').slice(0, 27);
|
||||
let id = new Id(typ, genesis);
|
||||
|
||||
const checksum = id.checksum();
|
||||
expect(BytesHelper.calculateChecksum(typ, genesis)).toEqual(checksum);
|
||||
expect(BytesHelper.checkChecksum(id.bytes)).toBeTruthy();
|
||||
|
||||
// check that if we change the checksum, returns false on CheckChecksum
|
||||
id = new Id(typ, genesis);
|
||||
id.bytes = Uint8Array.from([...id.bytes.slice(0, 29), ...[0x00, 0x01]]);
|
||||
expect(BytesHelper.checkChecksum(id.bytes)).toBeFalsy();
|
||||
|
||||
// check that if we change the type, returns false on CheckChecksum
|
||||
id = new Id(typ, genesis);
|
||||
id.bytes = Uint8Array.from([...[0x00, 0x01], ...id.bytes.slice(-29)]);
|
||||
expect(BytesHelper.checkChecksum(id.bytes)).toBeFalsy();
|
||||
|
||||
// check that if we change the genesis, returns false on CheckChecksum
|
||||
id = new Id(typ, genesis);
|
||||
const changedGenesis = BytesHelper.hashBytes('changedgenesis').slice(0, 27);
|
||||
id.bytes = Uint8Array.from([...id.bytes.slice(0, 2), ...changedGenesis, ...id.bytes.slice(-2)]);
|
||||
expect(BytesHelper.checkChecksum(id.bytes)).toBeFalsy();
|
||||
|
||||
// test with a empty id
|
||||
const empty = new Uint8Array(31);
|
||||
expect(() => Id.fromBytes(empty)).toThrow(new Error('fromBytes error: byte array empty'));
|
||||
});
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
import { helloWorld } from '../src';
|
||||
|
||||
describe('Hello World Function', () => {
|
||||
it('should return the hello world message', () => {
|
||||
const expected = 'Hello World from my example modern npm package!';
|
||||
const actual = helloWorld();
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
31
tests/schemaHash.test.ts
Normal file
31
tests/schemaHash.test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { SchemaHash } from '../src/schemaHash';
|
||||
import { Hex } from '../src/hex';
|
||||
describe('schema hash', () => {
|
||||
it('should return the hex value', () => {
|
||||
const source = Uint8Array.from([
|
||||
82, 253, 252, 7, 33, 130, 101, 79, 22, 63, 95, 15, 154, 98, 29, 114
|
||||
]);
|
||||
expect(source.length).toEqual(16);
|
||||
const actual = Hex.encode(source);
|
||||
const expected = Uint8Array.from([
|
||||
53, 50, 102, 100, 102, 99, 48, 55, 50, 49, 56, 50, 54, 53, 52, 102, 49, 54, 51, 102, 53, 102,
|
||||
48, 102, 57, 97, 54, 50, 49, 100, 55, 50
|
||||
]);
|
||||
expect(expected.length).toEqual(32);
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('new schema hash from hex', () => {
|
||||
const hash = 'ca938857241db9451ea329256b9c06e5';
|
||||
const got = SchemaHash.newSchemaHashFromHex(hash);
|
||||
const exp = Hex.decodeString(hash);
|
||||
expect(exp).toEqual(got.bytes);
|
||||
});
|
||||
|
||||
it('big int check', () => {
|
||||
const schema = SchemaHash.newSchemaHashFromHex('ca938857241db9451ea329256b9c06e5');
|
||||
const exp = BigInt('304427537360709784173770334266246861770');
|
||||
const got = schema.bigInt();
|
||||
expect(got).toEqual(exp);
|
||||
});
|
||||
});
|
||||
3
types/circomlibjs/index.d.ts
vendored
Normal file
3
types/circomlibjs/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare module 'circomlibjs' {
|
||||
export function buildPoseidon(): Promise<any>;
|
||||
}
|
||||
Reference in New Issue
Block a user