Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 10 additions & 32 deletions light-circuits/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -464,29 +464,7 @@
"@solana/buffer-layout-utils" "^0.2.0"
buffer "^6.0.3"

"@solana/web3.js@1.73.2":
version "1.73.2"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.73.2.tgz#4b30cd402b35733dae3a7d0b638be26a7742b395"
integrity sha512-9WACF8W4Nstj7xiDw3Oom22QmrhBh0VyZyZ7JvvG3gOxLWLlX3hvm5nPVJOGcCE/9fFavBbCUb5A6CIuvMGdoA==
dependencies:
"@babel/runtime" "^7.12.5"
"@noble/ed25519" "^1.7.0"
"@noble/hashes" "^1.1.2"
"@noble/secp256k1" "^1.6.3"
"@solana/buffer-layout" "^4.0.0"
agentkeepalive "^4.2.1"
bigint-buffer "^1.1.5"
bn.js "^5.0.0"
borsh "^0.7.0"
bs58 "^4.0.1"
buffer "6.0.1"
fast-stable-stringify "^1.0.0"
jayson "^3.4.4"
node-fetch "2"
rpc-websockets "^7.5.0"
superstruct "^0.14.2"

"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.68.0":
"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.73.2":
version "1.73.3"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.73.3.tgz#60e6bd68f6f364d4be360b1e0a03a0a68468a029"
integrity sha512-vHRMo589XEIpoujpE2sZZ1aMZvfA1ImKfNxobzEFyMb+H5j6mRRUXfdgWD0qJ0sm11e5BcBC7HPeRXJB+7f3Lg==
Expand Down Expand Up @@ -1401,7 +1379,7 @@ jsonparse@^1.2.0:
"@noble/hashes" "^1.1.5"
"@solana/spl-account-compression" "^0.1.5"
"@solana/spl-token" "0.3.7"
"@solana/web3.js" "1.73.2"
"@solana/web3.js" "^1.73.2"
axios "^1.3.4"
circomlib "^2.0.5"
circomlibjs "^0.1.7"
Expand Down Expand Up @@ -1569,20 +1547,20 @@ node-addon-api@^3.0.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==

node-fetch@2, node-fetch@^2.6.7:
version "2.6.9"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"
integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==
dependencies:
whatwg-url "^5.0.0"

node-fetch@2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"

node-fetch@^2.6.7:
version "2.6.9"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"
integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==
dependencies:
whatwg-url "^5.0.0"

node-gyp-build@^4.2.2, node-gyp-build@^4.3.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055"
Expand Down Expand Up @@ -1687,7 +1665,7 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==

rpc-websockets@^7.5.0, rpc-websockets@^7.5.1:
rpc-websockets@^7.5.1:
version "7.5.1"
resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.1.tgz#e0a05d525a97e7efc31a0617f093a13a2e10c401"
integrity sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==
Expand Down
2 changes: 1 addition & 1 deletion light-macros
Submodule light-macros updated from b98829 to 5215de
1 change: 1 addition & 0 deletions light-sdk-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"test-utxo": "ts-mocha --resolveJsonModule ./tsconfig.json -t 100000000 tests/utxo.test.ts --exit",
"test-verifiers": "ts-mocha --resolveJsonModule ./tsconfig.json -t 100000000 tests/verifiers.test.ts --exit",
"test-transaction": "ts-mocha --resolveJsonModule ./tsconfig.json -t 100000000 tests/transaction.test.ts --exit",
"test-account": "ts-mocha --resolveJsonModule ./tsconfig.json -t 100000000 tests/account.test.ts --exit",
"build": "yarn tsc",
"format": "prettier --write \"src/**/*.{ts,js}\"",
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
Expand Down
166 changes: 123 additions & 43 deletions light-sdk-ts/src/account.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
const nacl = require("tweetnacl");
import { BN } from "@coral-xyz/anchor";
import {
AccountError,
AccountErrorCode,
TransactionParametersErrorCode,
} from "./errors";
const { blake2b } = require("@noble/hashes/blake2b");
const b2params = { dkLen: 32 };
const ffjavascript = require("ffjavascript");
// @ts-ignore:
import { buildEddsa } from "circomlibjs";

export class Account {
/**
Expand All @@ -17,7 +24,7 @@ export class Account {
poseidon: any;
burnerSeed: Uint8Array;
// keypair for eddsa poseidon signatures
poseidonEddsa?: {
poseidonEddsaKeypair?: {
publicKey?: [Uint8Array, Uint8Array];
privateKey: Uint8Array;
};
Expand All @@ -41,6 +48,7 @@ export class Account {
poseidonEddsaPrivateKey,
eddsa,
encryptionPublicKey,
encryptionPrivateKey,
}: {
poseidon?: any;
seed?: string;
Expand All @@ -50,16 +58,33 @@ export class Account {
poseidonEddsaPrivateKey?: Uint8Array;
eddsa?: any;
encryptionPublicKey?: Uint8Array;
encryptionPrivateKey?: Uint8Array;
}) {
if (seed.length < 32) {
throw "seed too short length less than 32";
}
if (poseidon) {
this.poseidon = poseidon;
if (!poseidon) {
throw new AccountError(
TransactionParametersErrorCode.NO_POSEIDON_HASHER_PROVIDED,
"constructor",
);
}

this.poseidon = poseidon;
this.burnerSeed = new Uint8Array();
// creates a burner utxo by using the index for domain separation
if (burner) {
if (!seed) {
throw new AccountError(
AccountErrorCode.SEED_UNDEFINED,
"constructor",
"seed is required to create a burner account",
);
}
if (seed.length < 32) {
throw new AccountError(
AccountErrorCode.INVALID_SEED_SIZE,
"constructor",
"seed too short length less than 32",
);
}
// burnerSeed can be shared since hash cannot be inverted - only share this for app utxos
// sharing the burnerSeed saves 32 bytes in onchain data if it is require to share both
// the encryption and private key of a utxo
Expand All @@ -70,22 +95,26 @@ export class Account {
this.privkey,
this.poseidon,
);
this.poseidonEddsa = Account.getEddsaPrivateKey(
this.poseidonEddsaKeypair = Account.getEddsaPrivateKey(
this.burnerSeed.toString(),
);
} else if (privateKey) {
if (!encryptionPrivateKey) {
throw new AccountError(
AccountErrorCode.ENCRYPTION_PRIVATE_KEY_UNDEFINED,
"constructor",
);
}
this.privkey = privateKey;
this.pubkey = Account.generateShieldedPublicKey(
this.privkey,
this.poseidon,
);
if (poseidonEddsaPrivateKey) {
this.poseidonEddsa = { privateKey: poseidonEddsaPrivateKey };
this.poseidonEddsaKeypair = { privateKey: poseidonEddsaPrivateKey };
}
this.encryptionKeypair = {
publicKey: encryptionPublicKey ? encryptionPublicKey : new Uint8Array(),
secretKey: new Uint8Array(),
};
this.encryptionKeypair =
nacl.box.keyPair.fromSecretKey(encryptionPrivateKey);
} else if (publicKey) {
this.pubkey = publicKey;
this.privkey = new BN("0");
Expand All @@ -94,34 +123,69 @@ export class Account {
secretKey: new Uint8Array(),
};
} else {
if (!seed) {
throw new AccountError(
AccountErrorCode.SEED_UNDEFINED,
"constructor",
"seed is required to create an account",
);
}
if (seed.length < 32) {
throw new AccountError(
AccountErrorCode.INVALID_SEED_SIZE,
"constructor",
"seed too short length less than 32",
);
}
this.encryptionKeypair = Account.getEncryptionKeyPair(seed);
this.privkey = Account.generateShieldedPrivateKey(seed);
this.pubkey = Account.generateShieldedPublicKey(
this.privkey,
this.poseidon,
);
this.poseidonEddsa = Account.getEddsaPrivateKey(seed);
this.poseidonEddsaKeypair = Account.getEddsaPrivateKey(seed);
}
this.eddsa = eddsa;
}

async getEddsaPublicKey(): Promise<[Uint8Array, Uint8Array]> {
if (this.poseidonEddsa && this.eddsa) {
this.poseidonEddsa.publicKey = this.eddsa.prv2pub(
this.poseidonEddsa.privateKey,
async getEddsaPublicKey(eddsa?: any): Promise<[Uint8Array, Uint8Array]> {
if (!this.poseidonEddsaKeypair) {
throw new AccountError(
AccountErrorCode.POSEIDON_EDDSA_KEYPAIR_UNDEFINED,
"getEddsaPublicKey",
"poseidonEddsaKeypair.privateKey undefined",
);
}
if (!this.poseidonEddsaKeypair.privateKey) {
throw new AccountError(
AccountErrorCode.POSEIDON_EDDSA_KEYPAIR_UNDEFINED,
"getEddsaPublicKey",
"poseidonEddsaKeypair.privateKey undefined",
);
if (this.poseidonEddsa.publicKey) {
return this.poseidonEddsa.publicKey;
}
if (!this.eddsa) {
if (eddsa) {
this.eddsa = eddsa;
} else {
throw new Error("get poseidonEddsa.publicKey failed");
this.eddsa = await buildEddsa();
}
} else {
throw new Error("poseidonEddsa.privateKey undefined");
}

this.poseidonEddsaKeypair.publicKey = this.eddsa.prv2pub(
this.poseidonEddsaKeypair.privateKey,
);
if (!this.poseidonEddsaKeypair.publicKey) {
throw new AccountError(
AccountErrorCode.POSEIDON_EDDSA_GET_PUBKEY_FAILED,
"signEddsa",
"poseidonEddsaKeypair.privateKey undefined",
);
}
return this.poseidonEddsaKeypair.publicKey;
}

static getEddsaPrivateKey(seed: string) {
const privkeySeed = seed + "poseidonEddsa";
const privkeySeed = seed + "poseidonEddsaKeypair";
return {
publicKey: undefined,
privateKey: blake2b.create(b2params).update(privkeySeed).digest(),
Expand All @@ -132,31 +196,35 @@ export class Account {
return new BN(this.encryptionKeypair.publicKey).toBuffer("be", 32);
}

// TODO: make eddsa wrapper class
// TODO: include eddsa into static from methods
// TODO: Add check for uint8array to be well formed
async signEddsa(msg: string | Uint8Array, eddsa?: any): Promise<Uint8Array> {
if (!this.poseidonEddsaKeypair) {
throw new AccountError(
AccountErrorCode.POSEIDON_EDDSA_KEYPAIR_UNDEFINED,
"signEddsa",
"poseidonEddsaKeypair.privateKey undefined",
);
}

if (!this.eddsa) {
if (!eddsa) {
if (eddsa) {
this.eddsa = eddsa;
} else {
throw new Error("Eddsa is not provided");
this.eddsa = await buildEddsa();
}
}
if (this.poseidonEddsa) {
if (typeof msg == "string") {
return this.eddsa.packSignature(
this.eddsa.signPoseidon(
this.poseidonEddsa.privateKey,
this.poseidon.F.e(ffjavascript.Scalar.e(msg)),
),
);
} else {
return this.eddsa.packSignature(
this.eddsa.signPoseidon(this.poseidonEddsa.privateKey, msg),
);
}

if (typeof msg == "string") {
return this.eddsa.packSignature(
this.eddsa.signPoseidon(
this.poseidonEddsaKeypair.privateKey,
this.poseidon.F.e(ffjavascript.Scalar.e(msg)),
),
);
} else {
throw new Error("poseidonEddsa.privateKey undefined");
return this.eddsa.packSignature(
this.eddsa.signPoseidon(this.poseidonEddsaKeypair.privateKey, msg),
);
}
}

Expand All @@ -178,6 +246,13 @@ export class Account {
}

static createBurner(poseidon: any, seed: String, index: BN): Account {
if (seed.length < 32) {
throw new AccountError(
AccountErrorCode.INVALID_SEED_SIZE,
"constructor",
"seed too short length less than 32",
);
}
const burnerSeed = blake2b
.create(b2params)
.update(seed + "burnerSeed" + index.toString("hex"))
Expand All @@ -192,10 +267,15 @@ export class Account {
return new Account({ poseidon, seed: burnerSeedString, burner: true });
}

static fromPrivkey(poseidon: any, privateKey: Uint8Array): Account {
static fromPrivkey(
poseidon: any,
privateKey: Uint8Array,
encryptionPrivateKey: Uint8Array,
): Account {
const privkey = new BN(privateKey);
return new Account({ poseidon, privateKey: privkey });
return new Account({ poseidon, privateKey: privkey, encryptionPrivateKey });
}

static fromPubkey(
publicKey: Uint8Array,
encPubkey: Uint8Array,
Expand Down
13 changes: 13 additions & 0 deletions light-sdk-ts/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ export enum UtxoErrorCode {
ASSET_NOT_FOUND = "ASSET_NOT_FOUND",
}

export enum AccountErrorCode {
INVALID_SEED_SIZE = "INVALID_SEED_SIZE",
SEED_UNDEFINED = "SEED_UNDEFINED",
SEED_DEFINED = "SEED_DEFINED",
ENCRYPTION_PRIVATE_KEY_UNDEFINED = "ENCRYPTION_PRIVATE_KEY_UNDEFINED",
PRIVATE_KEY_UNDEFINED = "PRIVATE_KEY_UNDEFINED",
POSEIDON_EDDSA_KEYPAIR_UNDEFINED = "POSEIDON_EDDSA_KEYPAIR_UNDEFINED",
POSEIDON_EDDSA_GET_PUBKEY_FAILED = "POSEIDON_EDDSA_GET_PUBKEY_FAILED",
PUBLIC_KEY_UNDEFINED = "PUBLIC_KEY_UNDEFINED",
}

export enum ProviderErrorCode {
SOL_MERKLE_TREE_UNDEFINED = "SOL_MERKLE_TREE_UNDEFINED",
ANCHOR_PROVIDER_UNDEFINED = "ANCHOR_PROVIDER_UNDEFINED",
Expand Down Expand Up @@ -109,3 +120,5 @@ export class TransactioParametersError extends MetaError {}
export class UtxoError extends MetaError {}

export class VerifierError extends MetaError {}

export class AccountError extends MetaError {}
2 changes: 1 addition & 1 deletion light-sdk-ts/src/utxo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ export class Utxo {
const verifierAddress = new PublicKey(bytes.slice(95, 127));
// ...new Array(1), separator is otherwise 0
const appData = bytes.slice(128, bytes.length);
const burnerAccount = Account.fromPrivkey(
const burnerAccount = Account.fromBurnerSeed(
poseidon,
appData.slice(72, 104),
);
Expand Down
Loading