@aptos-labs/ts-sdk
Version:
Aptos TypeScript SDK
124 lines • 4.59 kB
JavaScript
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0
import { randomBytes } from "@noble/hashes/utils.js";
import { gcm } from "@noble/ciphers/aes.js";
import { hkdf } from "@noble/hashes/hkdf.js";
import { sha256 } from "@noble/hashes/sha2.js";
import { hash_to_field } from "@noble/curves/abstract/hash-to-curve.js";
import { bls12_381 } from "@noble/curves/bls12-381.js";
import { Serializable, Serializer } from "../../../bcs/serializer.js";
import { leBytesToBigint } from "./fieldSerialization.js";
import { g2ToBytes } from "./curveSerialization.js";
const HASH_G2_ELEMENT_DST = "APTOS_BATCH_ENCRYPTION_HASH_G2_ELEMENT";
const HKDF_SALT = new TextEncoder().encode("APTOS_BATCH_ENCRYPTION_OTP");
/** AES-128 key length and OTP length (both 16 bytes). */
const SYMMETRIC_KEY_LENGTH = 16;
/** AES-GCM nonce length. */
const GCM_NONCE_LENGTH = 12;
/** Domain separation tag for Id::from_verifying_key_and_ad. Must match Rust ID_HASH_DST. */
export const ID_HASH_DST = new TextEncoder().encode("APTOS_BATCH_ENCRYPTION_HASH_ID");
export class OneTimePad extends Serializable {
otp;
constructor(otp) {
super();
if (otp.length !== SYMMETRIC_KEY_LENGTH) {
throw new Error(`One-time-pad length must be ${SYMMETRIC_KEY_LENGTH} bytes`);
}
this.otp = otp;
}
static fromSourceBytes(otpSource) {
const derived = hmacKdf(otpSource);
return new OneTimePad(derived.slice(0, SYMMETRIC_KEY_LENGTH));
}
padKey(value) {
const paddedKey = new Uint8Array(SYMMETRIC_KEY_LENGTH);
for (let i = 0; i < SYMMETRIC_KEY_LENGTH; i++) {
paddedKey[i] = value.key[i] ^ this.otp[i];
}
return new SymmetricKey(paddedKey);
}
serialize(serializer) {
serializer.serializeFixedBytes(this.otp);
}
static deserialize(deserializer) {
return new OneTimePad(deserializer.deserializeFixedBytes(SYMMETRIC_KEY_LENGTH));
}
}
export class SymmetricCiphertext extends Serializable {
nonce;
ctBody;
constructor(nonce, ctBody) {
super();
if (nonce.length !== GCM_NONCE_LENGTH) {
throw new Error(`Nonce must be ${GCM_NONCE_LENGTH} bytes`);
}
this.nonce = nonce;
this.ctBody = ctBody;
}
serialize(serializer) {
serializer.serializeFixedBytes(this.nonce);
serializer.serializeBytes(this.ctBody);
}
static deserialize(deserializer) {
const nonce = deserializer.deserializeFixedBytes(GCM_NONCE_LENGTH);
const ctBody = deserializer.deserializeBytes();
return new SymmetricCiphertext(nonce, ctBody);
}
}
export class SymmetricKey extends Serializable {
key;
constructor(key) {
super();
if (key) {
if (key.length !== SYMMETRIC_KEY_LENGTH) {
throw new Error(`Must provide a key of size ${SYMMETRIC_KEY_LENGTH}`);
}
this.key = key;
}
else {
this.key = randomBytes(SYMMETRIC_KEY_LENGTH);
}
}
encrypt(msg) {
const nonce = randomBytes(GCM_NONCE_LENGTH);
const serializer = new Serializer();
msg.serialize(serializer);
const bytes = serializer.toUint8Array();
const ctBody = gcm(this.key, nonce).encrypt(bytes);
return new SymmetricCiphertext(nonce, ctBody);
}
serialize(serializer) {
serializer.serializeFixedBytes(this.key);
}
static deserialize(deserializer) {
return new SymmetricKey(deserializer.deserializeFixedBytes(SYMMETRIC_KEY_LENGTH));
}
}
export function hmacKdf(otpSource) {
return hkdf(sha256, otpSource, HKDF_SALT, new Uint8Array(), 32);
}
export function getRandomFr() {
const randomBigint = leBytesToBigint(randomBytes(128));
return bls12_381.fields.Fr.create(randomBigint);
}
/**
* Hash bytes to a field element in Fr. Used for Id and other hashes.
* @param input - bytes to hash (e.g. vk || BCS(associated_data) for Id)
* @param dst - optional domain separation tag; use ID_HASH_DST for Id::from_verifying_key_and_ad
*/
export function hashToFr(input, dst = new Uint8Array(0)) {
const options = {
DST: dst,
expand: "xmd",
hash: sha256,
p: bls12_381.fields.Fr.ORDER,
m: 1,
k: 128,
};
return hash_to_field(Uint8Array.from(input), 1, options)[0][0];
}
export function hashG2Element(g2Element) {
const bytes = g2ToBytes(g2Element);
return bls12_381.G1.hashToCurve(bytes, { DST: HASH_G2_ELEMENT_DST });
}
//# sourceMappingURL=symmetric.js.map