QuipSigner: Deterministic Winternitz keypair generator using quantum secret and vaultId
| 118 | // QuipSigner: Deterministic Winternitz keypair generator using quantum secret |
| 119 | // and vaultId |
| 120 | class QuipSigner { |
| 121 | public: |
| 122 | QuipSigner(const std::array<uint8_t, 32> &quantum_secret_raw) |
| 123 | : wots_(keccak256) { |
| 124 | // Store the raw quantum secret, hashing happens in recoverKeyPair |
| 125 | quantum_secret_ = quantum_secret_raw; |
| 126 | } |
| 127 | |
| 128 | // Generate a NEW keypair for a given vaultId using a random public seed |
| 129 | // This is used for creating the NEXT keypair during transfers |
| 130 | std::pair<WinternitzAddress, std::vector<std::array<uint8_t, 32>>> |
| 131 | generateKeyPair(const std::array<uint8_t, 32> &vaultId) { |
| 132 | // Use a cryptographically secure random 32-byte public seed for the next PQ owner |
| 133 | std::array<uint8_t, 32> public_seed = randomSeed32(); |
| 134 | return recoverKeyPair(vaultId, public_seed); |
| 135 | } |
| 136 | |
| 137 | // Deterministically recover keypair for a given vaultId and publicSeed |
| 138 | std::pair<WinternitzAddress, std::vector<std::array<uint8_t, 32>>> |
| 139 | recoverKeyPair(const std::array<uint8_t, 32> &vaultId, |
| 140 | const std::array<uint8_t, 32> &publicSeed) { |
| 141 | // Create the private seed by concatenating the quantum secret and |
| 142 | // the vaultId, then hash it (matching TypeScript implementation) |
| 143 | std::vector<uint8_t> concat(quantum_secret_.begin(), |
| 144 | quantum_secret_.end()); |
| 145 | concat.insert(concat.end(), vaultId.begin(), vaultId.end()); |
| 146 | std::array<uint8_t, 32> private_seed_array = keccak256(concat); |
| 147 | std::vector<uint8_t> private_seed(private_seed_array.begin(), private_seed_array.end()); |
| 148 | |
| 149 | |
| 150 | // Generate keypair using the two-parameter interface (protocol-compatible) |
| 151 | auto [public_key, private_key] = |
| 152 | wots_.generate_key_pair(private_seed, publicSeed); |
| 153 | |
| 154 | // Create WinternitzAddress from the generated public key |
| 155 | WinternitzAddress pqAddress; |
| 156 | std::copy(public_key.begin(), public_key.begin() + 32, |
| 157 | pqAddress.publicSeed.begin()); |
| 158 | std::copy(public_key.begin() + 32, public_key.end(), |
| 159 | pqAddress.publicKeyHash.begin()); |
| 160 | return {pqAddress, private_key}; |
| 161 | } |
| 162 | |
| 163 | // Sign a message using a recovered keypair |
| 164 | Signature sign(const std::array<uint8_t, 32> &message, |
| 165 | const std::vector<std::array<uint8_t, 32>> &private_key, |
| 166 | const std::array<uint8_t, 32> &publicSeed) { |
| 167 | // Sign the message with the provided private key and public seed |
| 168 | return wots_.sign(private_key, publicSeed, message); |
| 169 | } |
| 170 | |
| 171 | private: |
| 172 | std::array<uint8_t, 32> quantum_secret_; |
| 173 | hashsigs::WOTSPlus wots_; |
| 174 | }; |
| 175 | |
| 176 | CLI::CLI(const std::string &rpc_url, const std::string &contract_address) |
| 177 | : rpc_url_(rpc_url), contract_address_(contract_address) { |
nothing calls this directly
no outgoing calls
no test coverage detected