Skip to content

Commit

Permalink
Merge pull request #312 from TokenScript/feat/eas-identifier-attestation
Browse files Browse the repository at this point in the history
Feat: eas identifier attestation
  • Loading branch information
micwallace authored Sep 6, 2023
2 parents f692191 + 2319843 commit a493462
Show file tree
Hide file tree
Showing 11 changed files with 2,047 additions and 1,843 deletions.
17 changes: 15 additions & 2 deletions .github/workflows/js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ on:
- stage
paths:
- "src/main/javascript/crypto/**"
workflow_dispatch:
inputs:
snapshot_version:
description: "A custom version to use for the snapshot package"
required: false
default: false
type: string

jobs:
js-p1:
Expand Down Expand Up @@ -43,8 +50,13 @@ jobs:
id: publish
run: |
cd src/main/javascript/crypto/
SHORT_SHA="${GITHUB_SHA:0:7}"
SNAPSHOT_VERSION="SNAPSHOT-staging.${SHORT_SHA}"
# Only use hash when version isn't explicitly specified
if [ -n "${ATTESTATION_VER}" ]; then
SHORT_SHA="${GITHUB_SHA:0:7}"
SNAPSHOT_VERSION="SNAPSHOT-staging.${SHORT_SHA}"
fi;
sed -ri "s/\"version\"\s*:\s*\"(.*)\"/\"version\": \"\1-${SNAPSHOT_VERSION}\"/" package.json
cat package.json
npm i
Expand All @@ -53,6 +65,7 @@ jobs:
echo "version=${version}" >>$GITHUB_OUTPUT
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SNAPSHOT_VERSION: ${{ inputs.snapshot_version }}

downstream-attestation_id-ci:
needs:
Expand Down
3,308 changes: 1,537 additions & 1,771 deletions src/main/javascript/crypto/package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/main/javascript/crypto/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tokenscript/attestation",
"version": "0.5.1",
"version": "0.6.0-rc.6",
"description": "A library for integrating cryptographic attestations into applications",
"type": "commonjs",
"main": "dist/index.js",
Expand Down Expand Up @@ -49,6 +49,9 @@
"webpack": "^5.75.0",
"webpack-cli": "^4.10.0"
},
"overrides": {
"semver": "~7.5.2"
},
"browser": {
"crypto": false
},
Expand Down
51 changes: 51 additions & 0 deletions src/main/javascript/crypto/src/eas/EASIdentifierAttestation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {EasTicketAttestation, EasTicketCreationOptions} from "./EasTicketAttestation";
import {Signer} from "ethers";
import {KeyPair, KeysArray} from "../libs/KeyPair";

export interface EasIdentiferParameters {
version: number;
identifierType: "email";
commitment: string;
ethereumAddress: string;
}

export class EASIdentifierAttestation extends EasTicketAttestation {

constructor(
signingConfig?: {
EASconfig: {
address: string // EAS resolver contract address
version: string
chainId: number
},
signer: Signer
},
issuerKey?: KeyPair|string
) {
const schema = {
fields: [
{name: "version", type: "uint8"},
{name: "identifierType", type: "string"},
{name: "commitment", type: "bytes", isCommitment: true},
{name: "ethereumAddress", type: "address"}
]
};

super(schema, signingConfig, undefined, issuerKey ? <KeysArray>{"": issuerKey} : undefined);
}

async createEasAttestation(data: EasIdentiferParameters|any, options: EasTicketCreationOptions = {}, commitmentType = 'mail'){

// Identifier attestation is not revocable at this stage.
if (options.revocable === undefined)
options.revocable = false;

if (!options.validity){
const date = new Date();
date.setDate(date.getDate() + 30);
options.validity = {from: Math.round(Date.now() / 1000), to: Math.round(date.getTime() / 1000)}
}

return super.createEasAttestation(data, options, commitmentType);
}
}
146 changes: 146 additions & 0 deletions src/main/javascript/crypto/src/eas/EasAttestedObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import {EasTicketAttestation, TicketSchema} from "./EasTicketAttestation";
import {EASIdentifierAttestation} from "./EASIdentifierAttestation";
import {AttestationCrypto} from "../libs/AttestationCrypto";
import {ProofOfExponentInterface} from "../libs/ProofOfExponentInterface";
import {hexStringToUint8, logger, uint8tohex} from "../libs/utils";
import {AsnParser, AsnProp, AsnPropTypes, AsnSerializer} from "@peculiar/asn1-schema";
import {UsageProofOfExponent} from "../libs/UsageProofOfExponent";
import {defaultAbiCoder} from "ethers/lib/utils";

export class EasUseToken {

@AsnProp({ type: AsnPropTypes.Any })
public ticketAttestation: Uint8Array;

@AsnProp({ type: AsnPropTypes.Any })
public idAttestation: Uint8Array;

@AsnProp({ type: AsnPropTypes.Any })
public proof: Uint8Array;
}

export class EasAttestedObject {

private crypto = new AttestationCrypto();

private ticketAttestation: EasTicketAttestation;
private ticketSecret: bigint;
private identifierAttestation: EASIdentifierAttestation;
private identifierSecret: bigint;

private pok: ProofOfExponentInterface;

create(
ticketAttestation: EasTicketAttestation,
ticketSecret: bigint,
identifierAttestation: EASIdentifierAttestation,
identifierSecret: bigint){

this.ticketAttestation = ticketAttestation;
this.ticketSecret = ticketSecret;
this.identifierAttestation = identifierAttestation;
this.identifierSecret = identifierSecret;

this.pok = this.makeProof();
}

private makeProof(): ProofOfExponentInterface {

let attCom: Uint8Array = this.identifierAttestation.getCommitment();
let objCom: Uint8Array = this.ticketAttestation.getCommitment();
let pok: ProofOfExponentInterface = this.crypto.computeEqualityProof(uint8tohex(attCom), uint8tohex(objCom), this.identifierSecret, this.ticketSecret);

if (!this.crypto.verifyEqualityProof(attCom, objCom, pok)) {
throw new Error("The redeem proof did not verify");
}
return pok;
}

getEncoded(format: "abi"|"asn" = "abi"){

if (format === "abi"){

return defaultAbiCoder.encode(
["bytes", "bytes", "bytes"],
[
this.ticketAttestation.getAbiEncoded(),
this.identifierAttestation.getAbiEncoded(),
this.pok.getAbiEncoding()
]
)

} else {

const useToken = new EasUseToken();
useToken.ticketAttestation = new Uint8Array(this.ticketAttestation.getAsnEncoded());
useToken.idAttestation = new Uint8Array(this.identifierAttestation.getAsnEncoded());
useToken.proof = hexStringToUint8(this.pok.getDerEncoding());

return uint8tohex(new Uint8Array(AsnSerializer.serialize(useToken)));
}
}

static fromBytes<T extends EasTicketAttestation, A extends EASIdentifierAttestation>(encoded: Uint8Array, ticketClass: new () => T, idClass: new () => A, format: "abi"|"asn" = "abi"){

const me = new this();
me.ticketAttestation = new ticketClass();
me.identifierAttestation = new idClass();

let parts: {
ticketAttestation: Uint8Array,
idAttestation: Uint8Array
proof: Uint8Array
}

let pok = new UsageProofOfExponent();

if (format === "abi"){

const decoded = defaultAbiCoder.decode(
["bytes", "bytes", "bytes"],
encoded
);

parts = {
ticketAttestation: hexStringToUint8(decoded[0]),
idAttestation: hexStringToUint8(decoded[1]),
proof: hexStringToUint8(decoded[2])
}

pok.fromAbiBytes(parts.proof);

} else {
parts = AsnParser.parse(encoded, EasUseToken);
pok.fromBytes(new Uint8Array(parts.proof));
}

me.ticketAttestation.loadBinaryEncoded(format, parts.ticketAttestation);
me.identifierAttestation.loadBinaryEncoded(format, parts.idAttestation);
me.pok = pok;

return me;
}

async checkValidity(ethAddress:string = ""){

await this.ticketAttestation.validateEasAttestation();
await this.identifierAttestation.validateEasAttestation();

if (!this.crypto.verifyEqualityProof(
this.identifierAttestation.getCommitment(),
this.ticketAttestation.getCommitment(),
this.pok
)) {
throw new Error("Could not verify the consistency between the commitment in the identifier and ticket attestations");
}

if (ethAddress !== ""){
const attestedAddress = await this.identifierAttestation.getAttestationField("ethereumAddress");

if (attestedAddress.toLowerCase() !== ethAddress.toLowerCase())
throw Error("The provided ethereum address does not match the address specified in the identifier attestation");
}

return true;
}
}
Loading

2 comments on commit a493462

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for src/main/javascript/crypto/

St.
Category Percentage Covered / Total
🟡 Statements 76.91% 2545/3309
🔴 Branches 50.61% 376/743
🟢 Functions 82.39% 449/545
🟡 Lines 77.11% 2503/3246

Test suite run success

72 tests passing in 2 suites.

Report generated by 🧪jest coverage report action from a493462

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for src/main/javascript/crypto/

St.
Category Percentage Covered / Total
🟡 Statements 76.94% 2546/3309
🔴 Branches 50.74% 377/743
🟢 Functions 82.39% 449/545
🟡 Lines 77.14% 2504/3246

Test suite run success

72 tests passing in 2 suites.

Report generated by 🧪jest coverage report action from a493462

Please sign in to comment.