voltaire-effect 0.2.25 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +184 -12
- package/dist/{index-BCOuszKZ.d.ts → index-3UKSP3cd.d.ts} +74 -31
- package/dist/index.d.ts +107 -106
- package/dist/index.js +1770 -1224
- package/dist/native/index.d.ts +7 -6
- package/dist/native/index.js +1795 -1249
- package/dist/primitives/index.d.ts +1 -1
- package/dist/primitives/index.js +37 -4
- package/dist/services/index.d.ts +1031 -519
- package/dist/services/index.js +1276 -762
- package/package.json +11 -11
- package/src/primitives/Abi/encodeFunctionData.bench.ts +79 -0
- package/src/primitives/Address/Hex.ts +15 -1
- package/src/primitives/Block/BlockSchema.ts +1 -1
- package/src/primitives/PrivateKey/Bytes.ts +35 -0
- package/src/primitives/PrivateKey/Hex.ts +49 -1
- package/src/primitives/PrivateKey/PrivateKey.error.test.ts +167 -0
- package/src/primitives/PrivateKey/PrivateKey.test.ts +79 -0
- package/src/primitives/PrivateKey/index.ts +24 -32
- package/src/services/Contract/ContractsService.test.ts +351 -0
- package/src/services/Contract/ContractsService.ts +206 -0
- package/src/services/Contract/index.ts +54 -13
- package/src/services/Provider/ExecutionPlanProvider.test.ts +123 -0
- package/src/services/Provider/ExecutionPlanProvider.ts +151 -0
- package/src/services/Provider/index.ts +19 -0
- package/src/services/Provider/providers.ts +224 -0
- package/src/services/RpcBatch/RpcResolver.ts +4 -4
- package/src/services/RpcBatch/index.ts +9 -9
- package/src/services/Transport/BrowserTransport.ts +4 -4
- package/src/services/Transport/TransportInterceptor.ts +4 -4
- package/src/services/index.ts +20 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "voltaire-effect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Effect-TS integration for Voltaire Ethereum primitives library",
|
|
5
5
|
"author": "TEVM",
|
|
6
6
|
"license": "MIT",
|
|
@@ -78,20 +78,20 @@
|
|
|
78
78
|
},
|
|
79
79
|
"sideEffects": false,
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"effect": "^
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
"dependencies": {
|
|
85
|
-
"@effect/platform": "^0.77.0"
|
|
81
|
+
"@effect/platform": "^0.94.0",
|
|
82
|
+
"effect": "^3.19.0",
|
|
83
|
+
"@tevm/voltaire": "0.2.28"
|
|
86
84
|
},
|
|
87
85
|
"optionalDependencies": {
|
|
88
|
-
"@effect/platform-browser": "^0.
|
|
89
|
-
"@effect/platform-bun": "^0.
|
|
90
|
-
"@effect/platform-node": "^0.
|
|
86
|
+
"@effect/platform-browser": "^0.74.0",
|
|
87
|
+
"@effect/platform-bun": "^0.87.0",
|
|
88
|
+
"@effect/platform-node": "^0.104.0"
|
|
91
89
|
},
|
|
92
90
|
"devDependencies": {
|
|
93
|
-
"@effect/platform
|
|
94
|
-
"@effect/
|
|
91
|
+
"@effect/platform": "^0.94.0",
|
|
92
|
+
"@effect/platform-node": "^0.104.0",
|
|
93
|
+
"@effect/vitest": "^0.27.0",
|
|
94
|
+
"effect": "^3.19.0",
|
|
95
95
|
"astro": "^5.0.0",
|
|
96
96
|
"tsup": "^8.4.0",
|
|
97
97
|
"typescript": "^5.9.3",
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark: voltaire vs voltaire-effect vs viem encodeFunctionData
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Effect } from "effect";
|
|
6
|
+
import { bench, run } from "mitata";
|
|
7
|
+
import { encodeFunctionData as viemEncodeFunctionData } from "viem";
|
|
8
|
+
import * as Abi from "@tevm/voltaire/Abi";
|
|
9
|
+
import { encodeFunctionData } from "./encodeFunctionData.js";
|
|
10
|
+
|
|
11
|
+
const erc20Abi = [
|
|
12
|
+
{
|
|
13
|
+
type: "function",
|
|
14
|
+
name: "transfer",
|
|
15
|
+
stateMutability: "nonpayable",
|
|
16
|
+
inputs: [
|
|
17
|
+
{ type: "address", name: "to" },
|
|
18
|
+
{ type: "uint256", name: "amount" },
|
|
19
|
+
],
|
|
20
|
+
outputs: [{ type: "bool", name: "" }],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: "function",
|
|
24
|
+
name: "balanceOf",
|
|
25
|
+
stateMutability: "view",
|
|
26
|
+
inputs: [{ type: "address", name: "account" }],
|
|
27
|
+
outputs: [{ type: "uint256", name: "" }],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: "function",
|
|
31
|
+
name: "approve",
|
|
32
|
+
stateMutability: "nonpayable",
|
|
33
|
+
inputs: [
|
|
34
|
+
{ type: "address", name: "spender" },
|
|
35
|
+
{ type: "uint256", name: "amount" },
|
|
36
|
+
],
|
|
37
|
+
outputs: [{ type: "bool", name: "" }],
|
|
38
|
+
},
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
const recipient = "0x742d35cc6634c0532925a3b844bc9e7595f251e3";
|
|
42
|
+
const amount = 1000000000000000000n;
|
|
43
|
+
|
|
44
|
+
bench("encodeFunctionData - viem", () => {
|
|
45
|
+
viemEncodeFunctionData({
|
|
46
|
+
abi: erc20Abi,
|
|
47
|
+
functionName: "transfer",
|
|
48
|
+
args: [recipient, amount],
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
bench("encodeFunctionData - voltaire", () => {
|
|
53
|
+
Abi.encodeFunction(erc20Abi, "transfer", [recipient, amount]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
bench("encodeFunctionData - voltaire-effect (sync)", () => {
|
|
57
|
+
Effect.runSync(encodeFunctionData(erc20Abi, "transfer", [recipient, amount]));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await run();
|
|
61
|
+
|
|
62
|
+
// Also test balanceOf (single param)
|
|
63
|
+
bench("balanceOf - viem", () => {
|
|
64
|
+
viemEncodeFunctionData({
|
|
65
|
+
abi: erc20Abi,
|
|
66
|
+
functionName: "balanceOf",
|
|
67
|
+
args: [recipient],
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
bench("balanceOf - voltaire", () => {
|
|
72
|
+
Abi.encodeFunction(erc20Abi, "balanceOf", [recipient]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
bench("balanceOf - voltaire-effect (sync)", () => {
|
|
76
|
+
Effect.runSync(encodeFunctionData(erc20Abi, "balanceOf", [recipient]));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await run();
|
|
@@ -11,6 +11,12 @@ import * as ParseResult from "effect/ParseResult";
|
|
|
11
11
|
import * as S from "effect/Schema";
|
|
12
12
|
import { AddressTypeSchema } from "./AddressSchema.js";
|
|
13
13
|
|
|
14
|
+
// Pre-computed example addresses for schema annotations
|
|
15
|
+
const EXAMPLE_ADDRESSES: readonly [AddressType, AddressType] = [
|
|
16
|
+
Address("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"),
|
|
17
|
+
Address("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* Schema for Address encoded as a hex string.
|
|
16
22
|
*
|
|
@@ -53,4 +59,12 @@ export const Hex: S.Schema<AddressType, string> = S.transformOrFail(
|
|
|
53
59
|
return ParseResult.succeed(Address.toHex(addr));
|
|
54
60
|
},
|
|
55
61
|
},
|
|
56
|
-
).annotations({
|
|
62
|
+
).annotations({
|
|
63
|
+
identifier: "Address.Hex",
|
|
64
|
+
title: "Ethereum Address",
|
|
65
|
+
description:
|
|
66
|
+
"A 20-byte Ethereum address as a hex string. Accepts checksummed, lowercase, or uppercase.",
|
|
67
|
+
examples: EXAMPLE_ADDRESSES,
|
|
68
|
+
message: () =>
|
|
69
|
+
"Invalid Ethereum address: expected 40 hex characters with 0x prefix",
|
|
70
|
+
});
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { PrivateKey, type PrivateKeyType } from "@tevm/voltaire/PrivateKey";
|
|
27
|
+
import { Redacted } from "effect";
|
|
27
28
|
import * as ParseResult from "effect/ParseResult";
|
|
28
29
|
import * as S from "effect/Schema";
|
|
29
30
|
|
|
@@ -57,3 +58,37 @@ export const Bytes: S.Schema<PrivateKeyType, Uint8Array> = S.transformOrFail(
|
|
|
57
58
|
},
|
|
58
59
|
},
|
|
59
60
|
).annotations({ identifier: "PrivateKey.Bytes" });
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Schema for PrivateKey encoded as raw bytes, wrapped in Redacted.
|
|
64
|
+
*
|
|
65
|
+
* @description
|
|
66
|
+
* Transforms Uint8Array to Redacted<PrivateKeyType> for secure handling.
|
|
67
|
+
* The redacted wrapper prevents accidental logging of private keys.
|
|
68
|
+
* Use `Redacted.value()` to explicitly unwrap for cryptographic operations.
|
|
69
|
+
*
|
|
70
|
+
* @example Decoding
|
|
71
|
+
* ```typescript
|
|
72
|
+
* import * as PrivateKey from 'voltaire-effect/primitives/PrivateKey'
|
|
73
|
+
* import { Redacted } from 'effect'
|
|
74
|
+
* import * as S from 'effect/Schema'
|
|
75
|
+
*
|
|
76
|
+
* const bytes = new Uint8Array(32).fill(1)
|
|
77
|
+
* const pk = S.decodeSync(PrivateKey.RedactedBytes)(bytes)
|
|
78
|
+
* console.log(pk) // Redacted(<redacted>)
|
|
79
|
+
*
|
|
80
|
+
* const unwrapped = Redacted.value(pk)
|
|
81
|
+
* // Use unwrapped for signing
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @since 0.1.0
|
|
85
|
+
*/
|
|
86
|
+
export const RedactedBytes: S.Schema<
|
|
87
|
+
Redacted.Redacted<PrivateKeyType>,
|
|
88
|
+
Uint8Array
|
|
89
|
+
> = Bytes.pipe(S.Redacted).annotations({
|
|
90
|
+
identifier: "PrivateKey.RedactedBytes",
|
|
91
|
+
title: "Private Key (Redacted)",
|
|
92
|
+
description:
|
|
93
|
+
"A 32-byte secp256k1 private key wrapped in Redacted to prevent accidental logging",
|
|
94
|
+
});
|
|
@@ -29,9 +29,15 @@ import {
|
|
|
29
29
|
PrivateKey,
|
|
30
30
|
type PrivateKeyType,
|
|
31
31
|
} from "@tevm/voltaire/PrivateKey";
|
|
32
|
+
import { Redacted } from "effect";
|
|
32
33
|
import * as ParseResult from "effect/ParseResult";
|
|
33
34
|
import * as S from "effect/Schema";
|
|
34
35
|
|
|
36
|
+
// Pre-computed example private key for schema annotations (test vector only)
|
|
37
|
+
const EXAMPLE_PRIVATE_KEY: PrivateKeyType = PrivateKey.from(
|
|
38
|
+
"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
39
|
+
);
|
|
40
|
+
|
|
35
41
|
const PrivateKeyTypeSchema = S.declare<PrivateKeyType>(
|
|
36
42
|
(u): u is PrivateKeyType => u instanceof Uint8Array && u.length === 32,
|
|
37
43
|
{ identifier: "PrivateKey" },
|
|
@@ -61,4 +67,46 @@ export const Hex: S.Schema<PrivateKeyType, string> = S.transformOrFail(
|
|
|
61
67
|
}
|
|
62
68
|
},
|
|
63
69
|
},
|
|
64
|
-
).annotations({
|
|
70
|
+
).annotations({
|
|
71
|
+
identifier: "PrivateKey.Hex",
|
|
72
|
+
title: "Private Key",
|
|
73
|
+
description:
|
|
74
|
+
"A 32-byte secp256k1 private key as a hex string. NEVER log or expose this value.",
|
|
75
|
+
examples: [EXAMPLE_PRIVATE_KEY],
|
|
76
|
+
message: () => "Invalid private key: expected 64 hex characters (32 bytes)",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Schema for PrivateKey encoded as hex string, wrapped in Redacted.
|
|
81
|
+
*
|
|
82
|
+
* @description
|
|
83
|
+
* Transforms hex strings to Redacted<PrivateKeyType> for secure handling.
|
|
84
|
+
* The redacted wrapper prevents accidental logging of private keys.
|
|
85
|
+
* Use `Redacted.value()` to explicitly unwrap for cryptographic operations.
|
|
86
|
+
*
|
|
87
|
+
* @example Decoding
|
|
88
|
+
* ```typescript
|
|
89
|
+
* import * as PrivateKey from 'voltaire-effect/primitives/PrivateKey'
|
|
90
|
+
* import { Redacted } from 'effect'
|
|
91
|
+
* import * as S from 'effect/Schema'
|
|
92
|
+
*
|
|
93
|
+
* const pk = S.decodeSync(PrivateKey.RedactedHex)('0x0123...')
|
|
94
|
+
* console.log(pk) // Redacted(<redacted>)
|
|
95
|
+
*
|
|
96
|
+
* const unwrapped = Redacted.value(pk)
|
|
97
|
+
* // Use unwrapped for signing
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* @since 0.1.0
|
|
101
|
+
*/
|
|
102
|
+
export const RedactedHex: S.Schema<
|
|
103
|
+
Redacted.Redacted<PrivateKeyType>,
|
|
104
|
+
string
|
|
105
|
+
> = Hex.pipe(S.Redacted).annotations({
|
|
106
|
+
identifier: "PrivateKey.RedactedHex",
|
|
107
|
+
title: "Private Key (Redacted)",
|
|
108
|
+
description:
|
|
109
|
+
"A 32-byte secp256k1 private key wrapped in Redacted to prevent accidental logging. NEVER log or expose the unwrapped value.",
|
|
110
|
+
examples: [Redacted.make(EXAMPLE_PRIVATE_KEY)],
|
|
111
|
+
message: () => "Invalid private key: expected 64 hex characters (32 bytes)",
|
|
112
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, expect, it } from "@effect/vitest";
|
|
2
|
+
import { TreeFormatter } from "effect/ParseResult";
|
|
3
|
+
import * as S from "effect/Schema";
|
|
4
|
+
import * as PrivateKey from "./index.js";
|
|
5
|
+
|
|
6
|
+
const formatError = TreeFormatter.formatErrorSync;
|
|
7
|
+
|
|
8
|
+
describe("PrivateKey parse error messages", () => {
|
|
9
|
+
describe("PrivateKey.Hex", () => {
|
|
10
|
+
it("shows helpful error for wrong length (too short)", () => {
|
|
11
|
+
const result = S.decodeUnknownEither(PrivateKey.Hex)("0x1234");
|
|
12
|
+
|
|
13
|
+
expect(result._tag).toBe("Left");
|
|
14
|
+
if (result._tag === "Left") {
|
|
15
|
+
const formatted = formatError(result.left);
|
|
16
|
+
|
|
17
|
+
// Should mention expected length
|
|
18
|
+
expect(formatted).toContain("64");
|
|
19
|
+
|
|
20
|
+
// Should NOT expose the attempted value
|
|
21
|
+
expect(formatted).not.toContain("1234");
|
|
22
|
+
|
|
23
|
+
expect(formatted).toMatchInlineSnapshot(`"Invalid private key: expected 64 hex characters (32 bytes)"`);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("shows helpful error for invalid hex characters", () => {
|
|
28
|
+
const result = S.decodeUnknownEither(PrivateKey.Hex)("0x" + "gg".repeat(32));
|
|
29
|
+
|
|
30
|
+
expect(result._tag).toBe("Left");
|
|
31
|
+
if (result._tag === "Left") {
|
|
32
|
+
const formatted = formatError(result.left);
|
|
33
|
+
|
|
34
|
+
// Should NOT expose the invalid input
|
|
35
|
+
expect(formatted).not.toContain("gggg");
|
|
36
|
+
|
|
37
|
+
expect(formatted).toMatchInlineSnapshot(`"Invalid private key: expected 64 hex characters (32 bytes)"`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("shows helpful error for wrong type (number)", () => {
|
|
42
|
+
const result = S.decodeUnknownEither(PrivateKey.Hex)(12345);
|
|
43
|
+
|
|
44
|
+
expect(result._tag).toBe("Left");
|
|
45
|
+
if (result._tag === "Left") {
|
|
46
|
+
const formatted = formatError(result.left);
|
|
47
|
+
|
|
48
|
+
expect(formatted).toMatchInlineSnapshot(`
|
|
49
|
+
"PrivateKey.Hex
|
|
50
|
+
└─ Encoded side transformation failure
|
|
51
|
+
└─ Expected string, actual 12345"
|
|
52
|
+
`);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("shows helpful error for null", () => {
|
|
57
|
+
const result = S.decodeUnknownEither(PrivateKey.Hex)(null);
|
|
58
|
+
|
|
59
|
+
expect(result._tag).toBe("Left");
|
|
60
|
+
if (result._tag === "Left") {
|
|
61
|
+
const formatted = formatError(result.left);
|
|
62
|
+
|
|
63
|
+
expect(formatted).toContain("Expected");
|
|
64
|
+
expect(formatted).toMatchInlineSnapshot(`
|
|
65
|
+
"PrivateKey.Hex
|
|
66
|
+
└─ Encoded side transformation failure
|
|
67
|
+
└─ Expected string, actual null"
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("does not leak key material in errors for almost valid key", () => {
|
|
73
|
+
// 62 valid hex chars + 2 invalid chars - almost looks like a real key
|
|
74
|
+
const almostValidKey = "0x" + "ab".repeat(31) + "xx";
|
|
75
|
+
const result = S.decodeUnknownEither(PrivateKey.Hex)(almostValidKey);
|
|
76
|
+
|
|
77
|
+
expect(result._tag).toBe("Left");
|
|
78
|
+
if (result._tag === "Left") {
|
|
79
|
+
const formatted = formatError(result.left);
|
|
80
|
+
|
|
81
|
+
// Error should NOT contain ANY of the key material
|
|
82
|
+
expect(formatted).not.toContain("abab");
|
|
83
|
+
expect(formatted).not.toContain("ab".repeat(31));
|
|
84
|
+
expect(formatted).not.toContain(almostValidKey);
|
|
85
|
+
expect(formatted).not.toContain(almostValidKey.slice(2)); // without 0x
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("PrivateKey.Bytes", () => {
|
|
91
|
+
it("shows helpful error for wrong length (too short)", () => {
|
|
92
|
+
const bytes = new Uint8Array(16);
|
|
93
|
+
const result = S.decodeUnknownEither(PrivateKey.Bytes)(bytes);
|
|
94
|
+
|
|
95
|
+
expect(result._tag).toBe("Left");
|
|
96
|
+
if (result._tag === "Left") {
|
|
97
|
+
const formatted = formatError(result.left);
|
|
98
|
+
|
|
99
|
+
// Should mention expected length
|
|
100
|
+
expect(formatted).toContain("32");
|
|
101
|
+
|
|
102
|
+
expect(formatted).toMatchInlineSnapshot(`
|
|
103
|
+
"PrivateKey.Bytes
|
|
104
|
+
└─ Transformation process failure
|
|
105
|
+
└─ Private key must be 32 bytes, got 16
|
|
106
|
+
|
|
107
|
+
Docs: https://voltaire.dev/primitives/private-key"
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("shows helpful error for wrong type (not Uint8Array)", () => {
|
|
113
|
+
const result = S.decodeUnknownEither(PrivateKey.Bytes)([1, 2, 3]);
|
|
114
|
+
|
|
115
|
+
expect(result._tag).toBe("Left");
|
|
116
|
+
if (result._tag === "Left") {
|
|
117
|
+
const formatted = formatError(result.left);
|
|
118
|
+
|
|
119
|
+
expect(formatted).toMatchInlineSnapshot(`
|
|
120
|
+
"PrivateKey.Bytes
|
|
121
|
+
└─ Encoded side transformation failure
|
|
122
|
+
└─ Expected Uint8ArrayFromSelf, actual [1,2,3]"
|
|
123
|
+
`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("PrivateKey.RedactedHex", () => {
|
|
129
|
+
it("does not leak key material for invalid input", () => {
|
|
130
|
+
const almostValidKey = "0x" + "cd".repeat(31) + "zz";
|
|
131
|
+
const result = S.decodeUnknownEither(PrivateKey.RedactedHex)(almostValidKey);
|
|
132
|
+
|
|
133
|
+
expect(result._tag).toBe("Left");
|
|
134
|
+
if (result._tag === "Left") {
|
|
135
|
+
const formatted = formatError(result.left);
|
|
136
|
+
|
|
137
|
+
// Redacted schema should be extra careful
|
|
138
|
+
expect(formatted).not.toContain("cdcd");
|
|
139
|
+
expect(formatted).not.toContain(almostValidKey);
|
|
140
|
+
|
|
141
|
+
expect(formatted).toMatchInlineSnapshot(`"Invalid private key: expected 64 hex characters (32 bytes)"`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("PrivateKey.RedactedBytes", () => {
|
|
147
|
+
it("shows helpful error for wrong length", () => {
|
|
148
|
+
const bytes = new Uint8Array(31);
|
|
149
|
+
const result = S.decodeUnknownEither(PrivateKey.RedactedBytes)(bytes);
|
|
150
|
+
|
|
151
|
+
expect(result._tag).toBe("Left");
|
|
152
|
+
if (result._tag === "Left") {
|
|
153
|
+
const formatted = formatError(result.left);
|
|
154
|
+
|
|
155
|
+
expect(formatted).toMatchInlineSnapshot(`
|
|
156
|
+
"PrivateKey.RedactedBytes
|
|
157
|
+
└─ Encoded side transformation failure
|
|
158
|
+
└─ PrivateKey.Bytes
|
|
159
|
+
└─ Transformation process failure
|
|
160
|
+
└─ Private key must be 32 bytes, got 31
|
|
161
|
+
|
|
162
|
+
Docs: https://voltaire.dev/primitives/private-key"
|
|
163
|
+
`);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -3,6 +3,7 @@ import { Hash } from "@tevm/voltaire";
|
|
|
3
3
|
import { PrivateKey as CorePrivateKey } from "@tevm/voltaire/PrivateKey";
|
|
4
4
|
import * as Secp256k1 from "@tevm/voltaire/Secp256k1";
|
|
5
5
|
import * as Effect from "effect/Effect";
|
|
6
|
+
import { Redacted } from "effect";
|
|
6
7
|
import * as S from "effect/Schema";
|
|
7
8
|
import * as PrivateKey from "./index.js";
|
|
8
9
|
|
|
@@ -373,6 +374,84 @@ describe("cryptographic operations", () => {
|
|
|
373
374
|
});
|
|
374
375
|
});
|
|
375
376
|
|
|
377
|
+
describe("PrivateKey.RedactedHex", () => {
|
|
378
|
+
it("decodes to Redacted type", () => {
|
|
379
|
+
const pk = S.decodeSync(PrivateKey.RedactedHex)(validHex);
|
|
380
|
+
expect(Redacted.isRedacted(pk)).toBe(true);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("does not expose value when logged", () => {
|
|
384
|
+
const pk = S.decodeSync(PrivateKey.RedactedHex)(validHex);
|
|
385
|
+
expect(String(pk)).toBe("<redacted>");
|
|
386
|
+
expect(String(pk)).not.toContain("ab");
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("allows explicit unwrap", () => {
|
|
390
|
+
const pk = S.decodeSync(PrivateKey.RedactedHex)(validHex);
|
|
391
|
+
const unwrapped = Redacted.value(pk);
|
|
392
|
+
expect(unwrapped).toBeInstanceOf(Uint8Array);
|
|
393
|
+
expect(unwrapped.length).toBe(32);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("round-trips correctly", () => {
|
|
397
|
+
const pk = S.decodeSync(PrivateKey.RedactedHex)(validHex);
|
|
398
|
+
const encoded = S.encodeSync(PrivateKey.RedactedHex)(pk);
|
|
399
|
+
expect(encoded).toBe(validHex.toLowerCase());
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("works with cryptographic operations after unwrap", () => {
|
|
403
|
+
const pk = S.decodeSync(PrivateKey.RedactedHex)(validHex);
|
|
404
|
+
const unwrapped = Redacted.value(pk);
|
|
405
|
+
const publicKey = Secp256k1.derivePublicKey(unwrapped);
|
|
406
|
+
expect(publicKey.length).toBe(64);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("fails on invalid hex", () => {
|
|
410
|
+
expect(() => S.decodeSync(PrivateKey.RedactedHex)("0x1234")).toThrow();
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it("parses without prefix", () => {
|
|
414
|
+
const pk = S.decodeSync(PrivateKey.RedactedHex)(validHexNoPrefix);
|
|
415
|
+
expect(Redacted.isRedacted(pk)).toBe(true);
|
|
416
|
+
expect(Redacted.value(pk).length).toBe(32);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
describe("PrivateKey.RedactedBytes", () => {
|
|
421
|
+
it("decodes to Redacted type", () => {
|
|
422
|
+
const bytes = new Uint8Array(32).fill(0xab);
|
|
423
|
+
const pk = S.decodeSync(PrivateKey.RedactedBytes)(bytes);
|
|
424
|
+
expect(Redacted.isRedacted(pk)).toBe(true);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("does not expose value when logged", () => {
|
|
428
|
+
const bytes = new Uint8Array(32).fill(0xab);
|
|
429
|
+
const pk = S.decodeSync(PrivateKey.RedactedBytes)(bytes);
|
|
430
|
+
expect(String(pk)).toBe("<redacted>");
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("allows explicit unwrap", () => {
|
|
434
|
+
const bytes = new Uint8Array(32).fill(0xab);
|
|
435
|
+
const pk = S.decodeSync(PrivateKey.RedactedBytes)(bytes);
|
|
436
|
+
const unwrapped = Redacted.value(pk);
|
|
437
|
+
expect(unwrapped).toBeInstanceOf(Uint8Array);
|
|
438
|
+
expect(unwrapped.length).toBe(32);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("round-trips correctly", () => {
|
|
442
|
+
const bytes = new Uint8Array(32).fill(0xab);
|
|
443
|
+
const pk = S.decodeSync(PrivateKey.RedactedBytes)(bytes);
|
|
444
|
+
const encoded = S.encodeSync(PrivateKey.RedactedBytes)(pk);
|
|
445
|
+
expect([...encoded]).toEqual([...bytes]);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("fails on wrong length", () => {
|
|
449
|
+
expect(() =>
|
|
450
|
+
S.decodeSync(PrivateKey.RedactedBytes)(new Uint8Array(31)),
|
|
451
|
+
).toThrow();
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
376
455
|
describe("secp256k1 curve constraints", () => {
|
|
377
456
|
it("rejects zero private key", () => {
|
|
378
457
|
const zeroKey = new Uint8Array(32);
|
|
@@ -2,61 +2,53 @@
|
|
|
2
2
|
* @module PrivateKey
|
|
3
3
|
* @description Effect Schemas for secp256k1 private keys with cryptographic operations.
|
|
4
4
|
*
|
|
5
|
-
* ## Type Declarations
|
|
6
|
-
*
|
|
7
|
-
* ```typescript
|
|
8
|
-
* import * as PrivateKey from 'voltaire-effect/primitives/PrivateKey'
|
|
9
|
-
*
|
|
10
|
-
* function signMessage(key: PrivateKey.PrivateKeyType) {
|
|
11
|
-
* // ...
|
|
12
|
-
* }
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
5
|
* ## Schemas
|
|
16
6
|
*
|
|
17
|
-
* | Schema | Input | Output |
|
|
18
|
-
*
|
|
19
|
-
* | `PrivateKey.Hex` | hex string | PrivateKeyType |
|
|
20
|
-
* | `PrivateKey.Bytes` | Uint8Array | PrivateKeyType |
|
|
7
|
+
* | Schema | Input | Output | Use Case |
|
|
8
|
+
* |--------|-------|--------|----------|
|
|
9
|
+
* | `PrivateKey.Hex` | hex string | PrivateKeyType | Development |
|
|
10
|
+
* | `PrivateKey.Bytes` | Uint8Array | PrivateKeyType | Development |
|
|
11
|
+
* | `PrivateKey.RedactedHex` | hex string | Redacted<PrivateKeyType> | **Production** |
|
|
12
|
+
* | `PrivateKey.RedactedBytes` | Uint8Array | Redacted<PrivateKeyType> | **Production** |
|
|
13
|
+
*
|
|
14
|
+
* ## Redacted Schemas (Recommended)
|
|
21
15
|
*
|
|
22
|
-
*
|
|
16
|
+
* Use `RedactedHex` or `RedactedBytes` in production to prevent accidental logging:
|
|
23
17
|
*
|
|
24
18
|
* ```typescript
|
|
25
19
|
* import * as PrivateKey from 'voltaire-effect/primitives/PrivateKey'
|
|
20
|
+
* import { Redacted } from 'effect'
|
|
26
21
|
* import * as S from 'effect/Schema'
|
|
27
22
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
23
|
+
* const pk = S.decodeSync(PrivateKey.RedactedHex)('0x0123...')
|
|
24
|
+
* console.log(pk) // Redacted(<redacted>)
|
|
30
25
|
*
|
|
31
|
-
* //
|
|
32
|
-
* const
|
|
33
|
-
* const
|
|
26
|
+
* // Explicit unwrap for crypto operations
|
|
27
|
+
* const unwrapped = Redacted.value(pk)
|
|
28
|
+
* const sig = Secp256k1.sign(hash, unwrapped)
|
|
34
29
|
* ```
|
|
35
30
|
*
|
|
36
|
-
* ##
|
|
31
|
+
* ## Standard Usage
|
|
37
32
|
*
|
|
38
33
|
* ```typescript
|
|
39
|
-
* import * as
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* const address = yield* PrivateKey.toAddress(pk)
|
|
45
|
-
* const signature = yield* PrivateKey.sign(pk, messageHash)
|
|
46
|
-
* return { publicKey, address, signature }
|
|
47
|
-
* })
|
|
34
|
+
* import * as PrivateKey from 'voltaire-effect/primitives/PrivateKey'
|
|
35
|
+
* import * as S from 'effect/Schema'
|
|
36
|
+
*
|
|
37
|
+
* const pk = S.decodeSync(PrivateKey.Hex)('0x0123456789abcdef...')
|
|
38
|
+
* const hex = S.encodeSync(PrivateKey.Hex)(pk)
|
|
48
39
|
* ```
|
|
49
40
|
*
|
|
50
41
|
* ## Pure Functions
|
|
51
42
|
*
|
|
52
43
|
* ```typescript
|
|
53
44
|
* PrivateKey.isValid(value) // Effect<boolean>
|
|
45
|
+
* PrivateKey.random() // Effect<PrivateKeyType>
|
|
54
46
|
* ```
|
|
55
47
|
*
|
|
56
48
|
* @since 0.1.0
|
|
57
49
|
*/
|
|
58
50
|
|
|
59
|
-
export { Bytes } from "./Bytes.js";
|
|
60
|
-
export { Hex } from "./Hex.js";
|
|
51
|
+
export { Bytes, RedactedBytes } from "./Bytes.js";
|
|
52
|
+
export { Hex, RedactedHex } from "./Hex.js";
|
|
61
53
|
export { isValid } from "./isValid.js";
|
|
62
54
|
export { random } from "./random.js";
|