Skip to content

Commit 4edf00d

Browse files
committedDec 29, 2022
feature: added capability to ed25519 sign payload when creating or updating an Item
1 parent cb36b4f commit 4edf00d

File tree

3 files changed

+160
-58
lines changed

3 files changed

+160
-58
lines changed
 

‎src/cli.ts

+12
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ const cmd = new Command()
7373
default: "text",
7474
},
7575
)
76+
.globalEnv(
77+
"TRUESTAMP_SIGNING_KEY_SEED=<signingKeySeed:string>",
78+
"A Base64 encoded 32 byte random seed used to create an ed25519 signing keypair.",
79+
{
80+
required: false,
81+
prefix: "TRUESTAMP_",
82+
},
83+
)
84+
.globalOption(
85+
"-S, --signing-key-seed <signingKeySeed:string>",
86+
"A Base64 encoded 32 byte random seed used to create an ed25519 signing keypair. Overrides 'TRUESTAMP_SIGNING_KEY_SEED' env var.",
87+
)
7688
.action(() => {
7789
cmd.showHelp();
7890
})

‎src/commands/items.ts

+111-58
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ import {
99
ValidationError,
1010
} from "../deps.ts";
1111

12-
import { logSelectedOutputFormat, throwApiError } from "../utils.ts";
12+
import {
13+
logSelectedOutputFormat,
14+
signItemData,
15+
throwApiError,
16+
} from "../utils.ts";
1317

1418
import { createDataDir, writeItemToDb } from "../db.ts";
1519

1620
import { environmentType, outputType } from "../cli.ts";
1721

22+
import { ItemRequest, ItemRequestSchema } from "../types.ts";
23+
1824
export const inputType = new EnumType(["binary", "json"]);
1925

2026
// Limit the available hash types for now to those that are supported by the browser
@@ -28,6 +34,7 @@ const itemsCreate = new Command<{
2834
env: typeof environmentType;
2935
apiKey?: string;
3036
output: typeof outputType;
37+
signingKeySeed?: string;
3138
}>()
3239
.description(
3340
`Create a new Item.
@@ -281,40 +288,60 @@ Pipe JSON content to the 'items create' command using '--input json' plus the '-
281288
try {
282289
let itemResp;
283290

291+
const createItemArgs = {
292+
skipCF: !options.netMetadata,
293+
skipOE: !options.observableEntropy,
294+
};
295+
284296
if (jsonItem) {
285-
itemResp = await truestamp.createItem(jsonItem, {
286-
skipCF: !options.netMetadata,
287-
skipOE: !options.observableEntropy,
288-
});
297+
const itemRequest: ItemRequest = ItemRequestSchema.parse(jsonItem);
298+
299+
itemRequest.itemDataSignatures = options.signingKeySeed
300+
? signItemData(itemRequest, options.signingKeySeed)
301+
: undefined;
302+
303+
console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
304+
305+
itemResp = await truestamp.createItem(itemRequest, createItemArgs);
289306
} else if (options.hash && options.hashType) {
307+
const itemRequest: ItemRequest = {
308+
itemData: [
309+
{
310+
hash: options.hash,
311+
hashType: <HashTypes> options.hashType,
312+
},
313+
],
314+
};
315+
316+
itemRequest.itemDataSignatures = options.signingKeySeed
317+
? signItemData(itemRequest, options.signingKeySeed)
318+
: undefined;
319+
320+
// console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
321+
290322
itemResp = await truestamp.createItem(
291-
{
292-
itemData: [
293-
{
294-
hash: options.hash,
295-
hashType: <HashTypes> options.hashType,
296-
},
297-
],
298-
},
299-
{
300-
skipCF: !options.netMetadata,
301-
skipOE: !options.observableEntropy,
302-
},
323+
itemRequest,
324+
createItemArgs,
303325
);
304326
} else if (altHash && altHashType) {
327+
const itemRequest: ItemRequest = {
328+
itemData: [
329+
{
330+
hash: altHash,
331+
hashType: <HashTypes> altHashType,
332+
},
333+
],
334+
};
335+
336+
itemRequest.itemDataSignatures = options.signingKeySeed
337+
? signItemData(itemRequest, options.signingKeySeed)
338+
: undefined;
339+
340+
// console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
341+
305342
itemResp = await truestamp.createItem(
306-
{
307-
itemData: [
308-
{
309-
hash: altHash,
310-
hashType: <HashTypes> altHashType,
311-
},
312-
],
313-
},
314-
{
315-
skipCF: !options.netMetadata,
316-
skipOE: !options.observableEntropy,
317-
},
343+
itemRequest,
344+
createItemArgs,
318345
);
319346
} else {
320347
throw new Error("No hash or hash-type provided");
@@ -369,6 +396,7 @@ const itemsUpdate = new Command<{
369396
env: typeof environmentType;
370397
apiKey?: string;
371398
output: typeof outputType;
399+
signingKeySeed?: string;
372400
}>()
373401
.description(
374402
`Update an existing Item by replacing it with a new one.
@@ -569,45 +597,69 @@ Pipe JSON content to the 'items update' command using '--input json' plus the '-
569597
try {
570598
let itemResp;
571599

600+
const updateItemsArgs = {
601+
skipCF: !options.netMetadata,
602+
skipOE: !options.observableEntropy,
603+
};
604+
572605
if (jsonItem) {
573-
itemResp = await truestamp.updateItem(options.id, jsonItem, {
574-
skipCF: !options.netMetadata,
575-
skipOE: !options.observableEntropy,
576-
});
606+
const itemRequest: ItemRequest = ItemRequestSchema.parse(jsonItem);
607+
608+
itemRequest.itemDataSignatures = options.signingKeySeed
609+
? signItemData(itemRequest, options.signingKeySeed)
610+
: undefined;
611+
612+
console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
613+
614+
itemResp = await truestamp.updateItem(
615+
options.id,
616+
itemRequest,
617+
updateItemsArgs,
618+
);
577619
} else if (options.hash && options.hashType) {
620+
const itemRequest: ItemRequest = {
621+
itemData: [
622+
{
623+
hash: options.hash,
624+
hashType: <HashTypes> options.hashType,
625+
},
626+
],
627+
};
628+
629+
itemRequest.itemDataSignatures = options.signingKeySeed
630+
? signItemData(itemRequest, options.signingKeySeed)
631+
: undefined;
632+
633+
// console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
634+
578635
itemResp = await truestamp.updateItem(
579636
options.id,
580-
{
581-
itemData: [
582-
{
583-
hash: options.hash,
584-
hashType: <HashTypes> options.hashType,
585-
},
586-
],
587-
},
588-
{
589-
skipCF: !options.netMetadata,
590-
skipOE: !options.observableEntropy,
591-
},
637+
itemRequest,
638+
updateItemsArgs,
592639
);
593640
} else if (altHash && altHashType) {
641+
const itemRequest: ItemRequest = {
642+
itemData: [
643+
{
644+
hash: altHash,
645+
hashType: <HashTypes> altHashType,
646+
},
647+
],
648+
};
649+
650+
itemRequest.itemDataSignatures = options.signingKeySeed
651+
? signItemData(itemRequest, options.signingKeySeed)
652+
: undefined;
653+
654+
// console.log("itemRequest", JSON.stringify(itemRequest, null, 2));
655+
594656
itemResp = await truestamp.updateItem(
595657
options.id,
596-
{
597-
itemData: [
598-
{
599-
hash: altHash,
600-
hashType: <HashTypes> altHashType,
601-
},
602-
],
603-
},
604-
{
605-
skipCF: !options.netMetadata,
606-
skipOE: !options.observableEntropy,
607-
},
658+
itemRequest,
659+
updateItemsArgs,
608660
);
609661
} else {
610-
throw new Error("No hash or hashType provided");
662+
throw new Error("No hash or hash-type provided");
611663
}
612664

613665
const { id } = itemResp;
@@ -628,6 +680,7 @@ export const items = new Command<{
628680
env: typeof environmentType;
629681
apiKey?: string;
630682
output: typeof outputType;
683+
signingKeySeed?: string;
631684
}>()
632685
.description("Create or update Items.")
633686
.action(() => {

‎src/utils.ts

+37
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
// Copyright © 2020-2022 Truestamp Inc. All rights reserved.
22

3+
import { generateKeyPairFromSeed, sign } from "@stablelib/ed25519";
4+
import { hash as sha256 } from "@stablelib/sha256";
35
import { z } from "zod";
46

7+
import { canonify } from "@truestamp/canonify";
8+
9+
import {
10+
decode as base64Decode,
11+
encode as base64Encode,
12+
} from "@stablelib/base64";
13+
514
const OutputWrapper = z.object({
615
text: z.string(),
716
json: z.record(z.string().min(1), z.any()),
817
});
918

19+
import type { ItemRequest, Signature } from "./types.ts";
20+
1021
export type OutputWrapper = z.infer<typeof OutputWrapper>;
1122

1223
export function logSelectedOutputFormat(
@@ -149,3 +160,29 @@ export async function put<T, U>(
149160
};
150161
return await http<U>(path, init);
151162
}
163+
164+
export function signItemData(
165+
itemRequest: ItemRequest,
166+
signingKeySeed: Uint8Array | string,
167+
): Signature[] {
168+
if (typeof signingKeySeed === "string") {
169+
signingKeySeed = base64Decode(signingKeySeed);
170+
}
171+
172+
const signingKeyPair = generateKeyPairFromSeed(signingKeySeed);
173+
const canonicalData = canonify(itemRequest.itemData);
174+
const canonicalDataUint8Array = new TextEncoder().encode(canonicalData);
175+
const canonicalDataHash = sha256(canonicalDataUint8Array);
176+
const canonicalDataHashSignature = sign(
177+
signingKeyPair.secretKey,
178+
canonicalDataHash,
179+
);
180+
181+
return [
182+
{
183+
publicKey: base64Encode(signingKeyPair.publicKey),
184+
signature: base64Encode(canonicalDataHashSignature),
185+
signatureType: "ed25519",
186+
},
187+
];
188+
}

0 commit comments

Comments
 (0)
Please sign in to comment.