Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add secp256k1 verify support #81

Merged
merged 6 commits into from
Sep 9, 2024
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
1 change: 1 addition & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ RUN apt-get update \
git \
nano \
vim-tiny \
ca-certificates \
&& apt-get auto-remove -y \
&& apt-get clean -y \
&& chsh -s $(which bash) bun \
Expand Down
53 changes: 53 additions & 0 deletions libs/as-sdk-integration-tests/assembly/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
Bytes,
OracleProgram,
Process,
decodeHex,
secp256k1Verify,
} from "../../as-sdk/assembly";

export class TestSecp256k1VerifyValid extends OracleProgram {
execution(): void {
const message = Bytes.fromString("Hello, SEDA!");
const signature = Bytes.fromBytes(
decodeHex(
"58376cc76f4d4959b0adf8070ecf0079db889915a75370f6e39a8451ba5be0c35f091fa4d2fda3ced5b6e6acd1dbb4a45f2c6a1e643622ee4cf8b802b373d38f",
),
);
const publicKey = Bytes.fromBytes(
decodeHex(
"02a2bebd272aa28e410cc74cef28e5ce74a9ffc94caf817ed9bd23b01ce2068c7b",
),
);
const isValidSignature = secp256k1Verify(message, signature, publicKey);

if (isValidSignature) {
Process.success(Bytes.fromString("valid secp256k1 signature"));
} else {
Process.error(Bytes.fromString("invalid secp256k1 signature"));
}
}
}

export class TestSecp256k1VerifyInvalid extends OracleProgram {
execution(): void {
const message = Bytes.fromString("Hello, this is an invalid message!");
const signature = Bytes.fromBytes(
decodeHex(
"58376cc76f4d4959b0adf8070ecf0079db889915a75370f6e39a8451ba5be0c35f091fa4d2fda3ced5b6e6acd1dbb4a45f2c6a1e643622ee4cf8b802b373d38f",
),
);
const publicKey = Bytes.fromBytes(
decodeHex(
"02a2bebd272aa28e410cc74cef28e5ce74a9ffc94caf817ed9bd23b01ce2068c7b",
),
);
const isValidSignature = secp256k1Verify(message, signature, publicKey);

if (isValidSignature) {
Process.success(Bytes.fromString("valid secp256k1 signature"));
} else {
Process.error(Bytes.fromString("invalid secp256k1 signature"));
}
}
}
5 changes: 5 additions & 0 deletions libs/as-sdk-integration-tests/assembly/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Bytes, Process } from "../../as-sdk/assembly/index";
import { TestSecp256k1VerifyInvalid, TestSecp256k1VerifyValid } from "./crypto";
import {
TestHttpRejection,
TestHttpSuccess,
Expand Down Expand Up @@ -26,6 +27,10 @@ if (args === "testHttpRejection") {
new TestTallyVmRevealsFiltered().run();
} else if (args === "testProxyHttpFetch") {
new TestProxyHttpFetch().run();
} else if (args === "testSecp256k1VerifyValid") {
new TestSecp256k1VerifyValid().run();
} else if (args === "testSecp256k1VerifyInvalid") {
new TestSecp256k1VerifyInvalid().run();
}

Process.error(Bytes.fromString("No argument given"));
34 changes: 34 additions & 0 deletions libs/as-sdk-integration-tests/src/crypto.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { beforeEach, describe, expect, it, mock } from "bun:test";
import { readFile } from "node:fs/promises";
import { executeDrWasm } from "@seda/dev-tools";
import { Response } from "node-fetch";
Copy link
Member

Choose a reason for hiding this comment

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

We're not using this import right?


const mockHttpFetch = mock();

describe("Crypto", () => {
it("Test valid Secp256k1 signature", async () => {
const wasmBinary = await readFile(
"dist/libs/as-sdk-integration-tests/debug.wasm",
);

const result = await executeDrWasm(
wasmBinary,
Buffer.from("testSecp256k1VerifyValid"),
);

expect(result.resultAsString).toEqual("valid secp256k1 signature");
});

it("Test invalid Secp256k1 signature", async () => {
const wasmBinary = await readFile(
"dist/libs/as-sdk-integration-tests/debug.wasm",
);

const result = await executeDrWasm(
wasmBinary,
Buffer.from("testSecp256k1VerifyInvalid"),
);

expect(result.resultAsString).toEqual("invalid secp256k1 signature");
});
});
9 changes: 9 additions & 0 deletions libs/as-sdk/assembly/bindings/seda_v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,12 @@ export declare function execution_result(
result: usize,
result_length: u32,
): void;

export declare function secp256k1_verify(
message: usize,
message_length: u32,
signature: usize,
signature_length: u32,
public_key: usize,
public_key_length: u32,
): u8;
40 changes: 40 additions & 0 deletions libs/as-sdk/assembly/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { call_result_write, secp256k1_verify } from "./bindings/seda_v1";
import { Bytes } from "./bytes";
import { PromiseStatus } from "./promise";

export function secp256k1Verify(
message: Bytes,
signature: Bytes,
publicKey: Bytes,
): bool {
const messageBuffer = message.buffer();
const messagePtr = changetype<usize>(messageBuffer);

const signatureBuffer = signature.buffer();
const signaturePtr = changetype<usize>(signatureBuffer);

const publicKeyBuffer = publicKey.buffer();
const publicKeyPtr = changetype<usize>(publicKeyBuffer);

// Call secp256k1_verify and get the response length
const responseLength = secp256k1_verify(
messagePtr,
messageBuffer.byteLength,
signaturePtr,
signatureBuffer.byteLength,
publicKeyPtr,
publicKeyBuffer.byteLength,
);

// Allocate an ArrayBuffer for the response
const responseBuffer = new ArrayBuffer(responseLength);
// Get the pointer to the response buffer
const responseBufferPtr = changetype<usize>(responseBuffer);
// Write the result to the allocated buffer
call_result_write(responseBufferPtr, responseLength);
// Convert the response buffer into a Uint8Array
const responseArray = Uint8Array.wrap(responseBuffer);

// Return true if the response is [1] (valid)
return responseArray[0] === 1;
}
1 change: 1 addition & 0 deletions libs/as-sdk/assembly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export { Console } from "./console";
export { Bytes } from "./bytes";
export { decodeHex, encodeHex } from "./hex";
export { OracleProgram } from "./oracle-program";
export { secp256k1Verify } from "./crypto";
11 changes: 11 additions & 0 deletions libs/vm/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@
"generateExportsField": true
},
"outputs": ["{options.outputPath}"]
},
"test": {
"executor": "nx:run-commands",
"options": {
"command": "bun test libs/vm/"
},
"dependsOn": [
{
"target": "build"
}
]
}
}
}
20 changes: 20 additions & 0 deletions libs/vm/src/services/crypto.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from "bun:test";
import { secp256k1Verify } from "./crypto";

describe("Crypto", () => {
it("should Secp256k1 verify", async () => {
const message = Buffer.from("Hello, SEDA!");
const signature = Buffer.from(
"58376cc76f4d4959b0adf8070ecf0079db889915a75370f6e39a8451ba5be0c35f091fa4d2fda3ced5b6e6acd1dbb4a45f2c6a1e643622ee4cf8b802b373d38f",
"hex",
);
const publicKey = Buffer.from(
"02a2bebd272aa28e410cc74cef28e5ce74a9ffc94caf817ed9bd23b01ce2068c7b",
"hex",
);
const result = secp256k1Verify(message, signature, publicKey);

// Check if the result is a Uint8Array and has the value [1]
expect(result).toEqual(new Uint8Array([1]));
});
});
25 changes: 25 additions & 0 deletions libs/vm/src/services/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { keccak_256 } from "@noble/hashes/sha3";
import * as Secp256k1 from "@noble/secp256k1";

export function keccak256(input: Buffer): Buffer {
const hasher = keccak_256.create();
hasher.update(input);

return Buffer.from(hasher.digest());
}

export function secp256k1Verify(
message: Buffer,
signature: Buffer,
publicKey: Buffer,
): Uint8Array {
const signedMessage = keccak256(message);
const isValidSignature = Secp256k1.verify(
signature,
signedMessage,
publicKey,
);

// Return 1 as Uint8Array if valid, 0 if not
return new Uint8Array([isValidSignature ? 1 : 0]);
}
8 changes: 0 additions & 8 deletions libs/vm/src/services/keccak256.ts

This file was deleted.

53 changes: 52 additions & 1 deletion libs/vm/src/vm-imports.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import * as Secp256k1 from "@noble/secp256k1";
import { Maybe } from "true-myth";
import { keccak256 } from "./services/keccak256";
import { keccak256, secp256k1Verify } from "./services/crypto";
import { trySync } from "./services/try";
import { type HttpFetchAction, HttpFetchResponse } from "./types/vm-actions";
import { PromiseStatus } from "./types/vm-promise";
import { WorkerToHost } from "./worker-host-communication.js";

export default class VmImports {
memory?: WebAssembly.Memory;
workerToHost: WorkerToHost;
// Used for async calls (for knowing the length of the buffer)
callResult: Uint8Array = new Uint8Array();
// Execution result
result: Uint8Array = new Uint8Array();
usedPublicKeys: string[] = [];
processId: string;
Expand Down Expand Up @@ -113,6 +116,53 @@ export default class VmImports {
}
}

secp256k1Verify(
messagePtr: number,
messageLength: number,
signaturePtr: number,
signatureLength: number,
publicKeyPtr: number,
publicKeyLength: number,
) {
const message = Buffer.from(
new Uint8Array(
this.memory?.buffer.slice(messagePtr, messagePtr + messageLength) ?? [],
),
);
const signature = Buffer.from(
new Uint8Array(
this.memory?.buffer.slice(
signaturePtr,
signaturePtr + signatureLength,
) ?? [],
),
);
const publicKey = Buffer.from(
new Uint8Array(
this.memory?.buffer.slice(
publicKeyPtr,
publicKeyPtr + publicKeyLength,
) ?? [],
),
);

const result = trySync(() =>
secp256k1Verify(message, signature, publicKey),
);

if (result.isErr) {
console.error(
`[${this.processId}] - @secp256k1Verify: ${message}`,
result.error,
);
this.callResult = new Uint8Array();
return 0;
}

this.callResult = result.value;
return this.callResult.length;
}

callResultWrite(ptr: number, length: number) {
try {
const memory = new Uint8Array(this.memory?.buffer ?? []);
Expand All @@ -137,6 +187,7 @@ export default class VmImports {
// TODO: Should be this.proxyHttpFetch but since thats broken for now we will use httpFetch
proxy_http_fetch: this.httpFetch.bind(this),
http_fetch: this.httpFetch.bind(this),
secp256k1_verify: this.secp256k1Verify.bind(this),
call_result_write: this.callResultWrite.bind(this),
execution_result: this.executionResult.bind(this),
},
Expand Down
Loading