Skip to content

Commit

Permalink
feat: restore crypto-signature-ecdsa package (#720)
Browse files Browse the repository at this point in the history
* add back crypto-signature-ecdsa

* signature recovery test

* fix
  • Loading branch information
oXtxNt9U authored Oct 1, 2024
1 parent 8510f7e commit 4546dea
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ jobs:
run: cd packages/crypto-messages && pnpm run test
- name: Test crypto-signature-bls12-381
run: cd packages/crypto-signature-bls12-381 && pnpm run test
- name: Test crypto-signature-ecdsa
run: cd packages/crypto-signature-ecdsa && pnpm run test
- name: Test crypto-signature-schnorr
run: cd packages/crypto-signature-schnorr && pnpm run test
- name: Test crypto-transaction
Expand Down
19 changes: 19 additions & 0 deletions packages/crypto-signature-ecdsa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Mainsail - Crypto signature ECDSA

![banner](https://raw.githubusercontent.com/ArkEcosystem/mainsail/main/banner.jpeg)

## Documentation

You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://ark.dev/docs/mainsail).

## Security

If you discover a security vulnerability within this package, please send an e-mail to [[email protected]](mailto:[email protected]). All security vulnerabilities will be promptly addressed.

## Credits

This project exists thanks to all the people who [contribute](https://github.com/ArkEcosystem/mainsail/graphs/contributors).

## License

[GPL-3.0-only](https://github.com/ArkEcosystem/mainsail/blob/main/LICENSE) © [ARK Ecosystem](https://ark.io)
37 changes: 37 additions & 0 deletions packages/crypto-signature-ecdsa/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@mainsail/crypto-signature-ecdsa",
"version": "0.0.1-alpha.20",
"description": "Elliptic Curve Cryptography (ECDSA) signatures for the Mainsail blockchain",
"license": "GPL-3.0-only",
"contributors": [],
"type": "module",
"main": "distribution/index.js",
"types": "distribution/index.d.ts",
"files": [
"distribution"
],
"scripts": {
"build": "pnpm run clean && tsc",
"build:watch": "pnpm run clean && tsc -w",
"clean": "del distribution",
"release": "pnpm publish --access public",
"test": "pnpm run uvu source .test.ts",
"test:coverage": "c8 pnpm run test",
"test:coverage:html": "c8 -r html --all pnpm run test",
"test:file": "pnpm run uvu source",
"uvu": "tsx --tsconfig ../../tsconfig.test.json ./node_modules/uvu/bin.js"
},
"dependencies": {
"@mainsail/container": "workspace:*",
"@mainsail/contracts": "workspace:*",
"@mainsail/kernel": "workspace:*",
"@mainsail/utils": "workspace:*",
"bcrypto": "5.5.2"
},
"devDependencies": {
"uvu": "^0.5.6"
},
"engines": {
"node": ">=20.x"
}
}
30 changes: 30 additions & 0 deletions packages/crypto-signature-ecdsa/source/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Selectors } from "@mainsail/container";
import { Identifiers } from "@mainsail/contracts";
import { Providers } from "@mainsail/kernel";
import { ByteBuffer } from "@mainsail/utils";

import { Signature } from "./signature.js";

export class ServiceProvider extends Providers.ServiceProvider {
public async register(): Promise<void> {
this.app
.bind(Identifiers.Cryptography.Signature.Size)
.toFunction((buffer: ByteBuffer) => {
buffer.mark();
buffer.skip(1);

const lengthHex: string = buffer.readBytes(1).toString("hex");

buffer.reset();

return Number.parseInt(lengthHex, 16) + 2;
})
.when(Selectors.anyAncestorOrTargetTaggedFirst("type", "wallet"));

this.app
.bind(Identifiers.Cryptography.Signature.Instance)
.to(Signature)
.inSingletonScope()
.when(Selectors.anyAncestorOrTargetTaggedFirst("type", "wallet"));
}
}
50 changes: 50 additions & 0 deletions packages/crypto-signature-ecdsa/source/signature.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe } from "../../test-framework/source";
import { Signature } from "./signature";
import { secp256k1 } from "bcrypto";

describe("Signature", ({ assert, it }) => {
it("should sign and verify", async () => {
assert.true(
await new Signature().verify(
Buffer.from(
await new Signature().sign(
Buffer.from("64726e3da8", "hex"),
Buffer.from("814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2", "hex"),
),
"hex",
),
Buffer.from("64726e3da8", "hex"),
Buffer.from("03e84093c072af70004a38dd95e34def119d2348d5261228175d032e5f2070e19f", "hex"),
),
);
});

it("should sign recoverable and return r,s,v", async () => {
const privateKey = Buffer.from("814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2", "hex");
const message = Buffer.from("64726e3da8", "hex");

const [signature, recoverId] = secp256k1.signRecoverable(message, privateKey);

const r = signature.subarray(0, 32);
const s = signature.subarray(32, 64);

const sig = {
r: r.toString("hex"),
s: s.toString("hex"),
v: recoverId + 27,
};

assert.equal(sig, {
r: "66f1c6d9fe13834f6e348aae40426060339ed8cba7d9b2f105c8220be095877c",
s: "1368fffd8294f1e22086703d33511fc8bb25231d6e9dc64d6449035003184bdd",
v: 28,
});

const signatureBuffer = Buffer.concat([Buffer.from(sig.r, "hex"), Buffer.from(sig.s, "hex")]);
const publicKey = secp256k1.recover(message, signatureBuffer, sig.v - 27, true);
assert.equal(
publicKey,
Buffer.from("03e84093c072af70004a38dd95e34def119d2348d5261228175d032e5f2070e19f", "hex"),
);
});
});
64 changes: 64 additions & 0 deletions packages/crypto-signature-ecdsa/source/signature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { inject, injectable } from "@mainsail/container";
import { Contracts, Exceptions, Identifiers } from "@mainsail/contracts";
import { ByteBuffer } from "@mainsail/utils";
import { secp256k1 } from "bcrypto";

@injectable()
export class Signature implements Contracts.Crypto.Signature {
@inject(Identifiers.Cryptography.Signature.Size)
private readonly signatureSize!: Function;

public async sign(message: Buffer, privateKey: Buffer): Promise<string> {
return secp256k1.signatureExport(secp256k1.sign(message, privateKey)).toString("hex");
}

public async verify(signature: Buffer, message: Buffer, publicKey: Buffer): Promise<boolean> {
const signatureRS = secp256k1.signatureImport(signature);

if (!secp256k1.isLowS(signatureRS)) {
return false;
}

// check that global signature length matches R and S length, see DER format :
// <header byte><signature length><integer marker><R length><R><integer marker><S length><S>
const signatureLength = signature.readUInt8(1);
const rLength = signature.readUInt8(3);
const sLength = signature.readUInt8(4 + rLength + 1);
if (
signature.length !== 4 + rLength + 2 + sLength ||
signatureLength !== 2 + rLength + 2 + sLength ||
signatureLength > 127
) {
return false;
}

// check that first byte is positive, if it is then the whole R / S will be positive as required
const rFirstByte = signature.readInt8(4);
const sFirstByte = signature.readInt8(4 + rLength + 2);
if (rFirstByte < 0 || sFirstByte < 0 || rFirstByte > 127 || sFirstByte > 127) {
return false;
}

// if first byte is zero it is to make R/S positive, so second byte should be negative
if (
(rFirstByte === 0 && signature.readInt8(4 + 1) >= 0) ||
(sFirstByte === 0 && signature.readInt8(4 + rLength + 2 + 1) >= 0)
) {
return false;
}

return secp256k1.verify(message, signatureRS, publicKey);
}

public serialize(buffer: ByteBuffer, signature: string): void {
buffer.writeBytes(Buffer.from(signature, "hex"));
}

public deserialize(buffer: ByteBuffer): Buffer {
return buffer.readBytes(this.signatureSize(buffer));
}

public async aggregate(signatures: Buffer[]): Promise<string> {
throw new Exceptions.NotImplemented(this.constructor.name, "aggregate");
}
}
7 changes: 7 additions & 0 deletions packages/crypto-signature-ecdsa/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "distribution"
},
"include": ["source/**/**.ts"]
}
22 changes: 22 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4546dea

Please sign in to comment.