Skip to content

Commit

Permalink
feat: implement ABI encoding by default for UseTicketProof attestatio…
Browse files Browse the repository at this point in the history
…n bundle.
  • Loading branch information
micwallace committed Aug 9, 2023
1 parent e40f0c7 commit d906cca
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 56 deletions.
66 changes: 50 additions & 16 deletions src/main/javascript/crypto/src/eas/EasAttestedObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ 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 {KeyPair, KeysArray} from "../libs/KeyPair";
import {UsageProofOfExponent} from "../libs/UsageProofOfExponent";
import {DEBUGLEVEL} from "../config";
import {defaultAbiCoder} from "ethers/lib/utils";

export class EasUseToken {

Expand Down Expand Up @@ -57,31 +56,66 @@ export class EasAttestedObject {
return pok;
}

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

Check warning on line 59 in src/main/javascript/crypto/src/eas/EasAttestedObject.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

const useToken = new EasUseToken();
useToken.ticketAttestation = new Uint8Array(this.ticketAttestation.getAsnEncoded());
useToken.idAttestation = new Uint8Array(this.identifierAttestation.getAsnEncoded());
useToken.proof = hexStringToUint8(this.pok.getDerEncoding());
if (format === "abi"){

return uint8tohex(new Uint8Array(AsnSerializer.serialize(useToken)));
}
return defaultAbiCoder.encode(
["bytes", "bytes", "bytes"],
[
this.ticketAttestation.getAbiEncoded(),
this.identifierAttestation.getAbiEncoded(),
this.pok.getAbiEncoding()
]
)

static fromBytes<T extends EasTicketAttestation, A extends EASIdentifierAttestation>(encoded: Uint8Array, ticketClass: new () => T, idClass: new () => A){
} else {

const decoded = AsnParser.parse(encoded, EasUseToken);
const useToken = new EasUseToken();
useToken.ticketAttestation = new Uint8Array(this.ticketAttestation.getAsnEncoded());
useToken.idAttestation = new Uint8Array(this.identifierAttestation.getAsnEncoded());
useToken.proof = hexStringToUint8(this.pok.getDerEncoding());

const me = new this();
return uint8tohex(new Uint8Array(AsnSerializer.serialize(useToken)));
}
}

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

Check warning on line 83 in src/main/javascript/crypto/src/eas/EasAttestedObject.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

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

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

let pok = new UsageProofOfExponent();
pok.fromBytes( new Uint8Array(decoded.proof) ) ;

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;
Expand Down
127 changes: 98 additions & 29 deletions src/main/javascript/crypto/src/eas/EasTicketAttestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
ATTESTATION_TYPE,
Offchain,
SignedOffchainAttestation,
TypedDataSigner, SchemaEncoder
TypedDataSigner, SchemaEncoder, getSchemaUID
} from "@ethereum-attestation-service/eas-sdk";
import {BigNumber, ethers, Signer} from "ethers";
import {AttestationCrypto} from "../libs/AttestationCrypto";
Expand Down Expand Up @@ -52,11 +52,11 @@ export interface EasTicketCreationOptions {

export class EasAsnEmbeddedSchema {
@AsnProp({ type: AsnPropTypes.OctetString })
public easAttestation: Uint8Array
public attestation: Uint8Array
@AsnProp({ type: AsnPropTypes.BitString })
public signatureValue: Uint8Array;
public signature: Uint8Array;
@AsnProp({ type: AsnPropTypes.OctetString, optional: true })
public domainInfo: Uint8Array
public domain: Uint8Array
}

export class EasTicketAttestation extends AttestableObject implements Attestable {
Expand Down Expand Up @@ -109,7 +109,7 @@ export class EasTicketAttestation extends AttestableObject implements Attestable
* @param options
* @param commitmentType
*/
async createEasAttestation(data: {[key: string]: string|number}, options?: EasTicketCreationOptions, commitmentType = 'mail'){
async createEasAttestation(data: {[key: string]: string|number}, options: EasTicketCreationOptions = {}, commitmentType = 'mail'){

Check warning on line 112 in src/main/javascript/crypto/src/eas/EasTicketAttestation.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

if (!this.signingConfig)
throw new Error("Please provide valid signing config for this function.");
Expand Down Expand Up @@ -150,17 +150,20 @@ export class EasTicketAttestation extends AttestableObject implements Attestable
const schemaEncoder = new SchemaEncoder(this.getEasSchema());
const encodedData = schemaEncoder.encodeData(fieldData);

// Automatically calculate schema address (may be used for AW)
if (!options.schema)
options.schema = getSchemaUID(this.getEasSchema(), this.signingConfig.EASconfig.address, options.revocable !== false)

const newAttestation = await offchain.signOffchainAttestation({
recipient: options?.recipient ?? '0x0000000000000000000000000000000000000000',
recipient: options.recipient ?? '0x0000000000000000000000000000000000000000',
// Unix timestamp of when attestation expires. (0 for no expiration)
expirationTime: options?.validity?.to ?? 0,
expirationTime: options.validity?.to ?? 0,
// Unix timestamp of current time
time: options?.validity?.from ?? Math.round(Date.now() / 1000),
time: options.validity?.from ?? Math.round(Date.now() / 1000),
nonce: 0,
/*schema: "0x4677bc98bd107f75d03d13cf41e158e38f1b826502dd31f87bf384c1a888cbdc",*/
schema: options?.schema ?? "0x0000000000000000000000000000000000000000000000000000000000000000",
revocable: options?.revocable !== false,
refUID: options?.refUID ?? "0x0000000000000000000000000000000000000000000000000000000000000000",
schema: options.schema,
revocable: options.revocable !== false,
refUID: options.refUID ?? "0x0000000000000000000000000000000000000000000000000000000000000000",
data: encodedData,
}, this.signingConfig.signer as unknown as TypedDataSigner);

Expand Down Expand Up @@ -337,49 +340,112 @@ export class EasTicketAttestation extends AttestableObject implements Attestable
this.loadEasAttestation(decoded.sig as SignedOffchainAttestation, keys, commitmentSecret)
}

getAsnEncoded(compressed = false){
public getAbiEncodedParts(){

const domain = defaultAbiCoder.encode(
['string', "address", "uint256"],
[
this.signedAttestation.domain.version,
this.signedAttestation.domain.verifyingContract,
this.signedAttestation.domain.chainId
]
)

const abiEncoded = defaultAbiCoder.encode(
const attestation = defaultAbiCoder.encode(
this.signedAttestation.types.Attest.map((field) => field.type),
this.signedAttestation.types.Attest.map((field) => this.signedAttestation.message[field.name])
);

const signature = joinSignature(this.signedAttestation.signature);

return {domain, attestation, signature};
}

getBinaryEncoded(format: "abi"|"asn", compressed = false){

Check warning on line 364 in src/main/javascript/crypto/src/eas/EasTicketAttestation.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 364 in src/main/javascript/crypto/src/eas/EasTicketAttestation.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
return format === "abi" ? this.getAbiEncoded(compressed) : this.getAsnEncoded(compressed);

Check warning on line 365 in src/main/javascript/crypto/src/eas/EasTicketAttestation.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 365 in src/main/javascript/crypto/src/eas/EasTicketAttestation.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 365 in src/main/javascript/crypto/src/eas/EasTicketAttestation.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}

getAsnEncoded(compressed = false){

const encodedParts = this.getAbiEncodedParts();

const asnEmbedded = new EasAsnEmbeddedSchema();

asnEmbedded.easAttestation = hexStringToUint8(abiEncoded);
asnEmbedded.signatureValue = hexStringToUint8(joinSignature(this.signedAttestation.signature));
asnEmbedded.domain = hexStringToUint8(encodedParts.domain);
asnEmbedded.attestation = hexStringToUint8(encodedParts.attestation);
asnEmbedded.signature = hexStringToUint8(encodedParts.signature);

const domainEncoded = defaultAbiCoder.encode(
['string', "address", "uint256"],
const data = AsnSerializer.serialize(asnEmbedded);

if (!compressed)
return data;

return pako.deflate(data, {level: 9});
}

getAbiEncoded(compressed = false){

const encodedParts = this.getAbiEncodedParts();

const encoded = defaultAbiCoder.encode(
['bytes', "bytes", "bytes"],
[
this.signedAttestation.domain.version,
this.signedAttestation.domain.verifyingContract,
this.signedAttestation.domain.chainId
encodedParts.domain,
encodedParts.attestation,
encodedParts.signature
]
)
asnEmbedded.domainInfo = hexStringToUint8(domainEncoded);

const data = AsnSerializer.serialize(asnEmbedded);
const data = hexStringToUint8(encoded);

if (!compressed)
return data;

return pako.deflate(data, {level: 9});
}

loadAsnEncoded(bytes: ArrayBuffer|Uint8Array, keys?: KeysArray, compressed = false){
loadBinaryEncoded(format: "abi"|"asn", bytes: ArrayBuffer|Uint8Array, keys?: KeysArray, compressed = false){
return format === "abi" ? this.loadAbiEncoded(bytes, keys, compressed) : this.loadAsnEncoded(bytes, keys, compressed)
}

this.decodedData = undefined;
this.commitmentSecret = undefined;
loadAbiEncoded(bytes: ArrayBuffer|Uint8Array, keys?: KeysArray, compressed = false){

Check warning on line 411 in src/main/javascript/crypto/src/eas/EasTicketAttestation.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

if (compressed)
bytes = pako.inflate(bytes);

Check warning on line 414 in src/main/javascript/crypto/src/eas/EasTicketAttestation.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 414 in src/main/javascript/crypto/src/eas/EasTicketAttestation.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

const abiEmbedded = defaultAbiCoder.decode(
['bytes', "bytes", "bytes"],
new Uint8Array(bytes)
);

this.loadAbiEncodedParts({
domain: abiEmbedded[0],
attestation: abiEmbedded[1],
signature: abiEmbedded[2]
}, keys);
}

loadAsnEncoded(bytes: ArrayBuffer|Uint8Array, keys?: KeysArray, compressed = false){

if (compressed)
bytes = pako.inflate(bytes);

const asnEmbedded = AsnParser.parse(bytes, EasAsnEmbeddedSchema);

this.loadAbiEncodedParts(asnEmbedded, keys)
}

loadAbiEncodedParts(
parts: {domain: string|Uint8Array, attestation: string|Uint8Array, signature: string|Uint8Array},
keys?: KeysArray
){

this.decodedData = undefined;
this.commitmentSecret = undefined;

const domainDecoded = defaultAbiCoder.decode(
['string', "address", "uint256"],
asnEmbedded.domainInfo
parts.domain
);

const domain: EIP712DomainTypedData = {
Expand All @@ -391,7 +457,10 @@ export class EasTicketAttestation extends AttestableObject implements Attestable

// console.log("ABI Encoded bytes: ", "0x" + uint8tohex(new Uint8Array(asnEmbedded.easAttestation)));

const abiDecoded = defaultAbiCoder.decode(ATTESTATION_TYPE.map((field) => field.type), "0x" + uint8tohex(new Uint8Array(asnEmbedded.easAttestation)));
const abiDecoded = defaultAbiCoder.decode(
ATTESTATION_TYPE.map((field) => field.type),
parts.attestation
);

// console.log("Abi decoded: ", abiDecoded);

Expand All @@ -401,7 +470,7 @@ export class EasTicketAttestation extends AttestableObject implements Attestable
mappedDecodedValues[value.name] = abiDecoded[index];
}

const splitSignature = ethers.utils.splitSignature(new Uint8Array(asnEmbedded.signatureValue));
const splitSignature = ethers.utils.splitSignature(parts.signature);

this.signedAttestation = {
domain: domain,
Expand Down
16 changes: 12 additions & 4 deletions src/main/javascript/crypto/src/eas/EasZkProof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export class EasZkProof {
base64IdentifierAttestation: string,
attestorPublicKey: string,
base64senderPublicKeys: KeysConfig|KeysArray,
identifierFormat?: "asn"|"eas"
identifierFormat?: "asn"|"eas",
outputFormat: "abi"|"asn" = "abi"
){
try {
base64senderPublicKeys = KeyPair.parseKeyArrayStrings(base64senderPublicKeys);
Expand All @@ -47,7 +48,7 @@ export class EasZkProof {
const redeem = new EasAttestedObject();
redeem.create(ticketAttest, ticketSecret, idAttest, identifierSecret);

encodedUseTicket = redeem.getEncoded();
encodedUseTicket = redeem.getEncoded(outputFormat);

} else {
const idAttest = SignedIdentifierAttestation.fromBytes(base64ToUint8array(base64IdentifierAttestation), KeyPair.publicFromBase64orPEM(attestorPublicKey));
Expand All @@ -61,7 +62,14 @@ export class EasZkProof {
return hexStringToBase64(encodedUseTicket);
}

public async validateUseTicket(proof:string, base64attestorPublicKey:string, base64issuerPublicKeys: {[key: string]: KeyPair|string}, userEthKey?: string, identifierFormat?: "asn"|"eas"){
public async validateUseTicket(
proof:string,
base64attestorPublicKey:string,
base64issuerPublicKeys: {[key: string]: KeyPair|string},
userEthKey?: string,
identifierFormat?: "asn"|"eas",
outputFormat: "abi"|"asn" = "abi"
){

let attestorKey = KeyPair.publicFromBase64orPEM(base64attestorPublicKey);
let issuerKeys = KeyPair.parseKeyArrayStrings(base64issuerPublicKeys);
Expand All @@ -82,7 +90,7 @@ export class EasZkProof {
}
}

const decodedAttestedObject = EasAttestedObject.fromBytes(base64ToUint8array(proof), EasTicketWrapper, EasIdWrapper);
const decodedAttestedObject = EasAttestedObject.fromBytes(base64ToUint8array(proof), EasTicketWrapper, EasIdWrapper, outputFormat);
await decodedAttestedObject.checkValidity(userEthKey);

} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export interface ProofOfExponentInterface {
getChallengeResponse(): bigint;
getNonce(): Uint8Array;
getDerEncoding(): string;
getAbiEncoding(): string;
validateParameters(): boolean;
}
Loading

0 comments on commit d906cca

Please sign in to comment.