zattera-js 0.1.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/LICENSE +21 -0
- package/README.md +694 -0
- package/dist/browser/index.js +2466 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/node/auth/index.js +188 -0
- package/dist/node/auth/index.js.map +1 -0
- package/dist/node/auth/keys.js +264 -0
- package/dist/node/auth/keys.js.map +1 -0
- package/dist/node/auth/memo.js +79 -0
- package/dist/node/auth/memo.js.map +1 -0
- package/dist/node/auth/serializer.js +162 -0
- package/dist/node/auth/serializer.js.map +1 -0
- package/dist/node/client/index.js +838 -0
- package/dist/node/client/index.js.map +1 -0
- package/dist/node/index.js +30 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/node_modules/@noble/ciphers/aes.js +254 -0
- package/dist/node/node_modules/@noble/ciphers/aes.js.map +1 -0
- package/dist/node/node_modules/@noble/ciphers/utils.js +113 -0
- package/dist/node/node_modules/@noble/ciphers/utils.js.map +1 -0
- package/dist/node/node_modules/@noble/hashes/esm/_md.js +146 -0
- package/dist/node/node_modules/@noble/hashes/esm/_md.js.map +1 -0
- package/dist/node/node_modules/@noble/hashes/esm/_u64.js +51 -0
- package/dist/node/node_modules/@noble/hashes/esm/_u64.js.map +1 -0
- package/dist/node/node_modules/@noble/hashes/esm/legacy.js +123 -0
- package/dist/node/node_modules/@noble/hashes/esm/legacy.js.map +1 -0
- package/dist/node/node_modules/@noble/hashes/esm/sha2.js +346 -0
- package/dist/node/node_modules/@noble/hashes/esm/sha2.js.map +1 -0
- package/dist/node/node_modules/@noble/hashes/esm/utils.js +73 -0
- package/dist/node/node_modules/@noble/hashes/esm/utils.js.map +1 -0
- package/dist/node/node_modules/@noble/secp256k1/index.js +578 -0
- package/dist/node/node_modules/@noble/secp256k1/index.js.map +1 -0
- package/dist/node/node_modules/bs58/node_modules/base-x/src/esm/index.js +132 -0
- package/dist/node/node_modules/bs58/node_modules/base-x/src/esm/index.js.map +1 -0
- package/dist/node/node_modules/bs58/src/esm/index.js +7 -0
- package/dist/node/node_modules/bs58/src/esm/index.js.map +1 -0
- package/dist/node/types/index.js +48 -0
- package/dist/node/types/index.js.map +1 -0
- package/dist/node/utils/chain-id.js +9 -0
- package/dist/node/utils/chain-id.js.map +1 -0
- package/dist/types/auth/index.d.ts +134 -0
- package/dist/types/auth/index.d.ts.map +1 -0
- package/dist/types/auth/keys.d.ts +112 -0
- package/dist/types/auth/keys.d.ts.map +1 -0
- package/dist/types/auth/memo.d.ts +51 -0
- package/dist/types/auth/memo.d.ts.map +1 -0
- package/dist/types/auth/serializer.d.ts +57 -0
- package/dist/types/auth/serializer.d.ts.map +1 -0
- package/dist/types/client/index.d.ts +360 -0
- package/dist/types/client/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +16 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +593 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/utils/chain-id.d.ts +10 -0
- package/dist/types/utils/chain-id.d.ts.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { sha256 } from "../node_modules/@noble/hashes/esm/sha2.js";
|
|
2
|
+
import { PrivateKey, Signature, generateKeys } from "./keys.js";
|
|
3
|
+
import { PublicKey, isPublicKey, isWif } from "./keys.js";
|
|
4
|
+
import { serializeTransaction } from "./serializer.js";
|
|
5
|
+
import { TransactionSerializer, serializeSignedTransaction } from "./serializer.js";
|
|
6
|
+
import "../node_modules/bs58/src/esm/index.js";
|
|
7
|
+
async function signTransaction(transaction, privateKeys, chainId) {
|
|
8
|
+
const txBuffer = serializeTransaction(transaction);
|
|
9
|
+
const chainIdBytes = hexToBytes(chainId);
|
|
10
|
+
const digest = new Uint8Array(chainIdBytes.length + txBuffer.length);
|
|
11
|
+
digest.set(chainIdBytes);
|
|
12
|
+
digest.set(txBuffer, chainIdBytes.length);
|
|
13
|
+
const messageHash = sha256(digest);
|
|
14
|
+
const signatures = [];
|
|
15
|
+
for (const key of privateKeys) {
|
|
16
|
+
const privateKey = typeof key === "string" ? PrivateKey.fromWif(key) : key;
|
|
17
|
+
const signature = await privateKey.sign(messageHash);
|
|
18
|
+
signatures.push(bufferToHex(signature.toBuffer()));
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
...transaction,
|
|
22
|
+
signatures
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function verifyTransactionSignatures(transaction, chainId) {
|
|
26
|
+
const { signatures, ...txWithoutSigs } = transaction;
|
|
27
|
+
const txBuffer = serializeTransaction(txWithoutSigs);
|
|
28
|
+
const chainIdBytes = hexToBytes(chainId);
|
|
29
|
+
const digest = new Uint8Array(chainIdBytes.length + txBuffer.length);
|
|
30
|
+
digest.set(chainIdBytes);
|
|
31
|
+
digest.set(txBuffer, chainIdBytes.length);
|
|
32
|
+
const messageHash = sha256(digest);
|
|
33
|
+
const publicKeys = [];
|
|
34
|
+
for (const sigHex of signatures) {
|
|
35
|
+
const sigBytes = hexToBytes(sigHex);
|
|
36
|
+
const signature = new Signature(sigBytes);
|
|
37
|
+
const publicKey = await signature.recoverPublicKey(messageHash);
|
|
38
|
+
publicKeys.push(publicKey);
|
|
39
|
+
}
|
|
40
|
+
return publicKeys;
|
|
41
|
+
}
|
|
42
|
+
function createTransaction(refBlockNum, refBlockPrefix, expiration, operations) {
|
|
43
|
+
const expirationString = typeof expiration === "string" ? expiration : expiration.toISOString().split(".")[0] ?? "";
|
|
44
|
+
return {
|
|
45
|
+
ref_block_num: refBlockNum & 65535,
|
|
46
|
+
// Ensure it's within uint16 range
|
|
47
|
+
ref_block_prefix: refBlockPrefix >>> 0,
|
|
48
|
+
// Ensure it's uint32
|
|
49
|
+
expiration: expirationString,
|
|
50
|
+
operations,
|
|
51
|
+
extensions: []
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function hexToBytes(hex) {
|
|
55
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
56
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
57
|
+
for (let i = 0; i < cleanHex.length; i += 2) {
|
|
58
|
+
bytes[i / 2] = parseInt(cleanHex.slice(i, i + 2), 16);
|
|
59
|
+
}
|
|
60
|
+
return bytes;
|
|
61
|
+
}
|
|
62
|
+
function bufferToHex(buffer) {
|
|
63
|
+
return Array.from(buffer).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
64
|
+
}
|
|
65
|
+
const Auth = {
|
|
66
|
+
/**
|
|
67
|
+
* Generate all role keys from account name and password
|
|
68
|
+
* @param name Account name
|
|
69
|
+
* @param password Password
|
|
70
|
+
* @param roles Array of role names (default: ['owner', 'active', 'posting', 'memo'])
|
|
71
|
+
* @returns Object mapping roles to private WIF keys and public keys
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* const keys = Auth.getPrivateKeys('alice', 'password123');
|
|
76
|
+
* console.log(keys.active); // WIF private key
|
|
77
|
+
* console.log(keys.activePubkey); // ZTR... public key
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
getPrivateKeys(name, password, roles = ["owner", "active", "posting", "memo"]) {
|
|
81
|
+
const result = {};
|
|
82
|
+
const keys = generateKeys(name, password, roles);
|
|
83
|
+
for (const role of roles) {
|
|
84
|
+
const roleKey = keys[role];
|
|
85
|
+
if (roleKey) {
|
|
86
|
+
result[role] = roleKey.private;
|
|
87
|
+
result[`${role}Pubkey`] = roleKey.public;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
},
|
|
92
|
+
/**
|
|
93
|
+
* Generate public keys only from account name and password
|
|
94
|
+
* @param name Account name
|
|
95
|
+
* @param password Password
|
|
96
|
+
* @param roles Array of role names
|
|
97
|
+
* @returns Object mapping roles to public keys
|
|
98
|
+
*/
|
|
99
|
+
generateKeys(name, password, roles = ["owner", "active", "posting", "memo"]) {
|
|
100
|
+
const keys = generateKeys(name, password, roles);
|
|
101
|
+
const result = {};
|
|
102
|
+
for (const role of roles) {
|
|
103
|
+
const roleKey = keys[role];
|
|
104
|
+
if (roleKey) {
|
|
105
|
+
result[role] = roleKey.public;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
},
|
|
110
|
+
/**
|
|
111
|
+
* Convert account credentials to WIF format for a specific role
|
|
112
|
+
* @param name Account name
|
|
113
|
+
* @param password Password
|
|
114
|
+
* @param role Role name (e.g., 'active', 'posting')
|
|
115
|
+
* @returns WIF private key
|
|
116
|
+
*/
|
|
117
|
+
toWif(name, password, role = "active") {
|
|
118
|
+
const privateKey = PrivateKey.fromLogin(name, password, role);
|
|
119
|
+
return privateKey.toWif();
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* Convert WIF private key to public key
|
|
123
|
+
* @param privateWif WIF format private key
|
|
124
|
+
* @param addressPrefix Address prefix (default: 'ZTR')
|
|
125
|
+
* @returns Public key string
|
|
126
|
+
*/
|
|
127
|
+
wifToPublic(privateWif, addressPrefix = "ZTR") {
|
|
128
|
+
const privateKey = PrivateKey.fromWif(privateWif);
|
|
129
|
+
const publicKey = privateKey.toPublic();
|
|
130
|
+
return publicKey.toString(addressPrefix);
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* Check if a WIF key is valid
|
|
134
|
+
* @param privateWif WIF format private key
|
|
135
|
+
* @returns true if valid, false otherwise
|
|
136
|
+
*/
|
|
137
|
+
isWif(privateWif) {
|
|
138
|
+
try {
|
|
139
|
+
PrivateKey.fromWif(privateWif);
|
|
140
|
+
return true;
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
/**
|
|
146
|
+
* Sign a transaction with private keys
|
|
147
|
+
* @param tx Transaction to sign
|
|
148
|
+
* @param keys Array of WIF private keys
|
|
149
|
+
* @param chainId Chain ID
|
|
150
|
+
* @returns Signed transaction
|
|
151
|
+
*/
|
|
152
|
+
signTransaction(tx, keys, chainId) {
|
|
153
|
+
return signTransaction(tx, keys, chainId);
|
|
154
|
+
},
|
|
155
|
+
/**
|
|
156
|
+
* Verify account credentials against authorities
|
|
157
|
+
* @param name Account name
|
|
158
|
+
* @param password Password
|
|
159
|
+
* @param auths Authority public keys to verify against
|
|
160
|
+
* @param role Role to check (default: 'active')
|
|
161
|
+
* @returns true if credentials match, false otherwise
|
|
162
|
+
*/
|
|
163
|
+
verify(name, password, auths, role = "active") {
|
|
164
|
+
const privateKey = PrivateKey.fromLogin(name, password, role);
|
|
165
|
+
const publicKey = privateKey.toPublic().toString("ZTR");
|
|
166
|
+
const roleAuths = auths[role];
|
|
167
|
+
if (!roleAuths) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return roleAuths.includes(publicKey);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
export {
|
|
174
|
+
Auth,
|
|
175
|
+
PrivateKey,
|
|
176
|
+
PublicKey,
|
|
177
|
+
Signature,
|
|
178
|
+
TransactionSerializer,
|
|
179
|
+
createTransaction,
|
|
180
|
+
generateKeys,
|
|
181
|
+
isPublicKey,
|
|
182
|
+
isWif,
|
|
183
|
+
serializeSignedTransaction,
|
|
184
|
+
serializeTransaction,
|
|
185
|
+
signTransaction,
|
|
186
|
+
verifyTransactionSignatures
|
|
187
|
+
};
|
|
188
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/auth/index.ts"],"sourcesContent":["/**\n * Authentication and transaction signing for Zattera blockchain\n */\n\nimport { sha256 } from '@noble/hashes/sha2';\nimport { PrivateKey, PublicKey, Signature, generateKeys as genKeys } from './keys.js';\nimport { serializeTransaction } from './serializer.js';\nimport type { Transaction, SignedTransaction, Operation } from '../types/index.js';\n\nexport * from './keys.js';\nexport * from './serializer.js';\nexport * from './memo.js';\n\n/**\n * Configuration for signing\n */\nexport interface SignConfig {\n chainId: string;\n addressPrefix?: string;\n}\n\n/**\n * Sign a transaction with one or more private keys\n *\n * @param transaction The transaction to sign\n * @param privateKeys Array of private keys (WIF format) or PrivateKey instances\n * @param chainId Chain ID (hex string without 0x prefix)\n * @returns Signed transaction with signatures\n *\n * @example\n * ```typescript\n * const signedTx = await signTransaction(\n * transaction,\n * ['5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3'],\n * '0000000000000000000000000000000000000000000000000000000000000000'\n * );\n * ```\n */\nexport async function signTransaction(\n transaction: Transaction,\n privateKeys: (string | PrivateKey)[],\n chainId: string\n): Promise<SignedTransaction> {\n // Serialize the transaction (without signatures)\n const txBuffer = serializeTransaction(transaction);\n\n // Convert chain ID from hex to bytes\n const chainIdBytes = hexToBytes(chainId);\n\n // Create the digest: chainId + serialized transaction\n const digest = new Uint8Array(chainIdBytes.length + txBuffer.length);\n digest.set(chainIdBytes);\n digest.set(txBuffer, chainIdBytes.length);\n\n // Hash the digest with SHA-256\n const messageHash = sha256(digest);\n\n // Sign with each private key\n const signatures: string[] = [];\n\n for (const key of privateKeys) {\n const privateKey = typeof key === 'string' ? PrivateKey.fromWif(key) : key;\n const signature = await privateKey.sign(messageHash);\n signatures.push(bufferToHex(signature.toBuffer()));\n }\n\n // Return signed transaction\n return {\n ...transaction,\n signatures,\n };\n}\n\n/**\n * Verify a transaction signature\n *\n * @param transaction The signed transaction\n * @param chainId Chain ID (hex string without 0x prefix)\n * @returns Array of public keys that signed the transaction\n */\nexport async function verifyTransactionSignatures(\n transaction: SignedTransaction,\n chainId: string\n): Promise<PublicKey[]> {\n // Serialize the transaction (without signatures)\n const { signatures, ...txWithoutSigs } = transaction;\n const txBuffer = serializeTransaction(txWithoutSigs);\n\n // Create the digest\n const chainIdBytes = hexToBytes(chainId);\n const digest = new Uint8Array(chainIdBytes.length + txBuffer.length);\n digest.set(chainIdBytes);\n digest.set(txBuffer, chainIdBytes.length);\n\n // Hash the digest\n const messageHash = sha256(digest);\n\n // Recover public keys from signatures\n const publicKeys: PublicKey[] = [];\n\n for (const sigHex of signatures) {\n const sigBytes = hexToBytes(sigHex);\n const signature = new Signature(sigBytes);\n const publicKey = await signature.recoverPublicKey(messageHash);\n publicKeys.push(publicKey);\n }\n\n return publicKeys;\n}\n\n/**\n * Create a transaction ready for signing\n *\n * @param refBlockNum Reference block number (from head_block_number & 0xFFFF)\n * @param refBlockPrefix Reference block prefix (from block header)\n * @param expiration Expiration date (ISO 8601 string or Date)\n * @param operations Array of operations\n * @returns Transaction object ready for signing\n *\n * @example\n * ```typescript\n * const tx = createTransaction(\n * 12345,\n * 4567890,\n * new Date(Date.now() + 60000),\n * [\n * ['vote', { voter: 'alice', author: 'bob', permlink: 'test', weight: 10000 }]\n * ]\n * );\n * ```\n */\nexport function createTransaction(\n refBlockNum: number,\n refBlockPrefix: number,\n expiration: string | Date,\n operations: unknown[]\n): Transaction {\n const expirationString = typeof expiration === 'string'\n ? expiration\n : (expiration.toISOString().split('.')[0] ?? ''); // Remove milliseconds\n\n return {\n ref_block_num: refBlockNum & 0xffff, // Ensure it's within uint16 range\n ref_block_prefix: refBlockPrefix >>> 0, // Ensure it's uint32\n expiration: expirationString,\n operations: operations as Operation[],\n extensions: [],\n };\n}\n\n/**\n * Helper: Convert hex string to Uint8Array\n */\nfunction hexToBytes(hex: string): Uint8Array {\n // Remove '0x' prefix if present\n const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;\n\n const bytes = new Uint8Array(cleanHex.length / 2);\n for (let i = 0; i < cleanHex.length; i += 2) {\n bytes[i / 2] = parseInt(cleanHex.slice(i, i + 2), 16);\n }\n return bytes;\n}\n\n/**\n * Helper: Convert Uint8Array to hex string\n */\nfunction bufferToHex(buffer: Uint8Array): string {\n return Array.from(buffer)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Auth API - Comprehensive authentication utilities\n */\nexport const Auth = {\n /**\n * Generate all role keys from account name and password\n * @param name Account name\n * @param password Password\n * @param roles Array of role names (default: ['owner', 'active', 'posting', 'memo'])\n * @returns Object mapping roles to private WIF keys and public keys\n *\n * @example\n * ```typescript\n * const keys = Auth.getPrivateKeys('alice', 'password123');\n * console.log(keys.active); // WIF private key\n * console.log(keys.activePubkey); // ZTR... public key\n * ```\n */\n getPrivateKeys(\n name: string,\n password: string,\n roles: string[] = ['owner', 'active', 'posting', 'memo']\n ): Record<string, string> {\n const result: Record<string, string> = {};\n const keys = genKeys(name, password, roles);\n\n for (const role of roles) {\n const roleKey = keys[role];\n if (roleKey) {\n result[role] = roleKey.private;\n result[`${role}Pubkey`] = roleKey.public;\n }\n }\n\n return result;\n },\n\n /**\n * Generate public keys only from account name and password\n * @param name Account name\n * @param password Password\n * @param roles Array of role names\n * @returns Object mapping roles to public keys\n */\n generateKeys(\n name: string,\n password: string,\n roles: string[] = ['owner', 'active', 'posting', 'memo']\n ): Record<string, string> {\n const keys = genKeys(name, password, roles);\n const result: Record<string, string> = {};\n\n for (const role of roles) {\n const roleKey = keys[role];\n if (roleKey) {\n result[role] = roleKey.public;\n }\n }\n\n return result;\n },\n\n /**\n * Convert account credentials to WIF format for a specific role\n * @param name Account name\n * @param password Password\n * @param role Role name (e.g., 'active', 'posting')\n * @returns WIF private key\n */\n toWif(name: string, password: string, role: string = 'active'): string {\n const privateKey = PrivateKey.fromLogin(name, password, role);\n return privateKey.toWif();\n },\n\n /**\n * Convert WIF private key to public key\n * @param privateWif WIF format private key\n * @param addressPrefix Address prefix (default: 'ZTR')\n * @returns Public key string\n */\n wifToPublic(privateWif: string, addressPrefix: string = 'ZTR'): string {\n const privateKey = PrivateKey.fromWif(privateWif);\n const publicKey = privateKey.toPublic();\n return publicKey.toString(addressPrefix);\n },\n\n /**\n * Check if a WIF key is valid\n * @param privateWif WIF format private key\n * @returns true if valid, false otherwise\n */\n isWif(privateWif: string): boolean {\n try {\n PrivateKey.fromWif(privateWif);\n return true;\n } catch {\n return false;\n }\n },\n\n /**\n * Sign a transaction with private keys\n * @param tx Transaction to sign\n * @param keys Array of WIF private keys\n * @param chainId Chain ID\n * @returns Signed transaction\n */\n signTransaction(\n tx: Transaction,\n keys: string[],\n chainId: string\n ): Promise<SignedTransaction> {\n return signTransaction(tx, keys, chainId);\n },\n\n /**\n * Verify account credentials against authorities\n * @param name Account name\n * @param password Password\n * @param auths Authority public keys to verify against\n * @param role Role to check (default: 'active')\n * @returns true if credentials match, false otherwise\n */\n verify(\n name: string,\n password: string,\n auths: { owner?: string[]; active?: string[]; posting?: string[] },\n role: string = 'active'\n ): boolean {\n const privateKey = PrivateKey.fromLogin(name, password, role);\n const publicKey = privateKey.toPublic().toString('ZTR');\n\n const roleAuths = auths[role as keyof typeof auths];\n if (!roleAuths) {\n return false;\n }\n\n return roleAuths.includes(publicKey);\n },\n};\n"],"names":["genKeys"],"mappings":"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"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { getPublicKey, signAsync, getSharedSecret, verify, Signature as Signature$1 } from "../node_modules/@noble/secp256k1/index.js";
|
|
2
|
+
import { sha256 } from "../node_modules/@noble/hashes/esm/sha2.js";
|
|
3
|
+
import { ripemd160 } from "../node_modules/@noble/hashes/esm/legacy.js";
|
|
4
|
+
import bs58 from "../node_modules/bs58/src/esm/index.js";
|
|
5
|
+
class PrivateKey {
|
|
6
|
+
key;
|
|
7
|
+
constructor(key) {
|
|
8
|
+
if (key.length !== 32) {
|
|
9
|
+
throw new Error("Private key must be 32 bytes");
|
|
10
|
+
}
|
|
11
|
+
this.key = key;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create PrivateKey from WIF (Wallet Import Format) string
|
|
15
|
+
*/
|
|
16
|
+
static fromWif(wif) {
|
|
17
|
+
const decoded = bs58.decode(wif);
|
|
18
|
+
if (decoded[0] !== 128) {
|
|
19
|
+
throw new Error("Invalid WIF version byte");
|
|
20
|
+
}
|
|
21
|
+
const privateKey = decoded.slice(1, 33);
|
|
22
|
+
const checksum = decoded.slice(33, 37);
|
|
23
|
+
const hash1 = sha256(decoded.slice(0, 33));
|
|
24
|
+
const hash2 = sha256(hash1);
|
|
25
|
+
const expectedChecksum = hash2.slice(0, 4);
|
|
26
|
+
if (!arraysEqual(checksum, expectedChecksum)) {
|
|
27
|
+
throw new Error("Invalid WIF checksum");
|
|
28
|
+
}
|
|
29
|
+
return new PrivateKey(privateKey);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create PrivateKey from seed string
|
|
33
|
+
*/
|
|
34
|
+
static fromSeed(seed) {
|
|
35
|
+
const hash = sha256(new TextEncoder().encode(seed));
|
|
36
|
+
return new PrivateKey(hash);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Generate PrivateKey from account name, password and role
|
|
40
|
+
*/
|
|
41
|
+
static fromLogin(accountName, password, role = "active") {
|
|
42
|
+
const seed = accountName + role + password;
|
|
43
|
+
const normalized = seed.trim().replace(/\s+/g, " ");
|
|
44
|
+
return PrivateKey.fromSeed(normalized);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Convert to WIF format
|
|
48
|
+
*/
|
|
49
|
+
toWif() {
|
|
50
|
+
const versioned = new Uint8Array(33);
|
|
51
|
+
versioned[0] = 128;
|
|
52
|
+
versioned.set(this.key, 1);
|
|
53
|
+
const hash1 = sha256(versioned);
|
|
54
|
+
const hash2 = sha256(hash1);
|
|
55
|
+
const checksum = hash2.slice(0, 4);
|
|
56
|
+
const result = new Uint8Array(37);
|
|
57
|
+
result.set(versioned);
|
|
58
|
+
result.set(checksum, 33);
|
|
59
|
+
return bs58.encode(result);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get the public key
|
|
63
|
+
*/
|
|
64
|
+
toPublic() {
|
|
65
|
+
const publicKeyBytes = getPublicKey(this.key, true);
|
|
66
|
+
return new PublicKey(publicKeyBytes);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get raw bytes
|
|
70
|
+
*/
|
|
71
|
+
toBytes() {
|
|
72
|
+
return this.key;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Sign a message hash
|
|
76
|
+
* @param messageHash The message hash to sign
|
|
77
|
+
* @returns Signature with recovery parameter
|
|
78
|
+
*/
|
|
79
|
+
async sign(messageHash) {
|
|
80
|
+
const sig = await signAsync(messageHash, this.key, {
|
|
81
|
+
lowS: true
|
|
82
|
+
// Enforce canonical signatures (prevents malleability)
|
|
83
|
+
});
|
|
84
|
+
return new Signature({ signature: sig.toCompactRawBytes(), recovery: sig.recovery ?? 0 });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get shared secret with a public key (for ECIES encryption)
|
|
88
|
+
* @param publicKey The public key to generate shared secret with
|
|
89
|
+
* @returns Shared secret as Uint8Array
|
|
90
|
+
*/
|
|
91
|
+
getSharedSecret(publicKey) {
|
|
92
|
+
const sharedPoint = getSharedSecret(this.key, publicKey.toBytes(), true);
|
|
93
|
+
return sharedPoint.slice(1);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Derive child key from offset (hierarchical key derivation)
|
|
97
|
+
* @param offset Offset string for derivation
|
|
98
|
+
* @returns New derived PrivateKey
|
|
99
|
+
*/
|
|
100
|
+
child(offset) {
|
|
101
|
+
const offsetHash = sha256(new TextEncoder().encode(offset));
|
|
102
|
+
const offsetBigInt = BigInt("0x" + Array.from(offsetHash).map((b) => b.toString(16).padStart(2, "0")).join(""));
|
|
103
|
+
const keyBigInt = BigInt("0x" + Array.from(this.key).map((b) => b.toString(16).padStart(2, "0")).join(""));
|
|
104
|
+
const n = BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
|
|
105
|
+
const childKeyBigInt = (keyBigInt + offsetBigInt) % n;
|
|
106
|
+
const childKeyHex = childKeyBigInt.toString(16).padStart(64, "0");
|
|
107
|
+
const childKeyBytes = new Uint8Array(32);
|
|
108
|
+
for (let i = 0; i < 32; i++) {
|
|
109
|
+
childKeyBytes[i] = parseInt(childKeyHex.slice(i * 2, i * 2 + 2), 16);
|
|
110
|
+
}
|
|
111
|
+
return new PrivateKey(childKeyBytes);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
class PublicKey {
|
|
115
|
+
key;
|
|
116
|
+
constructor(key) {
|
|
117
|
+
if (key.length !== 33) {
|
|
118
|
+
throw new Error("Compressed public key must be 33 bytes");
|
|
119
|
+
}
|
|
120
|
+
this.key = key;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Create PublicKey from string (e.g., "ZTR...")
|
|
124
|
+
*/
|
|
125
|
+
static fromString(publicKeyString, prefix = "ZTR") {
|
|
126
|
+
if (!publicKeyString.startsWith(prefix)) {
|
|
127
|
+
throw new Error(`Public key must start with ${prefix}`);
|
|
128
|
+
}
|
|
129
|
+
const keyString = publicKeyString.substring(prefix.length);
|
|
130
|
+
const decoded = bs58.decode(keyString);
|
|
131
|
+
const publicKey = decoded.slice(0, 33);
|
|
132
|
+
const checksum = decoded.slice(33);
|
|
133
|
+
const hash = ripemd160(publicKey);
|
|
134
|
+
const expectedChecksum = hash.slice(0, 4);
|
|
135
|
+
if (!arraysEqual(checksum, expectedChecksum)) {
|
|
136
|
+
throw new Error("Invalid public key checksum");
|
|
137
|
+
}
|
|
138
|
+
return new PublicKey(publicKey);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Convert to string format (e.g., "ZTR...")
|
|
142
|
+
*/
|
|
143
|
+
toString(prefix = "ZTR") {
|
|
144
|
+
const checksum = ripemd160(this.key).slice(0, 4);
|
|
145
|
+
const result = new Uint8Array(37);
|
|
146
|
+
result.set(this.key);
|
|
147
|
+
result.set(checksum, 33);
|
|
148
|
+
return prefix + bs58.encode(result);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get raw bytes
|
|
152
|
+
*/
|
|
153
|
+
toBytes() {
|
|
154
|
+
return this.key;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Verify a signature
|
|
158
|
+
*/
|
|
159
|
+
async verify(messageHash, signature) {
|
|
160
|
+
try {
|
|
161
|
+
return verify(signature.toCompact(), messageHash, this.key);
|
|
162
|
+
} catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Derive child key from offset (hierarchical key derivation)
|
|
168
|
+
* Note: This is not implemented for public keys alone, derive from private key instead
|
|
169
|
+
* @param _offset Offset string for derivation (unused)
|
|
170
|
+
* @returns Never - throws error
|
|
171
|
+
*/
|
|
172
|
+
child(_offset) {
|
|
173
|
+
throw new Error("PublicKey.child() requires private key derivation");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
class Signature {
|
|
177
|
+
sig;
|
|
178
|
+
recovery;
|
|
179
|
+
constructor(sig) {
|
|
180
|
+
if (sig instanceof Uint8Array) {
|
|
181
|
+
if (sig.length !== 65) {
|
|
182
|
+
throw new Error("Signature must be 65 bytes");
|
|
183
|
+
}
|
|
184
|
+
const recoveryByte = sig[0];
|
|
185
|
+
if (recoveryByte === void 0) {
|
|
186
|
+
throw new Error("Invalid signature: missing recovery byte");
|
|
187
|
+
}
|
|
188
|
+
this.recovery = recoveryByte - 27;
|
|
189
|
+
this.sig = sig.slice(1);
|
|
190
|
+
} else {
|
|
191
|
+
this.sig = sig.signature;
|
|
192
|
+
this.recovery = sig.recovery ?? 0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get compact signature (65 bytes: recovery + r + s)
|
|
197
|
+
*/
|
|
198
|
+
toCompact() {
|
|
199
|
+
return this.sig;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get full signature with recovery parameter (65 bytes)
|
|
203
|
+
*/
|
|
204
|
+
toBuffer() {
|
|
205
|
+
const result = new Uint8Array(65);
|
|
206
|
+
result[0] = this.recovery + 27;
|
|
207
|
+
result.set(this.sig, 1);
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Recover public key from signature and message hash
|
|
212
|
+
*/
|
|
213
|
+
async recoverPublicKey(messageHash) {
|
|
214
|
+
const sig = Signature$1.fromCompact(this.sig);
|
|
215
|
+
const recoveredSig = sig.addRecoveryBit(this.recovery);
|
|
216
|
+
const point = recoveredSig.recoverPublicKey(messageHash);
|
|
217
|
+
const publicKeyBytes = point.toRawBytes(true);
|
|
218
|
+
return new PublicKey(publicKeyBytes);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function arraysEqual(a, b) {
|
|
222
|
+
if (a.length !== b.length) return false;
|
|
223
|
+
for (let i = 0; i < a.length; i++) {
|
|
224
|
+
if (a[i] !== b[i]) return false;
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
function generateKeys(accountName, password, roles = ["owner", "active", "posting", "memo"]) {
|
|
229
|
+
const keys = {};
|
|
230
|
+
for (const role of roles) {
|
|
231
|
+
const privateKey = PrivateKey.fromLogin(accountName, password, role);
|
|
232
|
+
const publicKey = privateKey.toPublic();
|
|
233
|
+
keys[role] = {
|
|
234
|
+
private: privateKey.toWif(),
|
|
235
|
+
public: publicKey.toString()
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return keys;
|
|
239
|
+
}
|
|
240
|
+
function isWif(wif) {
|
|
241
|
+
try {
|
|
242
|
+
PrivateKey.fromWif(wif);
|
|
243
|
+
return true;
|
|
244
|
+
} catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function isPublicKey(publicKey, prefix = "ZTR") {
|
|
249
|
+
try {
|
|
250
|
+
PublicKey.fromString(publicKey, prefix);
|
|
251
|
+
return true;
|
|
252
|
+
} catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
export {
|
|
257
|
+
PrivateKey,
|
|
258
|
+
PublicKey,
|
|
259
|
+
Signature,
|
|
260
|
+
generateKeys,
|
|
261
|
+
isPublicKey,
|
|
262
|
+
isWif
|
|
263
|
+
};
|
|
264
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sources":["../../../src/auth/keys.ts"],"sourcesContent":["/**\n * Cryptographic key utilities for Zattera blockchain\n */\n\nimport * as secp256k1 from '@noble/secp256k1';\nimport { sha256 } from '@noble/hashes/sha2';\nimport { ripemd160 } from '@noble/hashes/legacy';\nimport bs58 from 'bs58';\n\nexport class PrivateKey {\n private key: Uint8Array;\n\n constructor(key: Uint8Array) {\n if (key.length !== 32) {\n throw new Error('Private key must be 32 bytes');\n }\n this.key = key;\n }\n\n /**\n * Create PrivateKey from WIF (Wallet Import Format) string\n */\n static fromWif(wif: string): PrivateKey {\n const decoded = bs58.decode(wif);\n\n // Check version byte (0x80)\n if (decoded[0] !== 0x80) {\n throw new Error('Invalid WIF version byte');\n }\n\n // Extract private key (remove version byte and checksum)\n const privateKey = decoded.slice(1, 33);\n const checksum = decoded.slice(33, 37);\n\n // Verify checksum\n const hash1 = sha256(decoded.slice(0, 33));\n const hash2 = sha256(hash1);\n const expectedChecksum = hash2.slice(0, 4);\n\n if (!arraysEqual(checksum, expectedChecksum)) {\n throw new Error('Invalid WIF checksum');\n }\n\n return new PrivateKey(privateKey);\n }\n\n /**\n * Create PrivateKey from seed string\n */\n static fromSeed(seed: string): PrivateKey {\n const hash = sha256(new TextEncoder().encode(seed));\n return new PrivateKey(hash);\n }\n\n /**\n * Generate PrivateKey from account name, password and role\n */\n static fromLogin(\n accountName: string,\n password: string,\n role: string = 'active'\n ): PrivateKey {\n const seed = accountName + role + password;\n const normalized = seed.trim().replace(/\\s+/g, ' ');\n return PrivateKey.fromSeed(normalized);\n }\n\n /**\n * Convert to WIF format\n */\n toWif(): string {\n // Add version byte (0x80)\n const versioned = new Uint8Array(33);\n versioned[0] = 0x80;\n versioned.set(this.key, 1);\n\n // Calculate checksum\n const hash1 = sha256(versioned);\n const hash2 = sha256(hash1);\n const checksum = hash2.slice(0, 4);\n\n // Concatenate and encode\n const result = new Uint8Array(37);\n result.set(versioned);\n result.set(checksum, 33);\n\n return bs58.encode(result);\n }\n\n /**\n * Get the public key\n */\n toPublic(): PublicKey {\n const publicKeyBytes = secp256k1.getPublicKey(this.key, true);\n return new PublicKey(publicKeyBytes);\n }\n\n /**\n * Get raw bytes\n */\n toBytes(): Uint8Array {\n return this.key;\n }\n\n /**\n * Sign a message hash\n * @param messageHash The message hash to sign\n * @returns Signature with recovery parameter\n */\n async sign(messageHash: Uint8Array): Promise<Signature> {\n // signAsync returns RecoveredSignature which has r, s, and recovery properties\n const sig = await secp256k1.signAsync(messageHash, this.key, {\n lowS: true, // Enforce canonical signatures (prevents malleability)\n });\n\n return new Signature({ signature: sig.toCompactRawBytes(), recovery: sig.recovery ?? 0 });\n }\n\n /**\n * Get shared secret with a public key (for ECIES encryption)\n * @param publicKey The public key to generate shared secret with\n * @returns Shared secret as Uint8Array\n */\n getSharedSecret(publicKey: PublicKey): Uint8Array {\n const sharedPoint = secp256k1.getSharedSecret(this.key, publicKey.toBytes(), true);\n // Remove the prefix byte (0x02 or 0x03) to get the 32-byte shared secret\n return sharedPoint.slice(1);\n }\n\n /**\n * Derive child key from offset (hierarchical key derivation)\n * @param offset Offset string for derivation\n * @returns New derived PrivateKey\n */\n child(offset: string): PrivateKey {\n const offsetHash = sha256(new TextEncoder().encode(offset));\n const offsetBigInt = BigInt('0x' + Array.from(offsetHash).map(b => b.toString(16).padStart(2, '0')).join(''));\n const keyBigInt = BigInt('0x' + Array.from(this.key).map(b => b.toString(16).padStart(2, '0')).join(''));\n\n // secp256k1 curve order\n const n = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141');\n const childKeyBigInt = (keyBigInt + offsetBigInt) % n;\n\n // Convert back to 32-byte array\n const childKeyHex = childKeyBigInt.toString(16).padStart(64, '0');\n const childKeyBytes = new Uint8Array(32);\n for (let i = 0; i < 32; i++) {\n childKeyBytes[i] = parseInt(childKeyHex.slice(i * 2, i * 2 + 2), 16);\n }\n\n return new PrivateKey(childKeyBytes);\n }\n}\n\nexport class PublicKey {\n private key: Uint8Array;\n\n constructor(key: Uint8Array) {\n if (key.length !== 33) {\n throw new Error('Compressed public key must be 33 bytes');\n }\n this.key = key;\n }\n\n /**\n * Create PublicKey from string (e.g., \"ZTR...\")\n */\n static fromString(publicKeyString: string, prefix: string = 'ZTR'): PublicKey {\n if (!publicKeyString.startsWith(prefix)) {\n throw new Error(`Public key must start with ${prefix}`);\n }\n\n const keyString = publicKeyString.substring(prefix.length);\n const decoded = bs58.decode(keyString);\n\n // Extract public key (remove checksum)\n const publicKey = decoded.slice(0, 33);\n const checksum = decoded.slice(33);\n\n // Verify checksum\n const hash = ripemd160(publicKey);\n const expectedChecksum = hash.slice(0, 4);\n\n if (!arraysEqual(checksum, expectedChecksum)) {\n throw new Error('Invalid public key checksum');\n }\n\n return new PublicKey(publicKey);\n }\n\n /**\n * Convert to string format (e.g., \"ZTR...\")\n */\n toString(prefix: string = 'ZTR'): string {\n const checksum = ripemd160(this.key).slice(0, 4);\n const result = new Uint8Array(37);\n result.set(this.key);\n result.set(checksum, 33);\n\n return prefix + bs58.encode(result);\n }\n\n /**\n * Get raw bytes\n */\n toBytes(): Uint8Array {\n return this.key;\n }\n\n /**\n * Verify a signature\n */\n async verify(messageHash: Uint8Array, signature: Signature): Promise<boolean> {\n try {\n return secp256k1.verify(signature.toCompact(), messageHash, this.key);\n } catch {\n return false;\n }\n }\n\n /**\n * Derive child key from offset (hierarchical key derivation)\n * Note: This is not implemented for public keys alone, derive from private key instead\n * @param _offset Offset string for derivation (unused)\n * @returns Never - throws error\n */\n child(_offset: string): PublicKey {\n // This would require point addition which is complex\n // For now, we'll derive from private key if available\n throw new Error('PublicKey.child() requires private key derivation');\n }\n}\n\nexport class Signature {\n private sig: Uint8Array;\n private recovery: number;\n\n constructor(sig: Uint8Array | { signature: Uint8Array; recovery: number }) {\n if (sig instanceof Uint8Array) {\n // Compact format: 65 bytes (recovery + r + s)\n if (sig.length !== 65) {\n throw new Error('Signature must be 65 bytes');\n }\n const recoveryByte = sig[0];\n if (recoveryByte === undefined) {\n throw new Error('Invalid signature: missing recovery byte');\n }\n this.recovery = recoveryByte - 27;\n this.sig = sig.slice(1);\n } else {\n // From secp256k1.signAsync result\n this.sig = sig.signature;\n this.recovery = sig.recovery ?? 0;\n }\n }\n\n /**\n * Get compact signature (65 bytes: recovery + r + s)\n */\n toCompact(): Uint8Array {\n return this.sig;\n }\n\n /**\n * Get full signature with recovery parameter (65 bytes)\n */\n toBuffer(): Uint8Array {\n const result = new Uint8Array(65);\n result[0] = this.recovery + 27;\n result.set(this.sig, 1);\n return result;\n }\n\n /**\n * Recover public key from signature and message hash\n */\n async recoverPublicKey(messageHash: Uint8Array): Promise<PublicKey> {\n // Create a Signature instance from the compact bytes\n const sig = secp256k1.Signature.fromCompact(this.sig);\n\n // Add recovery bit\n const recoveredSig = sig.addRecoveryBit(this.recovery);\n\n // Recover the public key point\n const point = recoveredSig.recoverPublicKey(messageHash);\n\n // Convert to compressed bytes\n const publicKeyBytes = point.toRawBytes(true);\n\n return new PublicKey(publicKeyBytes);\n }\n}\n\n/**\n * Helper function to compare two Uint8Arrays\n */\nfunction arraysEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * Generate all account keys from account name and password\n */\nexport function generateKeys(\n accountName: string,\n password: string,\n roles: string[] = ['owner', 'active', 'posting', 'memo']\n): Record<string, { private: string; public: string }> {\n const keys: Record<string, { private: string; public: string }> = {};\n\n for (const role of roles) {\n const privateKey = PrivateKey.fromLogin(accountName, password, role);\n const publicKey = privateKey.toPublic();\n\n keys[role] = {\n private: privateKey.toWif(),\n public: publicKey.toString(),\n };\n }\n\n return keys;\n}\n\n/**\n * Check if a string is a valid WIF private key\n */\nexport function isWif(wif: string): boolean {\n try {\n PrivateKey.fromWif(wif);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a string is a valid public key\n */\nexport function isPublicKey(publicKey: string, prefix: string = 'ZTR'): boolean {\n try {\n PublicKey.fromString(publicKey, prefix);\n return true;\n } catch {\n return false;\n }\n}\n"],"names":["secp256k1.getPublicKey","secp256k1.signAsync","secp256k1.getSharedSecret","secp256k1.verify","secp256k1.Signature"],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { sha512, sha256 } from "../node_modules/@noble/hashes/esm/sha2.js";
|
|
2
|
+
import { cbc } from "../node_modules/@noble/ciphers/aes.js";
|
|
3
|
+
import { randomBytes } from "../node_modules/@noble/ciphers/utils.js";
|
|
4
|
+
import bs58 from "../node_modules/bs58/src/esm/index.js";
|
|
5
|
+
function generateNonce(size = 8) {
|
|
6
|
+
return randomBytes(size);
|
|
7
|
+
}
|
|
8
|
+
function encodeMemo(privateKey, publicKey, memo, nonce) {
|
|
9
|
+
if (!memo.startsWith("#")) {
|
|
10
|
+
memo = "#" + memo;
|
|
11
|
+
}
|
|
12
|
+
const message = memo.slice(1);
|
|
13
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
14
|
+
const nonceBytes = nonce ?? generateNonce(8);
|
|
15
|
+
const sharedSecret = privateKey.getSharedSecret(publicKey);
|
|
16
|
+
const combined = new Uint8Array(nonceBytes.length + sharedSecret.length);
|
|
17
|
+
combined.set(nonceBytes);
|
|
18
|
+
combined.set(sharedSecret, nonceBytes.length);
|
|
19
|
+
const hash = sha512(combined);
|
|
20
|
+
const iv = hash.slice(0, 16);
|
|
21
|
+
const encryptionKey = hash.slice(16, 48);
|
|
22
|
+
const cipher = cbc(encryptionKey, iv);
|
|
23
|
+
const encrypted = cipher.encrypt(messageBytes);
|
|
24
|
+
const checksumHash = sha256(encryptionKey);
|
|
25
|
+
const checksum = checksumHash.slice(0, 4);
|
|
26
|
+
const serialized = new Uint8Array(nonceBytes.length + checksum.length + encrypted.length);
|
|
27
|
+
serialized.set(nonceBytes, 0);
|
|
28
|
+
serialized.set(checksum, nonceBytes.length);
|
|
29
|
+
serialized.set(encrypted, nonceBytes.length + checksum.length);
|
|
30
|
+
return "#" + bs58.encode(serialized);
|
|
31
|
+
}
|
|
32
|
+
function decodeMemo(_privateKey, memo) {
|
|
33
|
+
if (!memo.startsWith("#")) {
|
|
34
|
+
return memo;
|
|
35
|
+
}
|
|
36
|
+
throw new Error("decodeMemo requires sender public key - use decodeMemoWithKey instead");
|
|
37
|
+
}
|
|
38
|
+
function decodeMemoWithKey(privateKey, publicKey, memo) {
|
|
39
|
+
if (!memo.startsWith("#")) {
|
|
40
|
+
return memo;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const decoded = bs58.decode(memo.slice(1));
|
|
44
|
+
const nonce = decoded.slice(0, 8);
|
|
45
|
+
const checksum = decoded.slice(8, 12);
|
|
46
|
+
const encrypted = decoded.slice(12);
|
|
47
|
+
const sharedSecret = privateKey.getSharedSecret(publicKey);
|
|
48
|
+
const combined = new Uint8Array(nonce.length + sharedSecret.length);
|
|
49
|
+
combined.set(nonce);
|
|
50
|
+
combined.set(sharedSecret, nonce.length);
|
|
51
|
+
const hash = sha512(combined);
|
|
52
|
+
const iv = hash.slice(0, 16);
|
|
53
|
+
const encryptionKey = hash.slice(16, 48);
|
|
54
|
+
const checksumHash = sha256(encryptionKey);
|
|
55
|
+
const expectedChecksum = checksumHash.slice(0, 4);
|
|
56
|
+
let checksumValid = true;
|
|
57
|
+
for (let i = 0; i < 4; i++) {
|
|
58
|
+
if (checksum[i] !== expectedChecksum[i]) {
|
|
59
|
+
checksumValid = false;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!checksumValid) {
|
|
64
|
+
throw new Error("Invalid memo checksum");
|
|
65
|
+
}
|
|
66
|
+
const decipher = cbc(encryptionKey, iv);
|
|
67
|
+
const decrypted = decipher.decrypt(encrypted);
|
|
68
|
+
const message = new TextDecoder().decode(decrypted);
|
|
69
|
+
return "#" + message;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw new Error(`Failed to decode memo: ${error instanceof Error ? error.message : String(error)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
decodeMemo,
|
|
76
|
+
decodeMemoWithKey,
|
|
77
|
+
encodeMemo
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=memo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memo.js","sources":["../../../src/auth/memo.ts"],"sourcesContent":["/**\n * Memo encryption/decryption for Zattera blockchain\n * Based on ECIES (Elliptic Curve Integrated Encryption Scheme)\n */\n\nimport { sha256, sha512 } from '@noble/hashes/sha2';\nimport * as aes from '@noble/ciphers/aes.js';\nimport { randomBytes } from '@noble/ciphers/utils.js';\nimport bs58 from 'bs58';\nimport { PrivateKey, PublicKey } from './keys.js';\n\n/**\n * Generate a random nonce\n * @param size Size of nonce in bytes (default: 8)\n * @returns Random nonce\n */\nfunction generateNonce(size: number = 8): Uint8Array {\n return randomBytes(size);\n}\n\n/**\n * Encode a memo for encryption\n * @param privateKey Sender's private key\n * @param publicKey Recipient's public key\n * @param memo Memo text (will be prefixed with # if not already)\n * @param nonce Optional nonce for testing (default: random)\n * @returns Encrypted memo string with # prefix\n *\n * @example\n * ```typescript\n * const senderKey = PrivateKey.fromSeed('sender seed');\n * const recipientPubKey = PrivateKey.fromSeed('recipient seed').toPublic();\n * const encrypted = await encodeMemo(senderKey, recipientPubKey, 'Hello!');\n * // Returns: \"#<base58_encoded_encrypted_data>\"\n * ```\n */\nexport function encodeMemo(\n privateKey: PrivateKey,\n publicKey: PublicKey,\n memo: string,\n nonce?: Uint8Array\n): string {\n // If memo doesn't start with #, it's not encrypted\n if (!memo.startsWith('#')) {\n memo = '#' + memo;\n }\n\n // Remove the # prefix for encryption\n const message = memo.slice(1);\n const messageBytes = new TextEncoder().encode(message);\n\n // Generate or use provided nonce\n const nonceBytes = nonce ?? generateNonce(8);\n\n // Get shared secret using ECDH\n const sharedSecret = privateKey.getSharedSecret(publicKey);\n\n // Derive encryption key and IV from shared secret and nonce\n // Key derivation: SHA512(nonce || shared_secret)\n const combined = new Uint8Array(nonceBytes.length + sharedSecret.length);\n combined.set(nonceBytes);\n combined.set(sharedSecret, nonceBytes.length);\n\n const hash = sha512(combined);\n\n // Split hash into IV (first 16 bytes) and key (next 32 bytes)\n const iv = hash.slice(0, 16);\n const encryptionKey = hash.slice(16, 48);\n\n // Encrypt the message\n const cipher = aes.cbc(encryptionKey, iv);\n const encrypted = cipher.encrypt(messageBytes);\n\n // Calculate checksum (first 4 bytes of SHA256 of encryption key)\n const checksumHash = sha256(encryptionKey);\n const checksum = checksumHash.slice(0, 4);\n\n // Serialize: nonce (8 bytes) + checksum (4 bytes) + encrypted data\n const serialized = new Uint8Array(nonceBytes.length + checksum.length + encrypted.length);\n serialized.set(nonceBytes, 0);\n serialized.set(checksum, nonceBytes.length);\n serialized.set(encrypted, nonceBytes.length + checksum.length);\n\n // Encode to base58 and add # prefix\n return '#' + bs58.encode(serialized);\n}\n\n/**\n * Decode an encrypted memo\n *\n * Note: This function is deprecated. Use decodeMemoWithKey instead, as it requires\n * the sender's public key to properly decrypt the memo.\n *\n * @param _privateKey Recipient's private key (unused)\n * @param memo Encrypted memo string (must start with #)\n * @returns Never - throws error directing to use decodeMemoWithKey\n * @deprecated Use decodeMemoWithKey instead\n */\nexport function decodeMemo(_privateKey: PrivateKey, memo: string): string {\n // If memo doesn't start with #, it's not encrypted\n if (!memo.startsWith('#')) {\n return memo;\n }\n\n throw new Error('decodeMemo requires sender public key - use decodeMemoWithKey instead');\n}\n\n/**\n * Decode an encrypted memo with explicit public key\n * @param privateKey Recipient's private key\n * @param publicKey Sender's public key\n * @param memo Encrypted memo string (must start with #)\n * @returns Decrypted memo text (with # prefix)\n *\n * @example\n * ```typescript\n * const recipientKey = PrivateKey.fromSeed('recipient seed');\n * const senderPubKey = PrivateKey.fromSeed('sender seed').toPublic();\n * const decrypted = await decodeMemoWithKey(recipientKey, senderPubKey, encryptedMemo);\n * // Returns: \"#Hello!\"\n * ```\n */\nexport function decodeMemoWithKey(\n privateKey: PrivateKey,\n publicKey: PublicKey,\n memo: string\n): string {\n // If memo doesn't start with #, it's not encrypted\n if (!memo.startsWith('#')) {\n return memo;\n }\n\n try {\n // Remove # prefix and decode from base58\n const decoded = bs58.decode(memo.slice(1));\n\n // Extract components\n const nonce = decoded.slice(0, 8);\n const checksum = decoded.slice(8, 12);\n const encrypted = decoded.slice(12);\n\n // Get shared secret using ECDH\n const sharedSecret = privateKey.getSharedSecret(publicKey);\n\n // Derive encryption key and IV\n const combined = new Uint8Array(nonce.length + sharedSecret.length);\n combined.set(nonce);\n combined.set(sharedSecret, nonce.length);\n\n const hash = sha512(combined);\n const iv = hash.slice(0, 16);\n const encryptionKey = hash.slice(16, 48);\n\n // Verify checksum\n const checksumHash = sha256(encryptionKey);\n const expectedChecksum = checksumHash.slice(0, 4);\n\n let checksumValid = true;\n for (let i = 0; i < 4; i++) {\n if (checksum[i] !== expectedChecksum[i]) {\n checksumValid = false;\n break;\n }\n }\n\n if (!checksumValid) {\n throw new Error('Invalid memo checksum');\n }\n\n // Decrypt the message\n const decipher = aes.cbc(encryptionKey, iv);\n const decrypted = decipher.decrypt(encrypted);\n\n // Convert to string and add # prefix\n const message = new TextDecoder().decode(decrypted);\n return '#' + message;\n } catch (error) {\n throw new Error(`Failed to decode memo: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n"],"names":["aes.cbc"],"mappings":";;;;AAgBA,SAAS,cAAc,OAAe,GAAe;AACnD,SAAO,YAAY,IAAI;AACzB;AAkBO,SAAS,WACd,YACA,WACA,MACA,OACQ;AAER,MAAI,CAAC,KAAK,WAAW,GAAG,GAAG;AACzB,WAAO,MAAM;AAAA,EACf;AAGA,QAAM,UAAU,KAAK,MAAM,CAAC;AAC5B,QAAM,eAAe,IAAI,cAAc,OAAO,OAAO;AAGrD,QAAM,aAAa,SAAS,cAAc,CAAC;AAG3C,QAAM,eAAe,WAAW,gBAAgB,SAAS;AAIzD,QAAM,WAAW,IAAI,WAAW,WAAW,SAAS,aAAa,MAAM;AACvE,WAAS,IAAI,UAAU;AACvB,WAAS,IAAI,cAAc,WAAW,MAAM;AAE5C,QAAM,OAAO,OAAO,QAAQ;AAG5B,QAAM,KAAK,KAAK,MAAM,GAAG,EAAE;AAC3B,QAAM,gBAAgB,KAAK,MAAM,IAAI,EAAE;AAGvC,QAAM,SAASA,IAAQ,eAAe,EAAE;AACxC,QAAM,YAAY,OAAO,QAAQ,YAAY;AAG7C,QAAM,eAAe,OAAO,aAAa;AACzC,QAAM,WAAW,aAAa,MAAM,GAAG,CAAC;AAGxC,QAAM,aAAa,IAAI,WAAW,WAAW,SAAS,SAAS,SAAS,UAAU,MAAM;AACxF,aAAW,IAAI,YAAY,CAAC;AAC5B,aAAW,IAAI,UAAU,WAAW,MAAM;AAC1C,aAAW,IAAI,WAAW,WAAW,SAAS,SAAS,MAAM;AAG7D,SAAO,MAAM,KAAK,OAAO,UAAU;AACrC;AAaO,SAAS,WAAW,aAAyB,MAAsB;AAExE,MAAI,CAAC,KAAK,WAAW,GAAG,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,QAAM,IAAI,MAAM,uEAAuE;AACzF;AAiBO,SAAS,kBACd,YACA,WACA,MACQ;AAER,MAAI,CAAC,KAAK,WAAW,GAAG,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,MAAI;AAEF,UAAM,UAAU,KAAK,OAAO,KAAK,MAAM,CAAC,CAAC;AAGzC,UAAM,QAAQ,QAAQ,MAAM,GAAG,CAAC;AAChC,UAAM,WAAW,QAAQ,MAAM,GAAG,EAAE;AACpC,UAAM,YAAY,QAAQ,MAAM,EAAE;AAGlC,UAAM,eAAe,WAAW,gBAAgB,SAAS;AAGzD,UAAM,WAAW,IAAI,WAAW,MAAM,SAAS,aAAa,MAAM;AAClE,aAAS,IAAI,KAAK;AAClB,aAAS,IAAI,cAAc,MAAM,MAAM;AAEvC,UAAM,OAAO,OAAO,QAAQ;AAC5B,UAAM,KAAK,KAAK,MAAM,GAAG,EAAE;AAC3B,UAAM,gBAAgB,KAAK,MAAM,IAAI,EAAE;AAGvC,UAAM,eAAe,OAAO,aAAa;AACzC,UAAM,mBAAmB,aAAa,MAAM,GAAG,CAAC;AAEhD,QAAI,gBAAgB;AACpB,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAI,SAAS,CAAC,MAAM,iBAAiB,CAAC,GAAG;AACvC,wBAAgB;AAChB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,uBAAuB;AAAA,IACzC;AAGA,UAAM,WAAWA,IAAQ,eAAe,EAAE;AAC1C,UAAM,YAAY,SAAS,QAAQ,SAAS;AAG5C,UAAM,UAAU,IAAI,cAAc,OAAO,SAAS;AAClD,WAAO,MAAM;AAAA,EACf,SAAS,OAAO;AACd,UAAM,IAAI,MAAM,0BAA0B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AAAA,EACpG;AACF;"}
|