Skip to content

Commit

Permalink
Crypto WIP:
Browse files Browse the repository at this point in the history
- Added substrate signer, signature, address.
- Migrated ethereum signer to crypto/ethereum.
- Some encoding & signature fixes for messages.
- Removed tests that failed to build.

TL;DR: SDK now compiles again (make all), but haven't run the tests yet.
  • Loading branch information
RmbRT committed Nov 2, 2023
1 parent 7f30709 commit 8b1f9bc
Show file tree
Hide file tree
Showing 30 changed files with 642 additions and 617 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ all: ts

ts:
@yarn install -s
@yarn run build -s
@yarn run build
@echo "Built erdstall-ts-sdk."

bindings:
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@
"walk": "^2.3.14"
},
"dependencies": {
"@polkadot/api": "^10.9.1",
"@polkadot/api-contract": "^10.9.1",
"@polkadot/api": "^10.10.1",
"@polkadot/api-contract": "^10.10.1",
"@polkadot/util-crypto": "^12.5.1",
"axios": "^0.21.4",
"canonicalize": "^2.0.0",
"ethereum-waffle": "^3.4.0",
Expand Down
18 changes: 8 additions & 10 deletions src/api/responses/balanceproof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ export class BalanceProofs extends ErdstallObject {
//
// E.g.: Polkadot can be compatible with Ethereum addresses. In that case
// we would have:
// .-> <PolkadotChain> -> <ChainProof>
// /
// <EthereumAddress> -|
// \
// '-> <EthereumChain> -> <ChainProof>
// .-> <PolkadotChain> -> <ChainProof>
// /
// <EthereumAddress> -|
// \
// '-> <EthereumChain> -> <ChainProof>
@jsonMapMember(
String,
() => MapT(String, ChainProof, { shape: MapShape.OBJECT }),
Expand All @@ -73,7 +73,7 @@ export class BalanceProofs extends ErdstallObject {
// Signature is issued on canonicalized json of BalanceProofs
// (w/o sig of course).
@jsonMember(crypto.Signature)
readonly sig: ErdstallSignature;
readonly sig: crypto.Signature<crypto.Crypto>;

constructor(
proofs: Map<string, Map<Chain, ChainProof>>,
Expand All @@ -94,11 +94,9 @@ export class BalanceProofs extends ErdstallObject {
}

public verify(address: crypto.Address<crypto.Crypto>): boolean {
const rec = utils.verifyMessage(
return this.sig.verify(
this.encodePayload(),
this.sig.toString(),
);
return address.toString() === rec;
address);
}

public encodePayload(): Uint8Array {
Expand Down
38 changes: 12 additions & 26 deletions src/api/responses/txreceipt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@
import { ErdstallObject, registerErdstallType } from "#erdstall/api";
import { Account } from "#erdstall/ledger";
import { Address, Signature, Crypto } from "#erdstall/crypto";
import { EthereumAddress } from "#erdstall/crypto/ethereum";
import { Transaction, TransactionOutput } from "#erdstall/api/transactions";
import {
jsonObject,
jsonMember,
jsonMapMember,
TypedJSON,
MapShape,
} from "#erdstall/export/typedjson";
import { utils } from "ethers";
import { ABIEncoder, ABIPacked } from "../util";
import { ETHZERO } from "#erdstall/ledger/assets";
import canonicalize from "canonicalize";

const txReceiptTypeName = "TxReceipt";

Expand Down Expand Up @@ -64,33 +62,21 @@ export class TxReceipt extends ErdstallObject {
protected objectTypeName(): string {
return txReceiptTypeName;
}
packTagged(_: Address<Crypto>): ABIPacked {
const enc = new ABIEncoder();
return enc.pack(this.encodeABI(enc));
}
protected encodeABI(e: ABIEncoder): string {
e.encode(
["bytes", utils.arrayify(this.hash as utils.BytesLike)],
["bytes", this.output.payload],
protected encodePayload(): Uint8Array {
const json = JSON.parse(TypedJSON.stringify(this, TxReceipt));
delete json.sig;
const msg = canonicalize(
JSON.stringify({value: json }),
);
return "ErdstallTransactionOutput";
return new TextEncoder().encode(msg);
}
verify(contract: Address<Crypto>): boolean {
console.log(
utils.hexlify(
this.packTagged(EthereumAddress.fromString(ETHZERO)).bytes,
),
);
verify(enclaveNativeSigner: Address<Crypto>): boolean {
if (!this.sig) {
return false;
}
const rec = utils.verifyMessage(
this.packTagged(contract).keccak256(),
this.sig!.toString(),
);
console.log(rec);
console.log(this.tx.sender.toString());
return rec === this.tx.sender.toString();
return this.sig!.verify(
this.encodePayload(),
enclaveNativeSigner);
}
}

Expand Down
48 changes: 23 additions & 25 deletions src/api/transactions/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import {
jsonObject,
jsonMember,
jsonBigIntMember,
TypedJSON,
} from "#erdstall/export/typedjson";
import { ABIPacked } from "#erdstall/api/util";
import { Address, Signature, Crypto } from "#erdstall/crypto";
import { utils } from "ethers";
import { Signer } from "#erdstall/ledger/backend";
import { Address, Signature, Crypto, Signer } from "#erdstall/crypto";
import canonicalize from "canonicalize";

@jsonObject
export class TradeFees {
Expand All @@ -23,9 +22,12 @@ export class TradeFees {
this.fee = fee;
}

packTagged(): ABIPacked {
throw new Error("Method not implemented.");
// return new ABIEncoder(this.market, this.fee).pack("ErdstallTradeFees");
encodePayload(): Uint8Array {
const json = JSON.parse(TypedJSON.stringify(this, TradeFees));
const msg = canonicalize({
value: json
});
return new TextEncoder().encode(msg);
}
}

Expand All @@ -52,33 +54,29 @@ export class TradeOffer {
}

async sign(signer: Signer<Crypto>): Promise<this> {
this.sig = await signer.signMessage(this.packTagged().keccak256());
this.owner = await signer.address();
this.sig = await signer.sign(this.encodePayload());
return this;
}

verify(): boolean {
verify(signer: Address<Crypto>): boolean {
if (!this.sig) {
return false;
}
const rec = utils.verifyMessage(
this.packTagged().keccak256(),
this.sig!.toString(),
return this.sig.verify(
this.encodePayload(),
signer
);

return rec === this.owner.toString();
}

packTagged(): ABIPacked {
throw new Error("Method not implemented.");
// return new ABIEncoder()
// .encodeTagged(
// this.owner,
// this.offer,
// ["uint64", this.expiry],
// this.request,
// this.fees ? this.fees! : new Uint8Array(),
// )
// .pack("ErdstallTradeOffer");
encodePayload(): Uint8Array {
const json = JSON.parse(TypedJSON.stringify(this, TradeOffer));
delete json.sig;

const msg = canonicalize({
value: json,
});
return new TextEncoder().encode(msg);
}
}

Expand Down
14 changes: 6 additions & 8 deletions src/api/transactions/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"use strict";

import { ErdstallObject, registerErdstallType } from "#erdstall/api";
import { Address, Signature, Crypto } from "#erdstall/crypto";
import { Address, Signature, Signer, Crypto } from "#erdstall/crypto";
import { customJSON, ABIPacked } from "#erdstall/api/util";
import {
jsonObject,
Expand All @@ -11,9 +11,8 @@ import {
Serializable,
jsonBigIntMember,
} from "#erdstall/export/typedjson";
import { utils } from "ethers";
import { Signer } from "#erdstall/ledger/backend";
import canonicalize from "canonicalize";
import { utils } from "ethers";

const transactionImpls = new Map<string, Serializable<Transaction>>();
const transactionTypeName = "Transaction";
Expand Down Expand Up @@ -42,21 +41,20 @@ export abstract class Transaction extends ErdstallObject {
// Make sure the signature is set to undefined, otherwise signing would not
// be idempotent.
this.sig = undefined;
this.sender = await signer.address();
const msg = this.encodePayload();
this.sig = await signer.signMessage(msg);
this.sig = await signer.sign(msg);
return this;
}

verify(): boolean {
if (!this.sig) {
return false;
}
const rec = utils.verifyMessage(
return this.sig.verify(
this.encodePayload(),
this.sig!.toString(),
this.sender
);

return rec === this.sender.toString();
}

static fromJSON(js: any): Transaction {
Expand Down
4 changes: 2 additions & 2 deletions src/crypto/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export function registerAddressType(
}

export abstract class Address<_C extends Crypto> {
abstract type(): Crypto;
abstract type(): _C;
abstract get key(): string;
abstract equals(other: Address<Crypto>): boolean;
abstract equals(other: Address<_C>): boolean;
abstract toString(): string;
abstract toJSON(): string;

Expand Down
7 changes: 5 additions & 2 deletions src/crypto/ethereum/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export class EthereumAddress extends Address<"ethereum"> implements ABIValue {
}

static fromJSON(val: any): EthereumAddress {
if (typeof val !== "string") {
throw new Error("Expected to decode address from a string");
}
return new EthereumAddress(
utils.arrayify(val, { allowMissingPrefix: true }),
);
Expand Down Expand Up @@ -75,6 +78,6 @@ export class EthereumAddress extends Address<"ethereum"> implements ABIValue {
registerAddressType("ethereum", EthereumAddress);
customJSON(EthereumAddress);

export function addressKey(addr: Address<"ethereum"> | string): string {
return addr instanceof Address<"ethereum"> ? addr.key : addr.toLowerCase();
export function addressKey(addr: EthereumAddress | string): string {
return EthereumAddress.ensure(addr).key;
}
30 changes: 15 additions & 15 deletions src/crypto/ethereum/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,38 @@ import { jsonMember, jsonObject } from "#erdstall/export/typedjson";

@jsonObject
export class EthereumSignature extends Signature<"ethereum"> {
@jsonMember msg: Uint8Array;
@jsonMember address: Address<"ethereum">;
@jsonMember bytes: Uint8Array;

constructor(value: Uint8Array, address: Address<"ethereum">) {
constructor(value: Uint8Array) {
super();
this.msg = value;
this.address = address;
this.bytes = value;
}

static fromJSON(data: any): Signature<"ethereum"> {
const address = EthereumAddress.fromJSON(data.address);
const value = utils.arrayify(data.msg);
return new EthereumSignature(value, address);
if(typeof data !== "string") {
throw new Error("Expected to decode address from a string");
}
return new EthereumSignature(utils.arrayify(data));
}

verify(msg: Uint8Array, signer: Address<"ethereum">): boolean {
return utils.verifyMessage(msg, this.toString()) === signer.toString()
}

toJSON() {
return {
address: this.address,
msg: utils.hexlify(this.msg),
};
return utils.hexlify(this.bytes);
}

toString(): string {
return utils.hexlify(this.msg);
return utils.hexlify(this.bytes);
}

toBytes(): Uint8Array {
return this.msg;
return this.bytes;
}

asABI() {
return this.msg;
return this.bytes;
}

ABIType(): string {
Expand Down
51 changes: 51 additions & 0 deletions src/crypto/ethereum/signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: Apache-2.0
"use strict";

import { Bytes, Signer as EthersSigner, utils } from "ethers";
import { Signer, Address, Signature } from "#erdstall/crypto";
import { EthereumSignature } from "./signature";
import { EthereumAddress } from "./address";
import { ethers } from "ethers";

// Compile-time check that the EthersSigner implements the Signer interface.
export class EthereumSigner implements Signer<"ethereum"> {
readonly ethersSigner: EthersSigner;

constructor(ethersSigner: EthersSigner) {
this.ethersSigner = ethersSigner;
}

async sign(message: Uint8Array): Promise<Signature<"ethereum">> {
const sig = await this.ethersSigner.signMessage(
utils.keccak256(message),
);
return new EthereumSignature(utils.arrayify(sig));
}

async address(): Promise<EthereumAddress> {
return EthereumAddress.fromString(await this.ethersSigner.getAddress());
}

// Generates a unique random custodial account. Returns a signer, its
// associated account's address, and the private key used for restoring
// that account later using `restoreCustodialAccount()`.
static generateCustodialAccount(): {
signer: EthereumSigner;
privateKey: string;
} {
let wallet = ethers.Wallet.createRandom();
return {
signer: new EthereumSigner(wallet),
privateKey: wallet.privateKey,
};
}

// Restores a custodial account from its private key, as returned by
// `generateCustodialAccount()`. Returns a signer and the associated
// account's address.
static restoreCustodialAccount(privateKey: string): EthereumSigner {
let signer = new ethers.Wallet(privateKey);
return new EthereumSigner(signer);
}
}

1 change: 1 addition & 0 deletions src/crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
export * from "./crypto";
export * from "./address";
export * from "./signature";
export * from "./signer";
export * from "./asset";
Loading

0 comments on commit 8b1f9bc

Please sign in to comment.