Skip to content

Commit

Permalink
Merge pull request #96 from fleet-sdk/add-signing
Browse files Browse the repository at this point in the history
add `proveDLog` signing
  • Loading branch information
arobsn authored Jun 1, 2024
2 parents b99c12c + 21890a7 commit 65d10f0
Show file tree
Hide file tree
Showing 27 changed files with 944 additions and 110 deletions.
5 changes: 5 additions & 0 deletions .changeset/angry-bobcats-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/wallet": minor
---

Add `ProveDLog` transaction signing and verifying
5 changes: 5 additions & 0 deletions .changeset/few-geese-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/crypto": minor
---

Add `bigintBE` coder
5 changes: 5 additions & 0 deletions .changeset/funny-seals-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/crypto": minor
---

Add `validateEcPoint()` function
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:

- uses: pnpm/action-setup@v2
with:
version: 8
version: 9

- uses: actions/setup-node@v3
with:
Expand All @@ -38,7 +38,7 @@ jobs:

- uses: pnpm/action-setup@v2
with:
version: 8
version: 9

- uses: actions/setup-node@v3
with:
Expand All @@ -61,7 +61,7 @@ jobs:

- uses: pnpm/action-setup@v2
with:
version: 8
version: 9

- name: Build and test using Node.js v${{ matrix.node-version }}
uses: actions/setup-node@v3
Expand All @@ -86,7 +86,7 @@ jobs:

- uses: pnpm/action-setup@v2
with:
version: 8
version: 9

- name: Build and test using bun ${{ matrix.bun-version }}
uses: oven-sh/setup-bun@v1
Expand All @@ -108,7 +108,7 @@ jobs:

- uses: pnpm/action-setup@v2
with:
version: 8
version: 9

- name: Build using Node.js v20
uses: actions/setup-node@v3
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

- uses: pnpm/action-setup@v2
with:
version: 8
version: 9

- name: Collect coverage
uses: actions/setup-node@v3
Expand All @@ -30,4 +30,5 @@ jobs:
if: success() || failure()
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/coverage-final.json
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:

- uses: pnpm/action-setup@v2
with:
version: 8
version: 9

- name: Setup Node.js 20.x
uses: actions/setup-node@v3
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"private": "true",
"packageManager": "pnpm@8.1.1",
"repository": "fleet-sdk/fleet",
"license": "MIT",
"scripts": {
Expand All @@ -19,7 +18,9 @@
"test:unit-edge": "vitest run --no-coverage --environment=edge-runtime",
"cov:check": "vitest run --coverage",
"cov:open": "vitest run --coverage ; open-cli coverage/index.html",
"bench": "vitest bench"
"bench": "vitest bench",
"snapshot:version": "changeset version --snapshot snapshot",
"snapshot:publish": "pnpm clear && pnpm -r build && changeset publish --no-git-tag --tag snapshot"
},
"devDependencies": {
"@changesets/cli": "^2.26.2",
Expand Down
28 changes: 4 additions & 24 deletions packages/core/src/models/ergoAddress.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AddressType, Base58String, HexString, isEmpty, Network } from "@fleet-sdk/common";
import { AddressType, Base58String, HexString, Network } from "@fleet-sdk/common";
import { areEqual, concatBytes, endsWith, first, isDefined, startsWith } from "@fleet-sdk/common";
import { base58, blake2b256, hex } from "@fleet-sdk/crypto";
import { base58, blake2b256, hex, validateEcPoint } from "@fleet-sdk/crypto";
import { InvalidAddress } from "../errors/invalidAddress";

const CHECKSUM_LENGTH = 4;
Expand Down Expand Up @@ -44,24 +44,6 @@ function _getErgoTreeType(ergoTree: Uint8Array): AddressType {
}
}

/**
* Validates a compressed Elliptic Curve point. Every non-infinity
* compressed point must contain 33 bytes, and the first byte must
* be equal to `0x02` or `0x03`, as described above:
*
* `0x02` = compressed, positive Y coordinate.
* `0x03` = compressed, negative Y coordinate.
*
* @param pointBytes ECPoint bytes
*/
function _validateCompressedEcPoint(pointBytes: Uint8Array) {
if (isEmpty(pointBytes) || pointBytes.length !== 33) {
return false;
}

return pointBytes[0] === 0x02 || pointBytes[0] === 0x03;
}

/**
* Ergo address model
*
Expand Down Expand Up @@ -123,7 +105,7 @@ export class ErgoAddress {
*/
public static fromPublicKey(publicKey: HexString | Uint8Array, network?: Network): ErgoAddress {
const bytes = _ensureBytes(publicKey);
if (!_validateCompressedEcPoint(bytes)) {
if (!validateEcPoint(bytes)) {
throw new Error("The Public Key is invalid.");
}

Expand Down Expand Up @@ -182,9 +164,7 @@ export class ErgoAddress {
if (_getEncodedAddressType(bytes) === AddressType.P2PK) {
const pk = bytes.subarray(1, bytes.length - CHECKSUM_LENGTH);

if (!_validateCompressedEcPoint(pk)) {
return false;
}
if (!validateEcPoint(pk)) return false;
}

const script = bytes.subarray(0, bytes.length - CHECKSUM_LENGTH);
Expand Down
4 changes: 2 additions & 2 deletions packages/crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
},
"dependencies": {
"@fleet-sdk/common": "workspace:^",
"@noble/hashes": "^1.3.2",
"@scure/base": "^1.1.3"
"@noble/hashes": "^1.4.0",
"@scure/base": "^1.1.6"
},
"files": [
"dist",
Expand Down
19 changes: 19 additions & 0 deletions packages/crypto/src/coders/bigintBE.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { bigintBE } from "./bigintBE";

describe("Big Endian BigInt coder", () => {
it("Should encode Uint8Array to bigint", () => {
expect(bigintBE.encode(Uint8Array.from([1]))).to.be.equal(1n);
expect(bigintBE.encode(Uint8Array.from([0]))).to.be.equal(0n);
expect(bigintBE.encode(Uint8Array.from([]))).to.be.equal(0n);
expect(bigintBE.encode(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]))).to.be.equal(3735928559n);
});

it("Should decode bigint to Uint8Array", () => {
expect(bigintBE.decode(1n)).to.be.deep.equal(Uint8Array.from([1]));
expect(bigintBE.decode(0n)).to.be.deep.equal(Uint8Array.from([0]));
expect(bigintBE.decode(3735928559n)).to.be.deep.equal(
Uint8Array.from([0xde, 0xad, 0xbe, 0xef])
);
});
});
23 changes: 23 additions & 0 deletions packages/crypto/src/coders/bigintBE.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { hex } from "@scure/base";
import { Coder } from "../types";

/**
* A coder for Big Endian `BigInt` <> `Uint8Array` conversion..
*/
export const bigintBE: Coder<Uint8Array, bigint> = {
/**
* Encode a `Uint8Array` to a `BigInt`.
*/
encode(data) {
const hexInput = hex.encode(data);
return BigInt(hexInput == "" ? "0" : "0x" + hexInput);
},

/**
* Decode a `BigInt` to a `Uint8Array`.
*/
decode(data) {
const hexData = data.toString(16);
return hex.decode(hexData.length % 2 ? "0" + hexData : hexData);
}
};
1 change: 1 addition & 0 deletions packages/crypto/src/coders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export const base64 = base64Coder as BytesCoder;

export { hex } from "./hex";
export { utf8 } from "./utf8";
export { bigintBE } from "./bigintBE";
75 changes: 75 additions & 0 deletions packages/crypto/src/ecpoint.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { describe, expect, test } from "vitest";
import { hex } from "./coders/hex";
import { validateEcPoint } from "./ecpoint";

const testVectors = [
{
name: "valid compressed, positive Y coordinate",
point: hex.decode("0289b72d85b8a72b0a53960bafddadb74a149c6c1785d2bb46c244401e61d80b4d"),
valid: true
},
{
name: "valid compressed, negative Y coordinate",
point: hex.decode("0376b32d0bb20f15004649946db5679adce657bef77c487add608115ce8050b16e"),
valid: true
},
{
name: "invalid compressed",
point: hex.decode("0476b32d0bb20f15004649946db5679adce657bef77c487add608115ce8050b16e"),
valid: false
},
{
name: "invalid compressed",
point: hex.decode("0576b32d0bb20f15004649946db5679adce657bef77c487add608115ce8050b16e"),
valid: false
},
{
name: "valid uncompressed",
point: hex.decode(
"04784781bfad60e7da448912726c463736acdbc1864cec9ea3bfb22b3e974aa2bc579b466b40775367863186a7d9557be1b0f99ea55cff204019eba6539abead7b"
),
valid: true
},
{
name: "valid uncompressed",
point: hex.decode(
"04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4"
),
valid: true
},
{
name: "invalid uncompressed",
point: hex.decode(
"03b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4"
),
valid: false
},
{
name: "length > 65",
point: hex.decode(
"04784781bfad60e7da448912726c463736acdbc1864cec9ea3bfb22b3e974aa2bc579b466b40775367863186a7d9557be1b0f99ea55cff204019eba6539abead7b9abead7b"
),
valid: false
},
{
name: "length > 33 and < 65",
point: hex.decode("0289b72d85b8a72b0a53960bafddadb74a149c6c1785d2bb46c244401e61d80b4d4e"),
valid: false
},
{
name: "length < 33",
point: hex.decode("020102030405060708"),
valid: false
},
{ name: "length < 33, valid positive head", point: hex.decode("02"), valid: false },
{ name: "length < 33, valid negative head", point: hex.decode("03"), valid: false },
{ name: "length < 33, valid negative head", point: hex.decode("04"), valid: false },
{ name: "empty", point: hex.decode(""), valid: false },
{ name: "undefined", point: undefined as unknown as Uint8Array, valid: false }
];

describe("Compressed ECPoint validation", () => {
test.each(testVectors)("Should validate compressed ECPoint: $name", (tv) => {
expect(validateEcPoint(tv.point)).to.be.equal(tv.valid);
});
});
39 changes: 39 additions & 0 deletions packages/crypto/src/ecpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { isEmpty } from "@fleet-sdk/common";

/**
* EC point type
*/
enum EcPointType {
/**
* Compressed, positive Y coordinate
*/
Compressed = 0x02,
/**
* Compressed, negative Y coordinate
*/
CompressedOdd = 0x03,
/**
* Uncompressed
*/
Uncompressed = 0x04
}

/**
* Validate Elliptic Curve point
*
* @param pointBytes EC point bytes
* @returns True if the point is valid
*/
export function validateEcPoint(pointBytes: Uint8Array) {
if (isEmpty(pointBytes)) return false;

switch (pointBytes[0]) {
case EcPointType.Compressed:
case EcPointType.CompressedOdd:
return pointBytes.length === 33;
case EcPointType.Uncompressed:
return pointBytes.length === 65;
default:
return false;
}
}
1 change: 1 addition & 0 deletions packages/crypto/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export const randomBytes = nobleRandomBytes as (bytesLength?: number) => Uint8Ar
export * from "./hashes";
export * from "./types";
export * from "./coders";
export * from "./ecpoint";
4 changes: 2 additions & 2 deletions packages/crypto/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export type BytesInput = Uint8Array | string;

export interface Coder<F, T> {
encode(from: F): T;
decode(to: T): F;
encode(decoded: F): T;
decode(encoded: T): F;
}

export interface BytesCoder extends Coder<Uint8Array, string> {
Expand Down
8 changes: 6 additions & 2 deletions packages/wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@
},
"dependencies": {
"@fleet-sdk/core": "workspace:^",
"@scure/bip32": "^1.3.2",
"@scure/bip39": "^1.2.1"
"@fleet-sdk/common": "workspace:^",
"@fleet-sdk/serializer": "workspace:^",
"@fleet-sdk/crypto": "workspace:^",
"@scure/bip32": "^1.4.0",
"@scure/bip39": "^1.3.0",
"@noble/curves": "^1.4.0"
},
"engines": {
"node": ">=14"
Expand Down
Loading

0 comments on commit 65d10f0

Please sign in to comment.