mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
9052f0d32e
@ -359,6 +359,7 @@ public struct PresentationGroupCallRequestedVideo: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var audioSsrc: UInt32
|
public var audioSsrc: UInt32
|
||||||
|
public var peerId: Int64
|
||||||
public var endpointId: String
|
public var endpointId: String
|
||||||
public var ssrcGroups: [SsrcGroup]
|
public var ssrcGroups: [SsrcGroup]
|
||||||
public var minQuality: Quality
|
public var minQuality: Quality
|
||||||
@ -383,7 +384,7 @@ public extension GroupCallParticipantsContext.Participant {
|
|||||||
guard let videoDescription = self.videoDescription else {
|
guard let videoDescription = self.videoDescription else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return PresentationGroupCallRequestedVideo(audioSsrc: audioSsrc, endpointId: videoDescription.endpointId, ssrcGroups: videoDescription.ssrcGroups.map { group in
|
return PresentationGroupCallRequestedVideo(audioSsrc: audioSsrc, peerId: self.peer.id.id._internalGetInt64Value(), endpointId: videoDescription.endpointId, ssrcGroups: videoDescription.ssrcGroups.map { group in
|
||||||
PresentationGroupCallRequestedVideo.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs)
|
PresentationGroupCallRequestedVideo.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs)
|
||||||
}, minQuality: minQuality, maxQuality: maxQuality)
|
}, minQuality: minQuality, maxQuality: maxQuality)
|
||||||
}
|
}
|
||||||
@ -395,7 +396,7 @@ public extension GroupCallParticipantsContext.Participant {
|
|||||||
guard let presentationDescription = self.presentationDescription else {
|
guard let presentationDescription = self.presentationDescription else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return PresentationGroupCallRequestedVideo(audioSsrc: audioSsrc, endpointId: presentationDescription.endpointId, ssrcGroups: presentationDescription.ssrcGroups.map { group in
|
return PresentationGroupCallRequestedVideo(audioSsrc: audioSsrc, peerId: self.peer.id.id._internalGetInt64Value(), endpointId: presentationDescription.endpointId, ssrcGroups: presentationDescription.ssrcGroups.map { group in
|
||||||
PresentationGroupCallRequestedVideo.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs)
|
PresentationGroupCallRequestedVideo.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs)
|
||||||
}, minQuality: minQuality, maxQuality: maxQuality)
|
}, minQuality: minQuality, maxQuality: maxQuality)
|
||||||
}
|
}
|
||||||
|
@ -9904,9 +9904,10 @@ public extension Api.functions.phone {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.phone {
|
public extension Api.functions.phone {
|
||||||
static func deleteConferenceCallParticipants(call: Api.InputGroupCall, ids: [Int64], block: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
static func deleteConferenceCallParticipants(flags: Int32, call: Api.InputGroupCall, ids: [Int64], block: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-115142380)
|
buffer.appendInt32(-1935276763)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
call.serialize(buffer, true)
|
call.serialize(buffer, true)
|
||||||
buffer.appendInt32(481674261)
|
buffer.appendInt32(481674261)
|
||||||
buffer.appendInt32(Int32(ids.count))
|
buffer.appendInt32(Int32(ids.count))
|
||||||
@ -9914,7 +9915,7 @@ public extension Api.functions.phone {
|
|||||||
serializeInt64(item, buffer: buffer, boxed: false)
|
serializeInt64(item, buffer: buffer, boxed: false)
|
||||||
}
|
}
|
||||||
serializeBytes(block, buffer: buffer, boxed: false)
|
serializeBytes(block, buffer: buffer, boxed: false)
|
||||||
return (FunctionDescription(name: "phone.deleteConferenceCallParticipants", parameters: [("call", String(describing: call)), ("ids", String(describing: ids)), ("block", String(describing: block))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
return (FunctionDescription(name: "phone.deleteConferenceCallParticipants", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("ids", String(describing: ids)), ("block", String(describing: block))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.Updates?
|
var result: Api.Updates?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
|
@ -752,8 +752,8 @@ private final class ConferenceCallE2EContextStateImpl: ConferenceCallE2EContextS
|
|||||||
return self.call.encrypt(message)
|
return self.call.encrypt(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decrypt(message: Data) -> Data? {
|
func decrypt(message: Data, userId: Int64) -> Data? {
|
||||||
return self.call.decrypt(message)
|
return self.call.decrypt(message, userId: userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1240,13 +1240,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
engine: accountContext.engine,
|
engine: accountContext.engine,
|
||||||
callId: initialCall.description.id,
|
callId: initialCall.description.id,
|
||||||
accessHash: initialCall.description.accessHash,
|
accessHash: initialCall.description.accessHash,
|
||||||
|
userId: accountContext.account.peerId.id._internalGetInt64Value(),
|
||||||
reference: initialCall.reference,
|
reference: initialCall.reference,
|
||||||
keyPair: keyPair,
|
keyPair: keyPair,
|
||||||
initializeState: { keyPair, block in
|
initializeState: { keyPair, userId, block in
|
||||||
guard let keyPair = TdKeyPair(keyId: keyPair.id, publicKey: keyPair.publicKey.data) else {
|
guard let keyPair = TdKeyPair(keyId: keyPair.id, publicKey: keyPair.publicKey.data) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let call = TdCall.make(with: keyPair, latestBlock: block) else {
|
guard let call = TdCall.make(with: keyPair, userId: userId, latestBlock: block) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return ConferenceCallE2EContextStateImpl(call: call)
|
return ConferenceCallE2EContextStateImpl(call: call)
|
||||||
@ -2103,8 +2104,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
return self.e2eCall.with({ $0.state?.encrypt(message: message) })
|
return self.e2eCall.with({ $0.state?.encrypt(message: message) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func decrypt(message: Data) -> Data? {
|
func decrypt(message: Data, userId: Int64) -> Data? {
|
||||||
return self.e2eCall.with({ $0.state?.decrypt(message: message) })
|
return self.e2eCall.with({ $0.state?.decrypt(message: message, userId: userId) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3666,6 +3667,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
return OngoingGroupCallContext.VideoChannel(
|
return OngoingGroupCallContext.VideoChannel(
|
||||||
audioSsrc: item.audioSsrc,
|
audioSsrc: item.audioSsrc,
|
||||||
|
peerId: item.peerId,
|
||||||
endpointId: item.endpointId,
|
endpointId: item.endpointId,
|
||||||
ssrcGroups: item.ssrcGroups.map { group in
|
ssrcGroups: item.ssrcGroups.map { group in
|
||||||
return OngoingGroupCallContext.VideoChannel.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs)
|
return OngoingGroupCallContext.VideoChannel.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs)
|
||||||
|
@ -13,7 +13,7 @@ public protocol ConferenceCallE2EContextState: AnyObject {
|
|||||||
func takeOutgoingBroadcastBlocks() -> [Data]
|
func takeOutgoingBroadcastBlocks() -> [Data]
|
||||||
|
|
||||||
func encrypt(message: Data) -> Data?
|
func encrypt(message: Data) -> Data?
|
||||||
func decrypt(message: Data) -> Data?
|
func decrypt(message: Data, userId: Int64) -> Data?
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ConferenceCallE2EContext {
|
public final class ConferenceCallE2EContext {
|
||||||
@ -31,9 +31,10 @@ public final class ConferenceCallE2EContext {
|
|||||||
private let engine: TelegramEngine
|
private let engine: TelegramEngine
|
||||||
private let callId: Int64
|
private let callId: Int64
|
||||||
private let accessHash: Int64
|
private let accessHash: Int64
|
||||||
|
private let userId: Int64
|
||||||
private let reference: InternalGroupCallReference
|
private let reference: InternalGroupCallReference
|
||||||
private let state: Atomic<ContextStateHolder>
|
private let state: Atomic<ContextStateHolder>
|
||||||
private let initializeState: (TelegramKeyPair, Data) -> ConferenceCallE2EContextState?
|
private let initializeState: (TelegramKeyPair, Int64, Data) -> ConferenceCallE2EContextState?
|
||||||
private let keyPair: TelegramKeyPair
|
private let keyPair: TelegramKeyPair
|
||||||
|
|
||||||
let e2eEncryptionKeyHashValue = ValuePromise<Data?>(nil)
|
let e2eEncryptionKeyHashValue = ValuePromise<Data?>(nil)
|
||||||
@ -52,7 +53,7 @@ public final class ConferenceCallE2EContext {
|
|||||||
private var synchronizeRemovedParticipantsDisposable: Disposable?
|
private var synchronizeRemovedParticipantsDisposable: Disposable?
|
||||||
private var synchronizeRemovedParticipantsTimer: Foundation.Timer?
|
private var synchronizeRemovedParticipantsTimer: Foundation.Timer?
|
||||||
|
|
||||||
init(queue: Queue, engine: TelegramEngine, callId: Int64, accessHash: Int64, reference: InternalGroupCallReference, state: Atomic<ContextStateHolder>, initializeState: @escaping (TelegramKeyPair, Data) -> ConferenceCallE2EContextState?, keyPair: TelegramKeyPair) {
|
init(queue: Queue, engine: TelegramEngine, callId: Int64, accessHash: Int64, userId: Int64, reference: InternalGroupCallReference, state: Atomic<ContextStateHolder>, initializeState: @escaping (TelegramKeyPair, Int64, Data) -> ConferenceCallE2EContextState?, keyPair: TelegramKeyPair) {
|
||||||
precondition(queue.isCurrent())
|
precondition(queue.isCurrent())
|
||||||
precondition(Queue.mainQueue().isCurrent())
|
precondition(Queue.mainQueue().isCurrent())
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ public final class ConferenceCallE2EContext {
|
|||||||
self.engine = engine
|
self.engine = engine
|
||||||
self.callId = callId
|
self.callId = callId
|
||||||
self.accessHash = accessHash
|
self.accessHash = accessHash
|
||||||
|
self.userId = userId
|
||||||
self.reference = reference
|
self.reference = reference
|
||||||
self.state = state
|
self.state = state
|
||||||
self.initializeState = initializeState
|
self.initializeState = initializeState
|
||||||
@ -125,6 +127,7 @@ public final class ConferenceCallE2EContext {
|
|||||||
|
|
||||||
private func addE2EBlocks(blocks: [Data], subChainId: Int) {
|
private func addE2EBlocks(blocks: [Data], subChainId: Int) {
|
||||||
let keyPair = self.keyPair
|
let keyPair = self.keyPair
|
||||||
|
let userId = self.userId
|
||||||
let initializeState = self.initializeState
|
let initializeState = self.initializeState
|
||||||
let (outBlocks, outEmoji) = self.state.with({ callState -> ([Data], Data) in
|
let (outBlocks, outEmoji) = self.state.with({ callState -> ([Data], Data) in
|
||||||
if let state = callState.state {
|
if let state = callState.state {
|
||||||
@ -141,7 +144,7 @@ public final class ConferenceCallE2EContext {
|
|||||||
guard let block = blocks.last else {
|
guard let block = blocks.last else {
|
||||||
return ([], Data())
|
return ([], Data())
|
||||||
}
|
}
|
||||||
guard let state = initializeState(keyPair, block) else {
|
guard let state = initializeState(keyPair, userId, block) else {
|
||||||
return ([], Data())
|
return ([], Data())
|
||||||
}
|
}
|
||||||
callState.state = state
|
callState.state = state
|
||||||
@ -286,7 +289,7 @@ public final class ConferenceCallE2EContext {
|
|||||||
return .single(false)
|
return .single(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return engine.calls.removeGroupCallBlockchainParticipants(callId: callId, accessHash: accessHash, participantIds: removedPeerIds, block: removeBlock)
|
return engine.calls.removeGroupCallBlockchainParticipants(callId: callId, accessHash: accessHash, mode: .cleanup, participantIds: removedPeerIds, block: removeBlock)
|
||||||
|> map { result -> Bool in
|
|> map { result -> Bool in
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
@ -321,11 +324,11 @@ public final class ConferenceCallE2EContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(engine: TelegramEngine, callId: Int64, accessHash: Int64, reference: InternalGroupCallReference, keyPair: TelegramKeyPair, initializeState: @escaping (TelegramKeyPair, Data) -> ConferenceCallE2EContextState?) {
|
public init(engine: TelegramEngine, callId: Int64, accessHash: Int64, userId: Int64, reference: InternalGroupCallReference, keyPair: TelegramKeyPair, initializeState: @escaping (TelegramKeyPair, Int64, Data) -> ConferenceCallE2EContextState?) {
|
||||||
let queue = Queue.mainQueue()
|
let queue = Queue.mainQueue()
|
||||||
let state = self.state
|
let state = self.state
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
return Impl(queue: queue, engine: engine, callId: callId, accessHash: accessHash, reference: reference, state: state, initializeState: initializeState, keyPair: keyPair)
|
return Impl(queue: queue, engine: engine, callId: callId, accessHash: accessHash, userId: userId, reference: reference, state: state, initializeState: initializeState, keyPair: keyPair)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -865,13 +865,25 @@ func _internal_inviteConferenceCallParticipant(account: Account, reference: Inte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum RemoveGroupCallBlockchainParticipantsMode {
|
||||||
|
case kick
|
||||||
|
case cleanup
|
||||||
|
}
|
||||||
|
|
||||||
public enum RemoveGroupCallBlockchainParticipantsResult {
|
public enum RemoveGroupCallBlockchainParticipantsResult {
|
||||||
case success
|
case success
|
||||||
case pollBlocksAndRetry
|
case pollBlocksAndRetry
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_removeGroupCallBlockchainParticipants(account: Account, callId: Int64, accessHash: Int64, participantIds: [Int64], block: Data) -> Signal<RemoveGroupCallBlockchainParticipantsResult, NoError> {
|
func _internal_removeGroupCallBlockchainParticipants(account: Account, callId: Int64, accessHash: Int64, mode: RemoveGroupCallBlockchainParticipantsMode, participantIds: [Int64], block: Data) -> Signal<RemoveGroupCallBlockchainParticipantsResult, NoError> {
|
||||||
return account.network.request(Api.functions.phone.deleteConferenceCallParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), ids: participantIds, block: Buffer(data: block)))
|
var flags: Int32 = 0
|
||||||
|
switch mode {
|
||||||
|
case .kick:
|
||||||
|
flags |= 1 << 1
|
||||||
|
case .cleanup:
|
||||||
|
flags |= 1 << 0
|
||||||
|
}
|
||||||
|
return account.network.request(Api.functions.phone.deleteConferenceCallParticipants(flags: flags, call: .inputGroupCall(id: callId, accessHash: accessHash), ids: participantIds, block: Buffer(data: block)))
|
||||||
|> map { updates -> RemoveGroupCallBlockchainParticipantsResult in
|
|> map { updates -> RemoveGroupCallBlockchainParticipantsResult in
|
||||||
account.stateManager.addUpdates(updates)
|
account.stateManager.addUpdates(updates)
|
||||||
return .success
|
return .success
|
||||||
|
@ -113,8 +113,8 @@ public extension TelegramEngine {
|
|||||||
return _internal_inviteConferenceCallParticipant(account: self.account, reference: reference, peerId: peerId, isVideo: isVideo)
|
return _internal_inviteConferenceCallParticipant(account: self.account, reference: reference, peerId: peerId, isVideo: isVideo)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeGroupCallBlockchainParticipants(callId: Int64, accessHash: Int64, participantIds: [Int64], block: Data) -> Signal<RemoveGroupCallBlockchainParticipantsResult, NoError> {
|
public func removeGroupCallBlockchainParticipants(callId: Int64, accessHash: Int64, mode: RemoveGroupCallBlockchainParticipantsMode, participantIds: [Int64], block: Data) -> Signal<RemoveGroupCallBlockchainParticipantsResult, NoError> {
|
||||||
return _internal_removeGroupCallBlockchainParticipants(account: self.account, callId: callId, accessHash: accessHash, participantIds: participantIds, block: block)
|
return _internal_removeGroupCallBlockchainParticipants(account: self.account, callId: callId, accessHash: accessHash, mode: mode, participantIds: participantIds, block: block)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func clearCachedGroupCallDisplayAsAvailablePeers(peerId: PeerId) -> Signal<Never, NoError> {
|
public func clearCachedGroupCallDisplayAsAvailablePeers(peerId: PeerId) -> Signal<Never, NoError> {
|
||||||
|
@ -218,7 +218,7 @@ final class OngoingGroupCallBroadcastPartTaskImpl: NSObject, OngoingGroupCallBro
|
|||||||
|
|
||||||
public protocol OngoingGroupCallEncryptionContext: AnyObject {
|
public protocol OngoingGroupCallEncryptionContext: AnyObject {
|
||||||
func encrypt(message: Data) -> Data?
|
func encrypt(message: Data) -> Data?
|
||||||
func decrypt(message: Data) -> Data?
|
func decrypt(message: Data, userId: Int64) -> Data?
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class OngoingGroupCallContext {
|
public final class OngoingGroupCallContext {
|
||||||
@ -300,13 +300,15 @@ public final class OngoingGroupCallContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var audioSsrc: UInt32
|
public var audioSsrc: UInt32
|
||||||
|
public var peerId: Int64
|
||||||
public var endpointId: String
|
public var endpointId: String
|
||||||
public var ssrcGroups: [SsrcGroup]
|
public var ssrcGroups: [SsrcGroup]
|
||||||
public var minQuality: Quality
|
public var minQuality: Quality
|
||||||
public var maxQuality: Quality
|
public var maxQuality: Quality
|
||||||
|
|
||||||
public init(audioSsrc: UInt32, endpointId: String, ssrcGroups: [SsrcGroup], minQuality: Quality, maxQuality: Quality) {
|
public init(audioSsrc: UInt32, peerId: Int64, endpointId: String, ssrcGroups: [SsrcGroup], minQuality: Quality, maxQuality: Quality) {
|
||||||
self.audioSsrc = audioSsrc
|
self.audioSsrc = audioSsrc
|
||||||
|
self.peerId = peerId
|
||||||
self.endpointId = endpointId
|
self.endpointId = endpointId
|
||||||
self.ssrcGroups = ssrcGroups
|
self.ssrcGroups = ssrcGroups
|
||||||
self.minQuality = minQuality
|
self.minQuality = minQuality
|
||||||
@ -643,11 +645,11 @@ public final class OngoingGroupCallContext {
|
|||||||
isConference: isConference,
|
isConference: isConference,
|
||||||
isActiveByDefault: audioIsActiveByDefault,
|
isActiveByDefault: audioIsActiveByDefault,
|
||||||
encryptDecrypt: encryptionContext.flatMap { encryptionContext in
|
encryptDecrypt: encryptionContext.flatMap { encryptionContext in
|
||||||
return { data, isEncrypt in
|
return { data, userId, isEncrypt in
|
||||||
if isEncrypt {
|
if isEncrypt {
|
||||||
return encryptionContext.encrypt(message: data)
|
return encryptionContext.encrypt(message: data)
|
||||||
} else {
|
} else {
|
||||||
return encryptionContext.decrypt(message: data)
|
return encryptionContext.decrypt(message: data, userId: userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -897,6 +899,7 @@ public final class OngoingGroupCallContext {
|
|||||||
}
|
}
|
||||||
return OngoingGroupCallRequestedVideoChannel(
|
return OngoingGroupCallRequestedVideoChannel(
|
||||||
audioSsrc: channel.audioSsrc,
|
audioSsrc: channel.audioSsrc,
|
||||||
|
userId: channel.peerId,
|
||||||
endpointId: channel.endpointId,
|
endpointId: channel.endpointId,
|
||||||
ssrcGroups: channel.ssrcGroups.map { group in
|
ssrcGroups: channel.ssrcGroups.map { group in
|
||||||
return OngoingGroupCallSsrcGroup(
|
return OngoingGroupCallSsrcGroup(
|
||||||
|
@ -402,13 +402,14 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) {
|
|||||||
@interface OngoingGroupCallRequestedVideoChannel : NSObject
|
@interface OngoingGroupCallRequestedVideoChannel : NSObject
|
||||||
|
|
||||||
@property (nonatomic, readonly) uint32_t audioSsrc;
|
@property (nonatomic, readonly) uint32_t audioSsrc;
|
||||||
|
@property (nonatomic, readonly) int64_t userId;
|
||||||
@property (nonatomic, strong, readonly) NSString * _Nonnull endpointId;
|
@property (nonatomic, strong, readonly) NSString * _Nonnull endpointId;
|
||||||
@property (nonatomic, strong, readonly) NSArray<OngoingGroupCallSsrcGroup *> * _Nonnull ssrcGroups;
|
@property (nonatomic, strong, readonly) NSArray<OngoingGroupCallSsrcGroup *> * _Nonnull ssrcGroups;
|
||||||
|
|
||||||
@property (nonatomic, readonly) OngoingGroupCallRequestedVideoQuality minQuality;
|
@property (nonatomic, readonly) OngoingGroupCallRequestedVideoQuality minQuality;
|
||||||
@property (nonatomic, readonly) OngoingGroupCallRequestedVideoQuality maxQuality;
|
@property (nonatomic, readonly) OngoingGroupCallRequestedVideoQuality maxQuality;
|
||||||
|
|
||||||
- (instancetype _Nonnull)initWithAudioSsrc:(uint32_t)audioSsrc endpointId:(NSString * _Nonnull)endpointId ssrcGroups:(NSArray<OngoingGroupCallSsrcGroup *> * _Nonnull)ssrcGroups minQuality:(OngoingGroupCallRequestedVideoQuality)minQuality maxQuality:(OngoingGroupCallRequestedVideoQuality)maxQuality;
|
- (instancetype _Nonnull)initWithAudioSsrc:(uint32_t)audioSsrc userId:(int64_t)userId endpointId:(NSString * _Nonnull)endpointId ssrcGroups:(NSArray<OngoingGroupCallSsrcGroup *> * _Nonnull)ssrcGroups minQuality:(OngoingGroupCallRequestedVideoQuality)minQuality maxQuality:(OngoingGroupCallRequestedVideoQuality)maxQuality;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@ -456,7 +457,7 @@ onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDet
|
|||||||
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
||||||
isConference:(bool)isConference
|
isConference:(bool)isConference
|
||||||
isActiveByDefault:(bool)isActiveByDefault
|
isActiveByDefault:(bool)isActiveByDefault
|
||||||
encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryptDecrypt;
|
encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, int64_t, bool))encryptDecrypt;
|
||||||
|
|
||||||
- (void)stop:(void (^ _Nullable)())completion;
|
- (void)stop:(void (^ _Nullable)())completion;
|
||||||
|
|
||||||
|
@ -2378,7 +2378,7 @@ onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDet
|
|||||||
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
||||||
isConference:(bool)isConference
|
isConference:(bool)isConference
|
||||||
isActiveByDefault:(bool)isActiveByDefault
|
isActiveByDefault:(bool)isActiveByDefault
|
||||||
encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryptDecrypt {
|
encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, int64_t, bool))encryptDecrypt {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
_queue = queue;
|
_queue = queue;
|
||||||
@ -2447,12 +2447,12 @@ encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryp
|
|||||||
|
|
||||||
std::string statsLogPathValue(statsLogPath.length == 0 ? "" : statsLogPath.UTF8String);
|
std::string statsLogPathValue(statsLogPath.length == 0 ? "" : statsLogPath.UTF8String);
|
||||||
|
|
||||||
std::function<std::vector<uint8_t>(std::vector<uint8_t> const &, bool)> mappedEncryptDecrypt;
|
std::function<std::vector<uint8_t>(std::vector<uint8_t> const &, int64_t, bool)> mappedEncryptDecrypt;
|
||||||
if (encryptDecrypt) {
|
if (encryptDecrypt) {
|
||||||
NSData * _Nullable (^encryptDecryptBlock)(NSData * _Nonnull, bool) = [encryptDecrypt copy];
|
NSData * _Nullable (^encryptDecryptBlock)(NSData * _Nonnull, int64_t, bool) = [encryptDecrypt copy];
|
||||||
mappedEncryptDecrypt = [encryptDecryptBlock](std::vector<uint8_t> const &message, bool isEncrypt) -> std::vector<uint8_t> {
|
mappedEncryptDecrypt = [encryptDecryptBlock](std::vector<uint8_t> const &message, int64_t userId, bool isEncrypt) -> std::vector<uint8_t> {
|
||||||
NSData *mappedMessage = [[NSData alloc] initWithBytes:message.data() length:message.size()];
|
NSData *mappedMessage = [[NSData alloc] initWithBytes:message.data() length:message.size()];
|
||||||
NSData *result = encryptDecryptBlock(mappedMessage, isEncrypt);
|
NSData *result = encryptDecryptBlock(mappedMessage, userId, isEncrypt);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return std::vector<uint8_t>();
|
return std::vector<uint8_t>();
|
||||||
}
|
}
|
||||||
@ -2635,6 +2635,7 @@ encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryp
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mappedChannel.userId = channel.peerId;
|
||||||
mappedChannel.audioSsrc = channel.audioSsrc;
|
mappedChannel.audioSsrc = channel.audioSsrc;
|
||||||
mappedChannel.videoInformation = channel.videoDescription.UTF8String ?: "";
|
mappedChannel.videoInformation = channel.videoDescription.UTF8String ?: "";
|
||||||
mappedChannels.push_back(std::move(mappedChannel));
|
mappedChannels.push_back(std::move(mappedChannel));
|
||||||
@ -2837,6 +2838,7 @@ encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryp
|
|||||||
for (OngoingGroupCallRequestedVideoChannel *channel : requestedVideoChannels) {
|
for (OngoingGroupCallRequestedVideoChannel *channel : requestedVideoChannels) {
|
||||||
tgcalls::VideoChannelDescription description;
|
tgcalls::VideoChannelDescription description;
|
||||||
description.audioSsrc = channel.audioSsrc;
|
description.audioSsrc = channel.audioSsrc;
|
||||||
|
description.userId = channel.userId;
|
||||||
description.endpointId = channel.endpointId.UTF8String ?: "";
|
description.endpointId = channel.endpointId.UTF8String ?: "";
|
||||||
for (OngoingGroupCallSsrcGroup *group in channel.ssrcGroups) {
|
for (OngoingGroupCallSsrcGroup *group in channel.ssrcGroups) {
|
||||||
tgcalls::MediaSsrcGroup parsedGroup;
|
tgcalls::MediaSsrcGroup parsedGroup;
|
||||||
@ -3075,10 +3077,11 @@ encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryp
|
|||||||
|
|
||||||
@implementation OngoingGroupCallRequestedVideoChannel
|
@implementation OngoingGroupCallRequestedVideoChannel
|
||||||
|
|
||||||
- (instancetype)initWithAudioSsrc:(uint32_t)audioSsrc endpointId:(NSString * _Nonnull)endpointId ssrcGroups:(NSArray<OngoingGroupCallSsrcGroup *> * _Nonnull)ssrcGroups minQuality:(OngoingGroupCallRequestedVideoQuality)minQuality maxQuality:(OngoingGroupCallRequestedVideoQuality)maxQuality {
|
- (instancetype)initWithAudioSsrc:(uint32_t)audioSsrc userId:(int64_t)userId endpointId:(NSString * _Nonnull)endpointId ssrcGroups:(NSArray<OngoingGroupCallSsrcGroup *> * _Nonnull)ssrcGroups minQuality:(OngoingGroupCallRequestedVideoQuality)minQuality maxQuality:(OngoingGroupCallRequestedVideoQuality)maxQuality {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
_audioSsrc = audioSsrc;
|
_audioSsrc = audioSsrc;
|
||||||
|
_userId = userId;
|
||||||
_endpointId = endpointId;
|
_endpointId = endpointId;
|
||||||
_ssrcGroups = ssrcGroups;
|
_ssrcGroups = ssrcGroups;
|
||||||
_minQuality = minQuality;
|
_minQuality = minQuality;
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 18ef54fd10115ad7fe73585b7bf8a7ddbe527124
|
Subproject commit fd1cfbd8151b2c32d5471a4f5431faa6274ce421
|
@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
|
|
||||||
@interface TdCall : NSObject
|
@interface TdCall : NSObject
|
||||||
|
|
||||||
+ (nullable instancetype)makeWithKeyPair:(TdKeyPair *)keyPair latestBlock:(NSData *)latestBlock;
|
+ (nullable instancetype)makeWithKeyPair:(TdKeyPair *)keyPair userId:(int64_t)userId latestBlock:(NSData *)latestBlock;
|
||||||
|
|
||||||
- (NSArray<NSData *> *)takeOutgoingBroadcastBlocks;
|
- (NSArray<NSData *> *)takeOutgoingBroadcastBlocks;
|
||||||
- (NSData *)emojiState;
|
- (NSData *)emojiState;
|
||||||
@ -43,7 +43,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
- (nullable NSData *)generateRemoveParticipantsBlock:(NSArray<NSNumber *> *)participantIds;
|
- (nullable NSData *)generateRemoveParticipantsBlock:(NSArray<NSNumber *> *)participantIds;
|
||||||
|
|
||||||
- (nullable NSData *)encrypt:(NSData *)message;
|
- (nullable NSData *)encrypt:(NSData *)message;
|
||||||
- (nullable NSData *)decrypt:(NSData *)message;
|
- (nullable NSData *)decrypt:(NSData *)message userId:(int64_t)userId;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
10
third-party/td/TdBinding/Sources/TdBinding.mm
vendored
10
third-party/td/TdBinding/Sources/TdBinding.mm
vendored
@ -91,7 +91,7 @@ static NSString *hexStringFromData(NSData *data) {
|
|||||||
tde2e_api::call_destroy(_callId);
|
tde2e_api::call_destroy(_callId);
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (nullable instancetype)makeWithKeyPair:(TdKeyPair *)keyPair latestBlock:(NSData *)latestBlock {
|
+ (nullable instancetype)makeWithKeyPair:(TdKeyPair *)keyPair userId:(int64_t)userId latestBlock:(NSData *)latestBlock {
|
||||||
std::string mappedLatestBlock((uint8_t *)latestBlock.bytes, ((uint8_t *)latestBlock.bytes) + latestBlock.length);
|
std::string mappedLatestBlock((uint8_t *)latestBlock.bytes, ((uint8_t *)latestBlock.bytes) + latestBlock.length);
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
auto describeResult = tde2e_api::call_describe_block(mappedLatestBlock);
|
auto describeResult = tde2e_api::call_describe_block(mappedLatestBlock);
|
||||||
@ -112,7 +112,7 @@ static NSString *hexStringFromData(NSData *data) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto call = tde2e_api::call_create(keyPair.keyId, mappedLatestBlock);
|
auto call = tde2e_api::call_create(userId, keyPair.keyId, mappedLatestBlock);
|
||||||
if (!call.is_ok()) {
|
if (!call.is_ok()) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
@ -232,16 +232,16 @@ static NSString *hexStringFromData(NSData *data) {
|
|||||||
|
|
||||||
- (nullable NSData *)encrypt:(NSData *)message {
|
- (nullable NSData *)encrypt:(NSData *)message {
|
||||||
std::string mappedMessage((uint8_t *)message.bytes, ((uint8_t *)message.bytes) + message.length);
|
std::string mappedMessage((uint8_t *)message.bytes, ((uint8_t *)message.bytes) + message.length);
|
||||||
auto result = tde2e_api::call_encrypt(_callId, mappedMessage);
|
auto result = tde2e_api::call_encrypt(_callId, 0, mappedMessage);
|
||||||
if (!result.is_ok()) {
|
if (!result.is_ok()) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
return [[NSData alloc] initWithBytes:result.value().data() length:result.value().size()];
|
return [[NSData alloc] initWithBytes:result.value().data() length:result.value().size()];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (nullable NSData *)decrypt:(NSData *)message {
|
- (nullable NSData *)decrypt:(NSData *)message userId:(int64_t)userId {
|
||||||
std::string mappedMessage((uint8_t *)message.bytes, ((uint8_t *)message.bytes) + message.length);
|
std::string mappedMessage((uint8_t *)message.bytes, ((uint8_t *)message.bytes) + message.length);
|
||||||
auto result = tde2e_api::call_decrypt(_callId, mappedMessage);
|
auto result = tde2e_api::call_decrypt(_callId, userId, 0, mappedMessage);
|
||||||
if (!result.is_ok()) {
|
if (!result.is_ok()) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
16
third-party/td/td/benchmark/bench_crypto.cpp
vendored
16
third-party/td/td/benchmark/bench_crypto.cpp
vendored
@ -127,7 +127,7 @@ class HmacSha256ShortBench final : public td::Benchmark {
|
|||||||
void run(int n) final {
|
void run(int n) final {
|
||||||
unsigned char md[32];
|
unsigned char md[32];
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
td::hmac_sha256(td::Slice(data, SHORT_DATA_SIZE), td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32));
|
td::hmac_sha256(td::Slice(data, 32), td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -145,9 +145,9 @@ class HmacSha512ShortBench final : public td::Benchmark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void run(int n) final {
|
void run(int n) final {
|
||||||
unsigned char md[32];
|
unsigned char md[64];
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
td::hmac_sha256(td::Slice(data, SHORT_DATA_SIZE), td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32));
|
td::hmac_sha512(td::Slice(data, 32), td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 64));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -491,6 +491,11 @@ class Crc64Bench final : public td::Benchmark {
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
td::init_openssl_threads();
|
td::init_openssl_threads();
|
||||||
|
td::bench(HmacSha256ShortBench());
|
||||||
|
td::bench(HmacSha512ShortBench());
|
||||||
|
td::bench(SHA1ShortBench());
|
||||||
|
td::bench(SHA256ShortBench());
|
||||||
|
td::bench(SHA512ShortBench());
|
||||||
td::bench(AesCtrBench());
|
td::bench(AesCtrBench());
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||||
td::bench(AesCtrOpenSSLBench());
|
td::bench(AesCtrOpenSSLBench());
|
||||||
@ -516,11 +521,6 @@ int main() {
|
|||||||
#if OPENSSL_VERSION_NUMBER <= 0x10100000L
|
#if OPENSSL_VERSION_NUMBER <= 0x10100000L
|
||||||
td::bench(SHA1Bench());
|
td::bench(SHA1Bench());
|
||||||
#endif
|
#endif
|
||||||
td::bench(SHA1ShortBench());
|
|
||||||
td::bench(SHA256ShortBench());
|
|
||||||
td::bench(SHA512ShortBench());
|
|
||||||
td::bench(HmacSha256ShortBench());
|
|
||||||
td::bench(HmacSha512ShortBench());
|
|
||||||
td::bench(Crc32Bench());
|
td::bench(Crc32Bench());
|
||||||
td::bench(Crc64Bench());
|
td::bench(Crc64Bench());
|
||||||
}
|
}
|
||||||
|
14
third-party/td/td/td/generate/scheme/e2e_api.tl
vendored
14
third-party/td/td/td/generate/scheme/e2e_api.tl
vendored
@ -64,15 +64,10 @@ e2e.keyContactByPublicKey public_key:int256 = e2e.Key;
|
|||||||
// TODO: store string instead of e2e.personalOnClient to support forward compatibility
|
// TODO: store string instead of e2e.personalOnClient to support forward compatibility
|
||||||
e2e.valueContactByPublicKey entries:vector<e2e.personalOnClient> = e2e.Value;
|
e2e.valueContactByPublicKey entries:vector<e2e.personalOnClient> = e2e.Value;
|
||||||
|
|
||||||
//
|
e2e.chain.groupBroadcastNonceCommit#d1512ae7 signature:int512 user_id:int64 chain_height:int32 chain_hash:int256 nonce_hash:int256 = e2e.chain.GroupBroadcast;
|
||||||
// Blockchain
|
e2e.chain.groupBroadcastNonceReveal#83f4f9d8 signature:int512 user_id:int64 chain_height:int32 chain_hash:int256 nonce:int256 = e2e.chain.GroupBroadcast;
|
||||||
//
|
|
||||||
|
|
||||||
// TODO: this relies on fact that key is one time. It is critical
|
e2e.chain.groupParticipant user_id:long public_key:int256 flags:# add_users:flags.0?true remove_users:flags.1?true version:int = e2e.chain.GroupParticipant;
|
||||||
e2e.chain.groupBroadcastNonceCommit signature:int512 public_key:int256 chain_height:int32 nonce_hash:int256 = e2e.chain.GroupBroadcast;
|
|
||||||
e2e.chain.groupBroadcastNonceReveal signature:int512 public_key:int256 chain_height:int32 nonce:int256 = e2e.chain.GroupBroadcast;
|
|
||||||
|
|
||||||
e2e.chain.groupParticipant user_id:long public_key:int256 flags:# add_users:flags.0?true remove_users:flags.1?true = e2e.chain.GroupParticipant;
|
|
||||||
e2e.chain.groupState participants:vector<e2e.chain.GroupParticipant> external_permissions:int = e2e.chain.GroupState;
|
e2e.chain.groupState participants:vector<e2e.chain.GroupParticipant> external_permissions:int = e2e.chain.GroupState;
|
||||||
e2e.chain.sharedKey ek:int256 encrypted_shared_key:string dest_user_id:vector<long> dest_header:vector<bytes> = e2e.chain.SharedKey;
|
e2e.chain.sharedKey ek:int256 encrypted_shared_key:string dest_user_id:vector<long> dest_header:vector<bytes> = e2e.chain.SharedKey;
|
||||||
|
|
||||||
@ -84,8 +79,7 @@ e2e.chain.changeSetSharedKey shared_key:e2e.chain.SharedKey = e2e.chain.Change;
|
|||||||
e2e.chain.stateProof flags:# kv_hash:int256 group_state:flags.0?e2e.chain.GroupState shared_key:flags.1?e2e.chain.SharedKey = e2e.chain.StateProof;
|
e2e.chain.stateProof flags:# kv_hash:int256 group_state:flags.0?e2e.chain.GroupState shared_key:flags.1?e2e.chain.SharedKey = e2e.chain.StateProof;
|
||||||
|
|
||||||
// signature is for the same block, but with empty signature
|
// signature is for the same block, but with empty signature
|
||||||
// TODO: change flags
|
e2e.chain.block#639a3db6 signature:int512 flags:# prev_block_hash:int256 changes:vector<e2e.chain.Change> height:int state_proof:e2e.chain.StateProof signature_public_key:flags.0?int256 = e2e.chain.Block;
|
||||||
e2e.chain.block signature:int512 flags:# prev_block_hash:int256 changes:vector<e2e.chain.Change> height:int state_proof:e2e.chain.StateProof signature_public_key:flags.0?int256 = e2e.chain.Block;
|
|
||||||
|
|
||||||
--- functions ---
|
--- functions ---
|
||||||
|
|
||||||
|
1
third-party/td/td/tde2e/CMakeLists.txt
vendored
1
third-party/td/td/tde2e/CMakeLists.txt
vendored
@ -56,6 +56,7 @@ set(TDE2E_TEST_SOURCE
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/td/e2e/TestBlockchain.h
|
${CMAKE_CURRENT_SOURCE_DIR}/td/e2e/TestBlockchain.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/e2e.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/e2e.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test/blockchain.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test/blockchain.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test/encryption.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(TDE2E_TEST_SOURCE "${TDE2E_TEST_SOURCE}" PARENT_SCOPE)
|
set(TDE2E_TEST_SOURCE "${TDE2E_TEST_SOURCE}" PARENT_SCOPE)
|
||||||
|
299
third-party/td/td/tde2e/td/e2e/Blockchain.cpp
vendored
299
third-party/td/td/tde2e/td/e2e/Blockchain.cpp
vendored
@ -20,6 +20,7 @@
|
|||||||
#include "td/utils/tl_helpers.h"
|
#include "td/utils/tl_helpers.h"
|
||||||
#include "td/utils/tl_parsers.h"
|
#include "td/utils/tl_parsers.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
@ -28,12 +29,23 @@
|
|||||||
namespace tde2e_core {
|
namespace tde2e_core {
|
||||||
|
|
||||||
GroupParticipant GroupParticipant::from_tl(const td::e2e_api::e2e_chain_groupParticipant &participant) {
|
GroupParticipant GroupParticipant::from_tl(const td::e2e_api::e2e_chain_groupParticipant &participant) {
|
||||||
return GroupParticipant{participant.user_id_, participant.flags_, PublicKey::from_u256(participant.public_key_)};
|
return GroupParticipant{participant.user_id_, participant.flags_, PublicKey::from_u256(participant.public_key_),
|
||||||
|
participant.version_};
|
||||||
}
|
}
|
||||||
|
|
||||||
e2e::object_ptr<e2e::e2e_chain_groupParticipant> GroupParticipant::to_tl() const {
|
e2e::object_ptr<e2e::e2e_chain_groupParticipant> GroupParticipant::to_tl() const {
|
||||||
return e2e::make_object<e2e::e2e_chain_groupParticipant>(user_id, public_key.to_u256(), flags, add_users(),
|
return e2e::make_object<e2e::e2e_chain_groupParticipant>(user_id, public_key.to_u256(), flags, add_users(),
|
||||||
remove_users());
|
remove_users(), version);
|
||||||
|
}
|
||||||
|
td::int32 GroupState::version() const {
|
||||||
|
if (participants.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
td::int32 version = participants.front().version;
|
||||||
|
for (auto &participant : participants) {
|
||||||
|
version = std::min(version, participant.version);
|
||||||
|
}
|
||||||
|
return std::clamp(version, 0, 255);
|
||||||
}
|
}
|
||||||
td::Result<GroupParticipant> GroupState::get_participant(td::int64 user_id) const {
|
td::Result<GroupParticipant> GroupState::get_participant(td::int64 user_id) const {
|
||||||
for (const auto &participant : participants) {
|
for (const auto &participant : participants) {
|
||||||
@ -52,12 +64,13 @@ td::Result<GroupParticipant> GroupState::get_participant(const PublicKey &public
|
|||||||
}
|
}
|
||||||
return td::Status::Error("Participant not found");
|
return td::Status::Error("Participant not found");
|
||||||
};
|
};
|
||||||
Permissions GroupState::get_permissions(const PublicKey &public_key) const {
|
Permissions GroupState::get_permissions(const PublicKey &public_key, td::int32 limit_permissions) const {
|
||||||
|
limit_permissions &= GroupParticipantFlags::AllPermissions;
|
||||||
auto r_participant = get_participant(public_key);
|
auto r_participant = get_participant(public_key);
|
||||||
if (r_participant.is_ok()) {
|
if (r_participant.is_ok()) {
|
||||||
return Permissions{r_participant.ok().flags | GroupParticipantFlags::IsParticipant};
|
return Permissions{(r_participant.ok().flags & limit_permissions) | GroupParticipantFlags::IsParticipant};
|
||||||
}
|
}
|
||||||
return Permissions{external_permissions};
|
return Permissions{(external_permissions & limit_permissions)};
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupStateRef GroupState::from_tl(const td::e2e_api::e2e_chain_groupState &state) {
|
GroupStateRef GroupState::from_tl(const td::e2e_api::e2e_chain_groupState &state) {
|
||||||
@ -74,7 +87,7 @@ e2e::object_ptr<e2e::e2e_chain_groupState> GroupState::to_tl() const {
|
|||||||
external_permissions);
|
external_permissions);
|
||||||
}
|
}
|
||||||
GroupStateRef GroupState::empty_state() {
|
GroupStateRef GroupState::empty_state() {
|
||||||
static GroupStateRef state = std::make_shared<GroupState>(GroupState{});
|
static GroupStateRef state = std::make_shared<GroupState>();
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
GroupSharedKeyRef GroupSharedKey::from_tl(const td::e2e_api::e2e_chain_sharedKey &shared_key) {
|
GroupSharedKeyRef GroupSharedKey::from_tl(const td::e2e_api::e2e_chain_sharedKey &shared_key) {
|
||||||
@ -87,7 +100,7 @@ e2e::object_ptr<e2e::e2e_chain_sharedKey> GroupSharedKey::to_tl() const {
|
|||||||
std::vector<td::int64>(dest_user_id), std::vector(dest_header));
|
std::vector<td::int64>(dest_user_id), std::vector(dest_header));
|
||||||
}
|
}
|
||||||
GroupSharedKeyRef GroupSharedKey::empty_shared_key() {
|
GroupSharedKeyRef GroupSharedKey::empty_shared_key() {
|
||||||
static GroupSharedKeyRef shared_key = std::make_shared<GroupSharedKey>(GroupSharedKey{});
|
static GroupSharedKeyRef shared_key = std::make_shared<GroupSharedKey>();
|
||||||
return shared_key;
|
return shared_key;
|
||||||
}
|
}
|
||||||
ChangeSetValue ChangeSetValue::from_tl(const td::e2e_api::e2e_chain_changeSetValue &change) {
|
ChangeSetValue ChangeSetValue::from_tl(const td::e2e_api::e2e_chain_changeSetValue &change) {
|
||||||
@ -166,7 +179,7 @@ td::Result<Block> Block::from_tl_serialized(td::Slice new_block) {
|
|||||||
auto magic = parser.fetch_int();
|
auto magic = parser.fetch_int();
|
||||||
if (magic != td::e2e_api::e2e_chain_block::ID) {
|
if (magic != td::e2e_api::e2e_chain_block::ID) {
|
||||||
return td::Status::Error(PSLICE() << "Expected magic " << td::format::as_hex(td::e2e_api::e2e_chain_block::ID)
|
return td::Status::Error(PSLICE() << "Expected magic " << td::format::as_hex(td::e2e_api::e2e_chain_block::ID)
|
||||||
<< td::format::as_hex(magic));
|
<< ", got " << td::format::as_hex(magic));
|
||||||
}
|
}
|
||||||
auto block_tl = td::e2e_api::e2e_chain_block::fetch(parser);
|
auto block_tl = td::e2e_api::e2e_chain_block::fetch(parser);
|
||||||
parser.fetch_end();
|
parser.fetch_end();
|
||||||
@ -198,10 +211,18 @@ td::StringBuilder &operator<<(td::StringBuilder &sb, const Block &block) {
|
|||||||
<< "\tchanges=" << block.changes_ << "\n"
|
<< "\tchanges=" << block.changes_ << "\n"
|
||||||
<< "\tsignature_key=" << block.o_signature_public_key_ << ")";
|
<< "\tsignature_key=" << block.o_signature_public_key_ << ")";
|
||||||
}
|
}
|
||||||
|
td::Result<BitString> key_to_bitstring(td::Slice key) {
|
||||||
|
if (key.size() != 32) {
|
||||||
|
return td::Status::Error("Invalid key size");
|
||||||
|
}
|
||||||
|
return BitString(key);
|
||||||
|
}
|
||||||
td::Result<std::string> KeyValueState::get_value(td::Slice key) const {
|
td::Result<std::string> KeyValueState::get_value(td::Slice key) const {
|
||||||
return get(node_, BitString(key), snapshot_.value());
|
TRY_RESULT(bitstring, key_to_bitstring(key));
|
||||||
|
return get(node_, bitstring, snapshot_.value());
|
||||||
}
|
}
|
||||||
td::Result<std::string> KeyValueState::gen_proof(td::Span<td::Slice> keys) const {
|
td::Result<std::string> KeyValueState::gen_proof(td::Span<td::Slice> keys) const {
|
||||||
|
// TODO: validate keys..
|
||||||
TRY_RESULT(pruned_tree, generate_pruned_tree(node_, keys, snapshot_.value()));
|
TRY_RESULT(pruned_tree, generate_pruned_tree(node_, keys, snapshot_.value()));
|
||||||
return TrieNode::serialize_for_network(pruned_tree);
|
return TrieNode::serialize_for_network(pruned_tree);
|
||||||
}
|
}
|
||||||
@ -218,7 +239,8 @@ td::Result<std::string> KeyValueState::build_snapshot() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
td::Status KeyValueState::set_value(td::Slice key, td::Slice value) {
|
td::Status KeyValueState::set_value(td::Slice key, td::Slice value) {
|
||||||
TRY_RESULT_ASSIGN(node_, set(node_, BitString(key), value, snapshot_.value()));
|
TRY_RESULT(bitstring, key_to_bitstring(key));
|
||||||
|
TRY_RESULT_ASSIGN(node_, set(node_, bitstring, value, snapshot_.value()));
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,55 +299,75 @@ td::Status State::set_value(td::Slice key, td::Slice value, const Permissions &p
|
|||||||
return key_value_state_.set_value(key, value);
|
return key_value_state_.set_value(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Status State::set_value_fast(KeyValueHash key_value_hash) {
|
td::Status State::set_value_fast(const KeyValueHash &key_value_hash) {
|
||||||
TRY_RESULT_ASSIGN(key_value_state_, KeyValueState::create_from_hash(key_value_hash));
|
TRY_RESULT_ASSIGN(key_value_state_, KeyValueState::create_from_hash(key_value_hash));
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Status State::set_group_state(GroupStateRef group_state, const Permissions &permissions) {
|
td::Status State::validate_group_state(const GroupStateRef &group_state) {
|
||||||
std::map<td::int64, td::int32> old_participants;
|
std::set<td::int64> new_user_ids;
|
||||||
std::set<td::int64> new_participants;
|
|
||||||
std::set<PublicKey> new_keys;
|
std::set<PublicKey> new_keys;
|
||||||
for (const auto &p : group_state_->participants) {
|
|
||||||
old_participants[p.user_id] = p.flags;
|
|
||||||
}
|
|
||||||
for (const auto &p : group_state->participants) {
|
for (const auto &p : group_state->participants) {
|
||||||
new_participants.insert(p.user_id);
|
new_user_ids.insert(p.user_id);
|
||||||
new_keys.insert(p.public_key);
|
new_keys.insert(p.public_key);
|
||||||
|
if ((p.flags & ~GroupParticipantFlags::AllPermissions) != 0) {
|
||||||
|
return Error(E::InvalidBlock_InvalidGroupState, "invalid permissions");
|
||||||
}
|
}
|
||||||
if (new_participants.size() != group_state->participants.size()) {
|
}
|
||||||
|
if ((group_state->external_permissions & ~GroupParticipantFlags::AllPermissions) != 0) {
|
||||||
|
return Error(E::InvalidBlock_InvalidGroupState, "invalid external permissions");
|
||||||
|
}
|
||||||
|
if (new_user_ids.size() != group_state->participants.size()) {
|
||||||
return Error(E::InvalidBlock_InvalidGroupState, "duplicate user_id");
|
return Error(E::InvalidBlock_InvalidGroupState, "duplicate user_id");
|
||||||
}
|
}
|
||||||
if (new_keys.size() != group_state->participants.size()) {
|
if (new_keys.size() != group_state->participants.size()) {
|
||||||
return Error(E::InvalidBlock_InvalidGroupState, "duplicate public_key");
|
return Error(E::InvalidBlock_InvalidGroupState, "duplicate public_key");
|
||||||
}
|
}
|
||||||
|
return td::Status::OK();
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Status State::set_group_state(GroupStateRef group_state, const Permissions &permissions) {
|
||||||
|
TRY_STATUS(validate_group_state(group_state));
|
||||||
|
|
||||||
|
std::map<std::pair<td::int64, PublicKey>, td::int32> old_participants;
|
||||||
|
std::map<std::pair<td::int64, PublicKey>, td::int32> new_participants;
|
||||||
|
|
||||||
|
for (const auto &p : group_state_->participants) {
|
||||||
|
old_participants[std::make_pair(p.user_id, p.public_key)] = p.flags;
|
||||||
|
}
|
||||||
|
for (const auto &p : group_state->participants) {
|
||||||
|
new_participants[std::make_pair(p.user_id, p.public_key)] = p.flags;
|
||||||
|
}
|
||||||
if ((~group_state_->external_permissions & group_state->external_permissions) != 0) {
|
if ((~group_state_->external_permissions & group_state->external_permissions) != 0) {
|
||||||
return Error(E::InvalidBlock_NoPermissions, "Can't increase external permissions");
|
return Error(E::InvalidBlock_NoPermissions, "Can't increase external permissions");
|
||||||
}
|
}
|
||||||
|
|
||||||
td::int32 needed_flags = 0;
|
td::int32 needed_flags = 0;
|
||||||
for (const auto &p : group_state_->participants) {
|
for (const auto &[p, flags] : old_participants) {
|
||||||
if (!new_participants.count(p.user_id)) {
|
if (!new_participants.count(p)) {
|
||||||
if (!permissions.may_remove_users()) {
|
if (!permissions.may_remove_users()) {
|
||||||
return Error(E::InvalidBlock_NoPermissions, "Can't remove users");
|
return Error(E::InvalidBlock_NoPermissions, "Can't remove users");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto &p : group_state->participants) {
|
for (const auto &[p, flags] : new_participants) {
|
||||||
auto old_p = old_participants.find(p.user_id);
|
auto old_p = old_participants.find(p);
|
||||||
if (old_p == old_participants.end()) {
|
if (old_p == old_participants.end()) {
|
||||||
if (!permissions.may_add_users()) {
|
if (!permissions.may_add_users()) {
|
||||||
return Error(E::InvalidBlock_NoPermissions, "Can't add users");
|
return Error(E::InvalidBlock_NoPermissions, "Can't add users");
|
||||||
}
|
}
|
||||||
needed_flags |= p.flags;
|
needed_flags |= flags;
|
||||||
} else {
|
} else if (flags != old_p->second) {
|
||||||
needed_flags |= p.flags & ~old_p->second;
|
if (!permissions.may_add_users() || !permissions.may_remove_users()) {
|
||||||
|
return Error(E::InvalidBlock_NoPermissions, "Can't add users");
|
||||||
|
}
|
||||||
|
needed_flags |= flags & ~old_p->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td::int32 missing_flags = needed_flags & ~permissions.flags;
|
td::int32 missing_flags = needed_flags & ~(permissions.flags & GroupParticipantFlags::AllPermissions);
|
||||||
if (missing_flags != 0) {
|
if (missing_flags != 0) {
|
||||||
return Error(E::InvalidBlock_NoPermissions, "Can't give more permissions that we have");
|
return Error(E::InvalidBlock_NoPermissions, "Can't give more permissions than we have");
|
||||||
}
|
}
|
||||||
group_state_ = std::move(group_state);
|
group_state_ = std::move(group_state);
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
@ -339,6 +381,31 @@ td::Status State::clear_shared_key(const Permissions &permissions) {
|
|||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td::Status State::validate_shared_key(const GroupSharedKeyRef &shared_key, const GroupStateRef &group_state) {
|
||||||
|
if (shared_key->empty_shared_key()) {
|
||||||
|
return td::Status::OK();
|
||||||
|
}
|
||||||
|
if (shared_key->dest_user_id.size() != shared_key->dest_header.size()) {
|
||||||
|
return td::Status::Error("Shared key different number of users and headers");
|
||||||
|
}
|
||||||
|
if (shared_key->dest_user_id.size() != group_state->participants.size()) {
|
||||||
|
return td::Status::Error("Shared key has wrong number of users");
|
||||||
|
}
|
||||||
|
std::set<td::int64> participants;
|
||||||
|
for (const auto user_id : shared_key->dest_user_id) {
|
||||||
|
participants.insert(user_id);
|
||||||
|
}
|
||||||
|
if (participants.size() != shared_key->dest_user_id.size()) {
|
||||||
|
return td::Status::Error("Shared key has duplicated users");
|
||||||
|
}
|
||||||
|
for (auto &p : group_state->participants) {
|
||||||
|
if (!participants.count(p.user_id)) {
|
||||||
|
return td::Status::Error("Unknown user_id in SetSharedKey");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return td::Status::OK();
|
||||||
|
}
|
||||||
|
|
||||||
td::Status State::set_shared_key(GroupSharedKeyRef shared_key, const Permissions &permissions) {
|
td::Status State::set_shared_key(GroupSharedKeyRef shared_key, const Permissions &permissions) {
|
||||||
if (*shared_key_ != *GroupSharedKey::empty_shared_key()) {
|
if (*shared_key_ != *GroupSharedKey::empty_shared_key()) {
|
||||||
return td::Status::Error("Shared key is already set");
|
return td::Status::Error("Shared key is already set");
|
||||||
@ -346,16 +413,8 @@ td::Status State::set_shared_key(GroupSharedKeyRef shared_key, const Permissions
|
|||||||
if (!permissions.may_change_shared_key()) {
|
if (!permissions.may_change_shared_key()) {
|
||||||
return Error(E::InvalidBlock_NoPermissions, "Can't set shared key");
|
return Error(E::InvalidBlock_NoPermissions, "Can't set shared key");
|
||||||
}
|
}
|
||||||
|
TRY_STATUS(validate_shared_key(shared_key, group_state_));
|
||||||
shared_key_ = std::move(shared_key);
|
shared_key_ = std::move(shared_key);
|
||||||
std::set<td::int64> participants;
|
|
||||||
for (const auto &p : group_state_->participants) {
|
|
||||||
participants.insert(p.user_id);
|
|
||||||
}
|
|
||||||
for (auto dest_user_id : shared_key_->dest_user_id) {
|
|
||||||
if (!participants.count(dest_user_id)) {
|
|
||||||
return td::Status::Error("Unknown user_id in SetSharedKey");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,6 +423,9 @@ td::Status State::validate_state(const StateProof &state_proof) const {
|
|||||||
return td::Status::Error("State hash mismatch");
|
return td::Status::Error("State hash mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!has_group_state_change_ && !has_set_value_) {
|
||||||
|
return Error(E::InvalidBlock_NoChanges, "There must be at least SetValue or SetGroupState changes");
|
||||||
|
}
|
||||||
if (has_group_state_change_ && state_proof.o_group_state) {
|
if (has_group_state_change_ && state_proof.o_group_state) {
|
||||||
return Error(E::InvalidBlock_InvalidStateProof_Group,
|
return Error(E::InvalidBlock_InvalidStateProof_Group,
|
||||||
"Group state must be omitted when there is a group state change");
|
"Group state must be omitted when there is a group state change");
|
||||||
@ -387,30 +449,40 @@ td::Status State::validate_state(const StateProof &state_proof) const {
|
|||||||
return Error(E::InvalidBlock_InvalidStateProof_Secret, "shared key state differs");
|
return Error(E::InvalidBlock_InvalidStateProof_Secret, "shared key state differs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TRY_STATUS(validate_group_state(group_state_));
|
||||||
|
TRY_STATUS(validate_shared_key(shared_key_, group_state_));
|
||||||
|
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Status State::apply_change(const Change &change_outer, const PublicKey &public_key, bool full_apply) {
|
td::Status State::apply_change(const Change &change_outer, const PublicKey &public_key,
|
||||||
return std::visit(td::overloaded([](const ChangeNoop &change) { return td::Status::OK(); },
|
const ValidateOptions &validate_options) {
|
||||||
[this, full_apply, &public_key](const ChangeSetValue &change) {
|
bool full_apply = validate_options.validate_state_hash;
|
||||||
|
auto limit_permissions = validate_options.permissions;
|
||||||
|
return std::visit(
|
||||||
|
td::overloaded(
|
||||||
|
[](const ChangeNoop &change) { return td::Status::OK(); },
|
||||||
|
[this, full_apply, limit_permissions, &public_key](const ChangeSetValue &change) {
|
||||||
|
has_set_value_ = true;
|
||||||
if (full_apply) {
|
if (full_apply) {
|
||||||
return set_value(change.key, change.value, group_state_->get_permissions(public_key));
|
return set_value(change.key, change.value, group_state_->get_permissions(public_key, limit_permissions));
|
||||||
}
|
}
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
},
|
},
|
||||||
[this, &public_key](const ChangeSetGroupState &change) {
|
[this, limit_permissions, &public_key](const ChangeSetGroupState &change) {
|
||||||
has_group_state_change_ = true;
|
has_group_state_change_ = true;
|
||||||
TRY_STATUS(set_group_state(change.group_state, group_state_->get_permissions(public_key)));
|
TRY_STATUS(
|
||||||
return clear_shared_key(group_state_->get_permissions(public_key));
|
set_group_state(change.group_state, group_state_->get_permissions(public_key, limit_permissions)));
|
||||||
|
return clear_shared_key(group_state_->get_permissions(public_key, limit_permissions));
|
||||||
},
|
},
|
||||||
[this, &public_key](const ChangeSetSharedKey &change) {
|
[this, limit_permissions, &public_key](const ChangeSetSharedKey &change) {
|
||||||
has_shared_key_change_ = true;
|
has_shared_key_change_ = true;
|
||||||
return set_shared_key(change.shared_key, group_state_->get_permissions(public_key));
|
return set_shared_key(change.shared_key, group_state_->get_permissions(public_key, limit_permissions));
|
||||||
}),
|
}),
|
||||||
change_outer.value);
|
change_outer.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Status State::apply(Block &block, bool validate_state_hash) {
|
td::Status State::apply(Block &block, ValidateOptions validate_options) {
|
||||||
// To apply the first block an ephemeral -1 block is used
|
// To apply the first block an ephemeral -1 block is used
|
||||||
// - It has only one participant - Participant(user_id = 0, public_key = signer_public_key, permissions = all)
|
// - It has only one participant - Participant(user_id = 0, public_key = signer_public_key, permissions = all)
|
||||||
if (block.height_ == 0) {
|
if (block.height_ == 0) {
|
||||||
@ -426,22 +498,21 @@ td::Status State::apply(Block &block, bool validate_state_hash) {
|
|||||||
return td::Status::Error("Unknown public key");
|
return td::Status::Error("Unknown public key");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Would identify permissions of the participant who created the block, i.e. the one with `signer_public_key` public key.
|
|
||||||
// - First we look for signer public key in the previous state. If found use its permissions
|
|
||||||
// - Otherwise, we use external_permissions GroupParticipant participant;
|
|
||||||
|
|
||||||
// 5. Verifies the signature of the block.
|
// 5. Verifies the signature of the block.
|
||||||
|
if (validate_options.validate_signature) {
|
||||||
TRY_STATUS(block.verify_signature(o_signature_public_key.value()));
|
TRY_STATUS(block.verify_signature(o_signature_public_key.value()));
|
||||||
|
}
|
||||||
|
|
||||||
// 6. Applies the changes to the state.
|
// 6. Applies the changes to the state.
|
||||||
// - If `validate_state_hash` is true, the state hash is validated.
|
// - If `validate_state_hash` is true, the state hash is validated.
|
||||||
// - Otherwise, the state hash is set to the hash of the block.
|
// - Otherwise, the state hash is set to the hash of the block.
|
||||||
|
has_set_value_ = false;
|
||||||
has_shared_key_change_ = false;
|
has_shared_key_change_ = false;
|
||||||
has_group_state_change_ = false;
|
has_group_state_change_ = false;
|
||||||
for (auto &change : block.changes_) {
|
for (auto &change : block.changes_) {
|
||||||
TRY_STATUS(apply_change(change, o_signature_public_key.value(), validate_state_hash));
|
TRY_STATUS(apply_change(change, o_signature_public_key.value(), validate_options));
|
||||||
}
|
}
|
||||||
if (!validate_state_hash) {
|
if (!validate_options.validate_state_hash) {
|
||||||
TRY_STATUS(set_value_fast(block.state_proof_.kv_hash));
|
TRY_STATUS(set_value_fast(block.state_proof_.kv_hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,13 +537,21 @@ td::Result<State> State::create_from_block(const Block &block, td::optional<td::
|
|||||||
group_state = std::make_shared<GroupState>(GroupState{{}, GroupParticipantFlags::AllPermissions});
|
group_state = std::make_shared<GroupState>(GroupState{{}, GroupParticipantFlags::AllPermissions});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool has_set_value = false;
|
||||||
|
bool has_group_state_change = false;
|
||||||
|
bool has_shared_key_change = false;
|
||||||
for (const auto &change_v : block.changes_) {
|
for (const auto &change_v : block.changes_) {
|
||||||
std::visit(td::overloaded([](const ChangeNoop &change) {}, [](const ChangeSetValue &change) {},
|
std::visit(
|
||||||
|
td::overloaded([](const ChangeNoop &change) {}, [&](const ChangeSetValue &change) { has_set_value = true; },
|
||||||
[&](const ChangeSetGroupState &change) {
|
[&](const ChangeSetGroupState &change) {
|
||||||
group_state = change.group_state;
|
group_state = change.group_state;
|
||||||
shared_key = GroupSharedKey::empty_shared_key();
|
shared_key = GroupSharedKey::empty_shared_key();
|
||||||
|
has_group_state_change = true;
|
||||||
},
|
},
|
||||||
[&](const ChangeSetSharedKey &change) { shared_key = change.shared_key; }),
|
[&](const ChangeSetSharedKey &change) {
|
||||||
|
shared_key = change.shared_key;
|
||||||
|
has_shared_key_change = true;
|
||||||
|
}),
|
||||||
change_v.value);
|
change_v.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,35 +567,57 @@ td::Result<State> State::create_from_block(const Block &block, td::optional<td::
|
|||||||
if (!shared_key) {
|
if (!shared_key) {
|
||||||
return Error(E::InvalidBlock_InvalidStateProof_Secret, "no shared key");
|
return Error(E::InvalidBlock_InvalidStateProof_Secret, "no shared key");
|
||||||
}
|
}
|
||||||
return State(key_value_state, group_state, shared_key);
|
|
||||||
|
auto state = State(key_value_state, group_state, shared_key);
|
||||||
|
state.has_set_value_ = has_set_value;
|
||||||
|
state.has_group_state_change_ = has_group_state_change;
|
||||||
|
state.has_shared_key_change_ = has_shared_key_change;
|
||||||
|
TRY_STATUS(state.validate_state(block.state_proof_));
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<Block> Blockchain::build_block(std::vector<Change> changes, const PrivateKey &private_key) const {
|
td::Result<Block> Blockchain::build_block(std::vector<Change> changes, const PrivateKey &private_key) const {
|
||||||
//TODO(now): check if we are allowed to sign this block
|
//TODO(now): check if we are allowed to sign this block
|
||||||
auto public_key = private_key.to_public_key();
|
auto public_key = private_key.to_public_key();
|
||||||
auto state = state_;
|
auto state = state_;
|
||||||
|
if (last_block_.height_ == std::numeric_limits<td::int32>::max()) {
|
||||||
|
return td::Status::Error("Blockchain::build_block: last block height is too high");
|
||||||
|
}
|
||||||
td::int32 height = last_block_.height_ + 1;
|
td::int32 height = last_block_.height_ + 1;
|
||||||
if (height == 0) {
|
if (height == 0) {
|
||||||
state.group_state_ = std::make_shared<GroupState>(GroupState{{}, GroupParticipantFlags::AllPermissions});
|
state.group_state_ = std::make_shared<GroupState>(GroupState{{}, GroupParticipantFlags::AllPermissions});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ValidateOptions validate_options;
|
||||||
|
validate_options.validate_state_hash = true;
|
||||||
|
validate_options.validate_signature = false;
|
||||||
|
validate_options.permissions = GroupParticipantFlags::AllPermissions;
|
||||||
for (const auto &change : changes) {
|
for (const auto &change : changes) {
|
||||||
TRY_STATUS(state.apply_change(change, public_key, true));
|
TRY_STATUS(state.apply_change(change, public_key, validate_options));
|
||||||
}
|
}
|
||||||
|
|
||||||
StateProof state_proof;
|
StateProof state_proof;
|
||||||
state_proof.kv_hash = KeyValueHash{state.key_value_state_.get_hash()};
|
state_proof.kv_hash = KeyValueHash{state.key_value_state_.get_hash()};
|
||||||
state_proof.o_group_state = state.group_state_;
|
state_proof.o_group_state = state.group_state_;
|
||||||
state_proof.o_shared_key = state.shared_key_;
|
state_proof.o_shared_key = state.shared_key_;
|
||||||
|
state.has_set_value_ = false;
|
||||||
|
state.has_group_state_change_ = false;
|
||||||
|
state.has_shared_key_change_ = false;
|
||||||
for (const auto &change_v : changes) {
|
for (const auto &change_v : changes) {
|
||||||
std::visit(td::overloaded([](const ChangeNoop &change) {}, [](const ChangeSetValue &change) {},
|
std::visit(td::overloaded([](const ChangeNoop &change) {},
|
||||||
|
[&](const ChangeSetValue &change) { state.has_set_value_ = true; },
|
||||||
[&](const ChangeSetGroupState &change) {
|
[&](const ChangeSetGroupState &change) {
|
||||||
state_proof.o_group_state = {};
|
state_proof.o_group_state = {};
|
||||||
state_proof.o_shared_key = {};
|
state_proof.o_shared_key = {};
|
||||||
|
state.has_group_state_change_ = true;
|
||||||
},
|
},
|
||||||
[&](const ChangeSetSharedKey &change) { state_proof.o_shared_key = {}; }),
|
[&](const ChangeSetSharedKey &change) {
|
||||||
|
state_proof.o_shared_key = {};
|
||||||
|
state.has_shared_key_change_ = true;
|
||||||
|
}),
|
||||||
change_v.value);
|
change_v.value);
|
||||||
}
|
}
|
||||||
|
TRY_STATUS(state.validate_state(state_proof));
|
||||||
|
|
||||||
Block block;
|
Block block;
|
||||||
block.height_ = height;
|
block.height_ = height;
|
||||||
@ -528,13 +629,13 @@ td::Result<Block> Blockchain::build_block(std::vector<Change> changes, const Pri
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Status Blockchain::try_apply_block(Block block, bool validate_state_hash) {
|
td::Status Blockchain::try_apply_block(Block block, ValidateOptions validate_options) {
|
||||||
// To apply the first block an ephemeral -1 block is used
|
// To apply the first block an ephemeral -1 block is used
|
||||||
// - It has hash UInt256(0)
|
// - It has hash UInt256(0)
|
||||||
// - It has height -1
|
// - It has height -1
|
||||||
// - It has only one participant - Participant(user_id = 0, public_key = signer_public_key, permissions = all)
|
// - It has only one participant - Participant(user_id = 0, public_key = signer_public_key, permissions = all)
|
||||||
|
|
||||||
if (block.height_ != get_height() + 1) {
|
if (block.height_ != get_height() + 1 || get_height() == std::numeric_limits<td::int32>::max()) {
|
||||||
return Error(E::InvalidBlock_HeightMismatch,
|
return Error(E::InvalidBlock_HeightMismatch,
|
||||||
PSLICE() << "new_block.height=" << block.height_ << " != 1 + last_block.height=" << get_height());
|
PSLICE() << "new_block.height=" << block.height_ << " != 1 + last_block.height=" << get_height());
|
||||||
}
|
}
|
||||||
@ -545,10 +646,7 @@ td::Status Blockchain::try_apply_block(Block block, bool validate_state_hash) {
|
|||||||
|
|
||||||
// TODO: validate total size of block
|
// TODO: validate total size of block
|
||||||
auto state = state_;
|
auto state = state_;
|
||||||
// TODO: use hint (state from build_block)
|
TRY_STATUS(state.apply(block, validate_options));
|
||||||
TRY_STATUS(state.apply(block, validate_state_hash));
|
|
||||||
|
|
||||||
TRY_STATUS(state.validate_state(block.state_proof_));
|
|
||||||
|
|
||||||
// NO errors after this point
|
// NO errors after this point
|
||||||
state_ = std::move(state);
|
state_ = std::move(state);
|
||||||
@ -567,14 +665,22 @@ td::int64 Blockchain::get_height() const {
|
|||||||
return last_block_.height_;
|
return last_block_.height_;
|
||||||
}
|
}
|
||||||
|
|
||||||
td::UInt256 as_key(td::Slice key) {
|
td::Result<td::UInt256> as_key(td::Slice key) {
|
||||||
CHECK(key.size() == 32);
|
if (key.size() != 32) {
|
||||||
|
return td::Status::Error("Invalid key size");
|
||||||
|
}
|
||||||
td::UInt256 key_int256;
|
td::UInt256 key_int256;
|
||||||
key_int256.as_mutable_slice().copy_from(key);
|
key_int256.as_mutable_slice().copy_from(key);
|
||||||
|
if (key_int256.is_zero()) {
|
||||||
|
return td::Status::Error("Invalid zero key");
|
||||||
|
}
|
||||||
return key_int256;
|
return key_int256;
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<Blockchain> Blockchain::create_from_block(Block block, td::optional<td::Slice> o_snapshot) {
|
td::Result<Blockchain> Blockchain::create_from_block(Block block, td::optional<td::Slice> o_snapshot) {
|
||||||
|
if (block.height_ < 0) {
|
||||||
|
return Error(E::InvalidBlock, "negative height");
|
||||||
|
}
|
||||||
Blockchain res;
|
Blockchain res;
|
||||||
res.last_block_hash_ = block.calc_hash();
|
res.last_block_hash_ = block.calc_hash();
|
||||||
TRY_RESULT_ASSIGN(res.state_, State::create_from_block(block, std::move(o_snapshot)));
|
TRY_RESULT_ASSIGN(res.state_, State::create_from_block(block, std::move(o_snapshot)));
|
||||||
@ -582,11 +688,51 @@ td::Result<Blockchain> Blockchain::create_from_block(Block block, td::optional<t
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
namespace {
|
||||||
|
bool is_good_magic(td::int32 magic) {
|
||||||
|
return magic == td::e2e_api::e2e_chain_block::ID || magic == td::e2e_api::e2e_chain_groupBroadcastNonceCommit::ID ||
|
||||||
|
magic == td::e2e_api::e2e_chain_groupBroadcastNonceReveal::ID;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool Blockchain::is_from_server(td::Slice block) {
|
||||||
|
if (block.size() < 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
td::int32 server_magic = td::as<td::int32>(block.data());
|
||||||
|
return is_good_magic(server_magic - 1) && !is_good_magic(server_magic);
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Result<std::string> Blockchain::from_any_to_local(std::string block) {
|
||||||
|
if (is_from_server(block)) {
|
||||||
|
return from_server_to_local(std::move(block));
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
td::Result<std::string> Blockchain::from_server_to_local(std::string block) {
|
||||||
|
if (block.size() < 4) {
|
||||||
|
return td::Status::Error("block is too short");
|
||||||
|
}
|
||||||
|
td::int32 server_magic = td::as<td::int32>(block.data());
|
||||||
|
if (is_good_magic(server_magic)) {
|
||||||
|
return td::Status::Error("Trying to apply local block, not from server");
|
||||||
|
}
|
||||||
|
td::int32 real_magic = server_magic - 1;
|
||||||
|
td::as<td::int32>(block.data()) = real_magic;
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
td::Result<std::string> Blockchain::from_local_to_server(std::string block) {
|
||||||
|
if (block.size() < 4) {
|
||||||
|
return td::Status::Error("block is too short");
|
||||||
|
}
|
||||||
|
td::int32 magic = td::as<td::int32>(block.data());
|
||||||
|
td::as<td::int32>(block.data()) = magic + 1;
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
td::Result<ClientBlockchain> ClientBlockchain::create_from_block(td::Slice block_slice, const PublicKey &public_key) {
|
td::Result<ClientBlockchain> ClientBlockchain::create_from_block(td::Slice block_slice, const PublicKey &public_key) {
|
||||||
TRY_RESULT(block, Block::from_tl_serialized(block_slice));
|
TRY_RESULT(block, Block::from_tl_serialized(block_slice));
|
||||||
TRY_RESULT(blockchain, Blockchain::create_from_block(std::move(block)));
|
TRY_RESULT(blockchain, Blockchain::create_from_block(std::move(block)));
|
||||||
// TODO: check public key is in blockchain
|
|
||||||
ClientBlockchain res;
|
ClientBlockchain res;
|
||||||
res.blockchain_ = std::move(blockchain);
|
res.blockchain_ = std::move(blockchain);
|
||||||
return res;
|
return res;
|
||||||
@ -601,11 +747,15 @@ td::Result<ClientBlockchain> ClientBlockchain::create_empty() {
|
|||||||
td::Result<std::vector<Change>> ClientBlockchain::try_apply_block(td::Slice block_slice) {
|
td::Result<std::vector<Change>> ClientBlockchain::try_apply_block(td::Slice block_slice) {
|
||||||
TRY_RESULT(block, Block::from_tl_serialized(block_slice));
|
TRY_RESULT(block, Block::from_tl_serialized(block_slice));
|
||||||
|
|
||||||
TRY_STATUS(blockchain_.try_apply_block(block, false));
|
ValidateOptions validate_options;
|
||||||
|
validate_options.validate_signature = true;
|
||||||
|
validate_options.validate_state_hash = false;
|
||||||
|
TRY_STATUS(blockchain_.try_apply_block(block, validate_options));
|
||||||
for (auto &change : block.changes_) {
|
for (auto &change : block.changes_) {
|
||||||
if (std::holds_alternative<ChangeSetValue>(change.value)) {
|
if (std::holds_alternative<ChangeSetValue>(change.value)) {
|
||||||
auto &change_value = std::get<ChangeSetValue>(change.value);
|
auto &change_value = std::get<ChangeSetValue>(change.value);
|
||||||
map_[as_key(change_value.key)] = Entry{block.height_, change_value.value};
|
auto key = as_key(change_value.key).move_as_ok(); // already verified in try_apply_block
|
||||||
|
map_[key] = Entry{block.height_, change_value.value};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,17 +776,16 @@ td::Status ClientBlockchain::add_proof(td::Slice proof) {
|
|||||||
td::Result<std::string> ClientBlockchain::build_block(const std::vector<Change> &changes,
|
td::Result<std::string> ClientBlockchain::build_block(const std::vector<Change> &changes,
|
||||||
const PrivateKey &private_key) const {
|
const PrivateKey &private_key) const {
|
||||||
TRY_RESULT(block, blockchain_.build_block(changes, private_key));
|
TRY_RESULT(block, blockchain_.build_block(changes, private_key));
|
||||||
//return serialize(*block.to_tl());
|
|
||||||
return block.to_tl_serialized();
|
return block.to_tl_serialized();
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<std::string> ClientBlockchain::get_value(td::Slice key) const {
|
td::Result<std::string> ClientBlockchain::get_value(td::Slice raw_key) const {
|
||||||
auto it = map_.find(as_key(key));
|
TRY_RESULT(key, as_key(raw_key));
|
||||||
|
auto it = map_.find(key);
|
||||||
if (it != map_.end()) {
|
if (it != map_.end()) {
|
||||||
return it->second.value;
|
return it->second.value;
|
||||||
}
|
}
|
||||||
return blockchain_.state_.key_value_state_.get_value(key);
|
return blockchain_.state_.key_value_state_.get_value(raw_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace tde2e_core
|
} // namespace tde2e_core
|
||||||
|
38
third-party/td/td/tde2e/td/e2e/Blockchain.h
vendored
38
third-party/td/td/tde2e/td/e2e/Blockchain.h
vendored
@ -43,6 +43,7 @@ struct GroupParticipant {
|
|||||||
td::int64 user_id{0};
|
td::int64 user_id{0};
|
||||||
td::int32 flags{0};
|
td::int32 flags{0};
|
||||||
PublicKey public_key{};
|
PublicKey public_key{};
|
||||||
|
td::int32 version{0};
|
||||||
bool add_users() const {
|
bool add_users() const {
|
||||||
return (flags & GroupParticipantFlags::AddUsers) != 0;
|
return (flags & GroupParticipantFlags::AddUsers) != 0;
|
||||||
}
|
}
|
||||||
@ -50,7 +51,8 @@ struct GroupParticipant {
|
|||||||
return (flags & GroupParticipantFlags::RemoveUsers) != 0;
|
return (flags & GroupParticipantFlags::RemoveUsers) != 0;
|
||||||
}
|
}
|
||||||
bool operator==(const GroupParticipant &other) const {
|
bool operator==(const GroupParticipant &other) const {
|
||||||
return user_id == other.user_id && flags == other.flags && public_key == other.public_key;
|
return user_id == other.user_id && flags == other.flags && public_key == other.public_key &&
|
||||||
|
version == other.version;
|
||||||
}
|
}
|
||||||
bool operator!=(const GroupParticipant &other) const {
|
bool operator!=(const GroupParticipant &other) const {
|
||||||
return !(other == *this);
|
return !(other == *this);
|
||||||
@ -61,7 +63,8 @@ struct GroupParticipant {
|
|||||||
};
|
};
|
||||||
|
|
||||||
inline td::StringBuilder &operator<<(td::StringBuilder &sb, const GroupParticipant &part) {
|
inline td::StringBuilder &operator<<(td::StringBuilder &sb, const GroupParticipant &part) {
|
||||||
return sb << "(uid=" << part.user_id << ", flags=" << part.flags << ", pk=" << part.public_key << ")";
|
return sb << "(uid=" << part.user_id << ", flags=" << part.flags << ", pk=" << part.public_key
|
||||||
|
<< ", version=" << part.version << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GroupState;
|
struct GroupState;
|
||||||
@ -94,9 +97,10 @@ struct GroupState {
|
|||||||
bool empty() const {
|
bool empty() const {
|
||||||
return participants.empty();
|
return participants.empty();
|
||||||
}
|
}
|
||||||
|
td::int32 version() const;
|
||||||
td::Result<GroupParticipant> get_participant(td::int64 user_id) const;
|
td::Result<GroupParticipant> get_participant(td::int64 user_id) const;
|
||||||
td::Result<GroupParticipant> get_participant(const PublicKey &public_key) const;
|
td::Result<GroupParticipant> get_participant(const PublicKey &public_key) const;
|
||||||
Permissions get_permissions(const PublicKey &public_key) const;
|
Permissions get_permissions(const PublicKey &public_key, td::int32 limit_permissions) const;
|
||||||
static GroupStateRef from_tl(const td::e2e_api::e2e_chain_groupState &state);
|
static GroupStateRef from_tl(const td::e2e_api::e2e_chain_groupState &state);
|
||||||
e2e::object_ptr<e2e::e2e_chain_groupState> to_tl() const;
|
e2e::object_ptr<e2e::e2e_chain_groupState> to_tl() const;
|
||||||
static GroupStateRef empty_state();
|
static GroupStateRef empty_state();
|
||||||
@ -119,6 +123,9 @@ struct GroupSharedKey {
|
|||||||
static GroupSharedKeyRef from_tl(const td::e2e_api::e2e_chain_sharedKey &shared_key);
|
static GroupSharedKeyRef from_tl(const td::e2e_api::e2e_chain_sharedKey &shared_key);
|
||||||
e2e::object_ptr<e2e::e2e_chain_sharedKey> to_tl() const;
|
e2e::object_ptr<e2e::e2e_chain_sharedKey> to_tl() const;
|
||||||
static GroupSharedKeyRef empty_shared_key();
|
static GroupSharedKeyRef empty_shared_key();
|
||||||
|
bool empty() const {
|
||||||
|
return *this == *empty_shared_key();
|
||||||
|
}
|
||||||
bool operator==(const GroupSharedKey &other) const {
|
bool operator==(const GroupSharedKey &other) const {
|
||||||
return ek == other.ek && encrypted_shared_key == other.encrypted_shared_key && dest_user_id == other.dest_user_id &&
|
return ek == other.ek && encrypted_shared_key == other.encrypted_shared_key && dest_user_id == other.dest_user_id &&
|
||||||
dest_header == other.dest_header;
|
dest_header == other.dest_header;
|
||||||
@ -201,13 +208,20 @@ struct StateProof {
|
|||||||
static StateProof from_tl(const td::e2e_api::e2e_chain_stateProof &proof);
|
static StateProof from_tl(const td::e2e_api::e2e_chain_stateProof &proof);
|
||||||
e2e::object_ptr<e2e::e2e_chain_stateProof> to_tl() const;
|
e2e::object_ptr<e2e::e2e_chain_stateProof> to_tl() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
td::StringBuilder &operator<<(td::StringBuilder &sb, const StateProof &state);
|
td::StringBuilder &operator<<(td::StringBuilder &sb, const StateProof &state);
|
||||||
|
struct ValidateOptions {
|
||||||
|
bool validate_state_hash{true};
|
||||||
|
bool validate_signature{true};
|
||||||
|
td::int32 permissions{GroupParticipantFlags::AllPermissions};
|
||||||
|
};
|
||||||
|
|
||||||
struct Block;
|
struct Block;
|
||||||
struct State {
|
struct State {
|
||||||
KeyValueState key_value_state_;
|
KeyValueState key_value_state_;
|
||||||
GroupStateRef group_state_;
|
GroupStateRef group_state_;
|
||||||
GroupSharedKeyRef shared_key_;
|
GroupSharedKeyRef shared_key_;
|
||||||
|
bool has_set_value_{};
|
||||||
bool has_shared_key_change_{};
|
bool has_shared_key_change_{};
|
||||||
bool has_group_state_change_{};
|
bool has_group_state_change_{};
|
||||||
|
|
||||||
@ -223,18 +237,19 @@ struct State {
|
|||||||
static State create_empty();
|
static State create_empty();
|
||||||
static td::Result<State> create_from_block(const Block &block, td::optional<td::Slice> o_snapshot = {});
|
static td::Result<State> create_from_block(const Block &block, td::optional<td::Slice> o_snapshot = {});
|
||||||
|
|
||||||
// TODO snapshot..
|
|
||||||
// TODO apply
|
|
||||||
td::Status set_value(td::Slice key, td::Slice value, const Permissions &permissions);
|
td::Status set_value(td::Slice key, td::Slice value, const Permissions &permissions);
|
||||||
td::Status set_group_state(GroupStateRef group_state, const Permissions &permissions);
|
td::Status set_group_state(GroupStateRef group_state, const Permissions &permissions);
|
||||||
td::Status clear_shared_key(const Permissions &permissions);
|
td::Status clear_shared_key(const Permissions &permissions);
|
||||||
td::Status set_shared_key(GroupSharedKeyRef shared_key, const Permissions &permissions);
|
td::Status set_shared_key(GroupSharedKeyRef shared_key, const Permissions &permissions);
|
||||||
td::Status set_value_fast(KeyValueHash key_value_hash);
|
td::Status set_value_fast(const KeyValueHash &key_value_hash);
|
||||||
td::Status apply_change(const Change &change_outer, const PublicKey &public_key, bool full_apply);
|
td::Status apply_change(const Change &change_outer, const PublicKey &public_key, const ValidateOptions &options);
|
||||||
|
|
||||||
td::Status apply(Block &block, bool validate_state_hash = true);
|
td::Status apply(Block &block, ValidateOptions validate_options = {});
|
||||||
|
|
||||||
td::Status validate_state(const StateProof &state_proof) const;
|
td::Status validate_state(const StateProof &state_proof) const;
|
||||||
|
|
||||||
|
static td::Status validate_group_state(const GroupStateRef &group_state);
|
||||||
|
static td::Status validate_shared_key(const GroupSharedKeyRef &shared_key, const GroupStateRef &group_state);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Block {
|
struct Block {
|
||||||
@ -270,8 +285,13 @@ struct Blockchain {
|
|||||||
}
|
}
|
||||||
static td::Result<Blockchain> create_from_block(Block block, td::optional<td::Slice> o_snapshot = {});
|
static td::Result<Blockchain> create_from_block(Block block, td::optional<td::Slice> o_snapshot = {});
|
||||||
|
|
||||||
|
static bool is_from_server(td::Slice block);
|
||||||
|
static td::Result<std::string> from_any_to_local(std::string block);
|
||||||
|
static td::Result<std::string> from_server_to_local(std::string block);
|
||||||
|
static td::Result<std::string> from_local_to_server(std::string block);
|
||||||
|
|
||||||
td::Result<Block> build_block(std::vector<Change> changes, const PrivateKey &private_key) const;
|
td::Result<Block> build_block(std::vector<Change> changes, const PrivateKey &private_key) const;
|
||||||
td::Status try_apply_block(Block block, bool validate_state_hash = true);
|
td::Status try_apply_block(Block block, ValidateOptions validate_options);
|
||||||
Block set_value(td::Slice key, td::Slice value, const PrivateKey &private_key) const;
|
Block set_value(td::Slice key, td::Slice value, const PrivateKey &private_key) const;
|
||||||
td::int64 get_height() const;
|
td::int64 get_height() const;
|
||||||
|
|
||||||
|
56
third-party/td/td/tde2e/td/e2e/Blockchain.md
vendored
56
third-party/td/td/tde2e/td/e2e/Blockchain.md
vendored
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#TODO
|
#TODO
|
||||||
- version of supported encryption protocol in each participant (should use the lowest one)
|
- version of supported encryption protocol in each participant (should use the lowest one)
|
||||||
- broadcast packets should be applied only after corresponding key is applied
|
- store hash of block in broadcast
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ The blockchain supports four types of changes:
|
|||||||
|
|
||||||
2. **ChangeSetGroupState**: Updates the group of participants and their permissions
|
2. **ChangeSetGroupState**: Updates the group of participants and their permissions
|
||||||
```
|
```
|
||||||
e2e.chain.groupParticipant user_id:long public_key:int256 flags:# add_users:flags.0?true remove_users:flags.1?true = e2e.chain.GroupParticipant;
|
e2e.chain.groupParticipant user_id:long public_key:int256 flags:# add_users:flags.0?true remove_users:flags.1?true version:int = e2e.chain.GroupParticipant;
|
||||||
e2e.chain.groupState participants:vector<e2e.chain.GroupParticipant> = e2e.chain.GroupState;
|
e2e.chain.groupState participants:vector<e2e.chain.GroupParticipant> = e2e.chain.GroupState;
|
||||||
e2e.chain.changeSetGroupState group_state:e2e.chain.GroupState = e2e.chain.Change;
|
e2e.chain.changeSetGroupState group_state:e2e.chain.GroupState = e2e.chain.Change;
|
||||||
```
|
```
|
||||||
@ -68,7 +68,7 @@ Participants in the blockchain have specific permissions:
|
|||||||
- **RemoveUsers**: Can remove existing participants from the blockchain
|
- **RemoveUsers**: Can remove existing participants from the blockchain
|
||||||
|
|
||||||
```
|
```
|
||||||
e2e.chain.groupParticipant user_id:long public_key:int256 flags:# add_users:flags.0?true remove_users:flags.1?true = e2e.chain.GroupParticipant;
|
e2e.chain.groupParticipant user_id:long public_key:int256 flags:# add_users:flags.0?true remove_users:flags.1?true version:int = e2e.chain.GroupParticipant;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Implementation Details
|
### Implementation Details
|
||||||
@ -185,5 +185,51 @@ The client library does not store the entire key-value state. To create a block,
|
|||||||
1. It should be impossible to create a new key if the user is not in the group, even if the key is automatically removed after a state change.
|
1. It should be impossible to create a new key if the user is not in the group, even if the key is automatically removed after a state change.
|
||||||
2. It should be impossible to create a key if a participant has the Add or Remove permissions.
|
2. It should be impossible to create a key if a participant has the Add or Remove permissions.
|
||||||
3. Statement (1) implies that it is impossible to remove yourself from the group.
|
3. Statement (1) implies that it is impossible to remove yourself from the group.
|
||||||
# End of Selection
|
|
||||||
```
|
TODO:
|
||||||
|
+ handle short keys in SetValue
|
||||||
|
+ dest_user_id and dest_header must be the same size
|
||||||
|
+ check proto version in call encryption
|
||||||
|
+ Do I need some permissions to raise others flags -- NO, just Add AND Remove
|
||||||
|
+ validate group state from received block
|
||||||
|
+ validate keys
|
||||||
|
+ verify that generated block will indeed apply -- It is checked but, not explicitly
|
||||||
|
+ verify creator of network packet (pass user_id into decrypt method. Or even user_id and public key?)
|
||||||
|
+ sort nonces
|
||||||
|
+ what if we change nothing in block
|
||||||
|
+ fail if height overflows
|
||||||
|
+ CHECK(blockhain.get_height() > height_);
|
||||||
|
+ turn off the log by default
|
||||||
|
+ return specific error in get_status
|
||||||
|
+ optimize hmac
|
||||||
|
+ remove user_id from packet?
|
||||||
|
+ encrypt key (simple version)
|
||||||
|
+ channel_id in encryption
|
||||||
|
+ user_id instead of public_key in GroupBroadcast ?
|
||||||
|
+ received_messages_ in CallVerificationChain are not needed for clients
|
||||||
|
+ turn off signature verification on server
|
||||||
|
+ fix up magic of block on server
|
||||||
|
+ remove user_id from payload (we already got it externally)
|
||||||
|
|
||||||
|
- ! VERIFY block signature during creation blockchain from block
|
||||||
|
it is impossible to do currently using just current block, because we don't know who signe the block
|
||||||
|
|
||||||
|
later:
|
||||||
|
-+ fix tl constructor?
|
||||||
|
- add magic in container's id
|
||||||
|
- ttl of chain on server
|
||||||
|
|
||||||
|
-------
|
||||||
|
|
||||||
|
# Security guidance
|
||||||
|
|
||||||
|
There are several we should be really careful about.
|
||||||
|
|
||||||
|
1. Client must apply only blocks received from server. It is especially important for blocks created by the client itself. Sever does extra work to ensure correctness of blocks and that the will be no forks. Forks per se are not a security problem, but they would lead to a broken call
|
||||||
|
|
||||||
|
2. Blocks should be sent to the server till it some answer is received. Either it is success, or error. In case of error INVALID_BLOCK__HASH_MISMATCH, new block could be created, but one should be careful about how group state has been changed.
|
||||||
|
|
||||||
|
3. Broadcast blocks should also be sent till accepted or declined. Probably it should be allowed to skip older packets.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
522
third-party/td/td/tde2e/td/e2e/Call.cpp
vendored
522
third-party/td/td/tde2e/td/e2e/Call.cpp
vendored
@ -24,6 +24,7 @@
|
|||||||
#include "td/utils/tl_parsers.h"
|
#include "td/utils/tl_parsers.h"
|
||||||
#include "td/utils/tl_storers.h"
|
#include "td/utils/tl_storers.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@ -34,24 +35,38 @@ CallVerificationChain::State CallVerificationChain::get_state() const {
|
|||||||
return state_;
|
return state_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
struct LambdaStorer {
|
||||||
|
const F &store_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class F, class StorerT>
|
||||||
|
void store(const LambdaStorer<F> &lambda_storer, StorerT &storer) {
|
||||||
|
lambda_storer.store_(storer);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
std::string lambda_serialize(F &&f) {
|
||||||
|
return td::serialize(LambdaStorer<F>{std::forward<F>(f)});
|
||||||
|
}
|
||||||
|
|
||||||
void CallVerificationChain::on_new_main_block(const Blockchain &blockhain) {
|
void CallVerificationChain::on_new_main_block(const Blockchain &blockhain) {
|
||||||
state_ = Commit;
|
state_ = Commit;
|
||||||
CHECK(blockhain.get_height() >= height_);
|
CHECK(blockhain.get_height() > height_);
|
||||||
height_ = td::narrow_cast<td::int32>(blockhain.get_height());
|
height_ = td::narrow_cast<td::int32>(blockhain.get_height());
|
||||||
last_block_hash_ = blockhain.last_block_hash_;
|
last_block_hash_ = blockhain.last_block_hash_;
|
||||||
verification_state_ = {};
|
verification_state_ = {};
|
||||||
verification_state_.height = height_;
|
verification_state_.height = height_;
|
||||||
|
|
||||||
verification_words_ = CallVerificationWords{blockhain.last_block_.height_,
|
verification_words_ =
|
||||||
Mnemonic::generate_verification_words(last_block_hash_.as_slice())};
|
CallVerificationWords{height_, Mnemonic::generate_verification_words(last_block_hash_.as_slice())};
|
||||||
auto group_state = *blockhain.state_.group_state_;
|
auto &group_state = *blockhain.state_.group_state_;
|
||||||
committed_ = {};
|
committed_ = {};
|
||||||
revealed_ = {};
|
revealed_ = {};
|
||||||
received_messages_ = {};
|
|
||||||
|
|
||||||
participant_keys_ = {};
|
participant_keys_ = {};
|
||||||
for (auto &participant : group_state.participants) {
|
for (auto &participant : group_state.participants) {
|
||||||
participant_keys_.emplace(participant.public_key, participant.user_id);
|
participant_keys_.emplace(participant.user_id, participant.public_key);
|
||||||
}
|
}
|
||||||
CHECK(participant_keys_.size() == group_state.participants.size());
|
CHECK(participant_keys_.size() == group_state.participants.size());
|
||||||
|
|
||||||
@ -69,20 +84,24 @@ td::Status CallVerificationChain::try_apply_block(td::Slice message) {
|
|||||||
auto kv_broadcast = e2e::e2e_chain_GroupBroadcast::fetch(parser);
|
auto kv_broadcast = e2e::e2e_chain_GroupBroadcast::fetch(parser);
|
||||||
parser.fetch_end();
|
parser.fetch_end();
|
||||||
TRY_STATUS(parser.get_status());
|
TRY_STATUS(parser.get_status());
|
||||||
td::Status status;
|
|
||||||
|
|
||||||
td::int32 broadcast_height{-1};
|
td::int32 chain_height{-1};
|
||||||
downcast_call(*kv_broadcast, td::overloaded([&](auto &broadcast) { broadcast_height = broadcast.chain_height_; }));
|
downcast_call(*kv_broadcast, td::overloaded([&](auto &broadcast) { chain_height = broadcast.chain_height_; }));
|
||||||
|
|
||||||
if (broadcast_height < height_) {
|
if (chain_height < height_) {
|
||||||
LOG(INFO) << "skip old broadcast " << to_short_string(kv_broadcast);
|
LOG(INFO) << "skip old broadcast " << to_short_string(kv_broadcast);
|
||||||
// broadcast is too old
|
// broadcast is too old
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (broadcast_height > height_) {
|
if (chain_height > height_) {
|
||||||
|
if (!delay_allowed_) {
|
||||||
|
return Error(E::InvalidBroadcast_InFuture, PSLICE()
|
||||||
|
<< "broadcast_height=" << chain_height << " height=" << height_);
|
||||||
|
}
|
||||||
|
|
||||||
LOG(INFO) << "delay broadcast " << to_short_string(kv_broadcast);
|
LOG(INFO) << "delay broadcast " << to_short_string(kv_broadcast);
|
||||||
delayed_broadcasts_[broadcast_height].emplace_back(message.str(), std::move(kv_broadcast));
|
delayed_broadcasts_[chain_height].emplace_back(message.str(), std::move(kv_broadcast));
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,18 +109,17 @@ td::Status CallVerificationChain::try_apply_block(td::Slice message) {
|
|||||||
}
|
}
|
||||||
std::string CallVerificationChain::to_short_string(e2e::object_ptr<e2e::e2e_chain_GroupBroadcast> &broadcast) {
|
std::string CallVerificationChain::to_short_string(e2e::object_ptr<e2e::e2e_chain_GroupBroadcast> &broadcast) {
|
||||||
td::StringBuilder sb;
|
td::StringBuilder sb;
|
||||||
downcast_call(*broadcast, td::overloaded([&](e2e::e2e_chain_groupBroadcastNonceCommit &commit) { sb << "CommitBroadcast"; },
|
downcast_call(*broadcast,
|
||||||
|
td::overloaded([&](e2e::e2e_chain_groupBroadcastNonceCommit &commit) { sb << "CommitBroadcast"; },
|
||||||
[&](e2e::e2e_chain_groupBroadcastNonceReveal &reveal) { sb << "RevealBroadcast"; }));
|
[&](e2e::e2e_chain_groupBroadcastNonceReveal &reveal) { sb << "RevealBroadcast"; }));
|
||||||
downcast_call(*broadcast, [&](auto &v) {
|
downcast_call(*broadcast, [&](auto &v) {
|
||||||
sb << "{height=" << v.chain_height_;
|
sb << "{height=" << v.chain_height_ << " user_id=" << v.user_id_;
|
||||||
auto public_key = PublicKey::from_u256(v.public_key_);
|
auto it = participant_keys_.find(v.user_id_);
|
||||||
auto it = participant_keys_.find(public_key);
|
|
||||||
if (it != participant_keys_.end()) {
|
if (it != participant_keys_.end()) {
|
||||||
sb << " user_id=" << it->second;
|
sb << " pk=" << it->second;
|
||||||
} else {
|
} else {
|
||||||
sb << " user_id=?";
|
sb << " pk=?";
|
||||||
}
|
}
|
||||||
sb << " " << public_key;
|
|
||||||
sb << "}";
|
sb << "}";
|
||||||
});
|
});
|
||||||
return sb.as_cslice().str();
|
return sb.as_cslice().str();
|
||||||
@ -110,18 +128,22 @@ std::string CallVerificationChain::to_short_string(e2e::object_ptr<e2e::e2e_chai
|
|||||||
td::Status CallVerificationChain::process_broadcast(std::string message,
|
td::Status CallVerificationChain::process_broadcast(std::string message,
|
||||||
e2e::object_ptr<e2e::e2e_chain_GroupBroadcast> broadcast) {
|
e2e::object_ptr<e2e::e2e_chain_GroupBroadcast> broadcast) {
|
||||||
td::Status status;
|
td::Status status;
|
||||||
|
td::UInt256 got_chain_hash{};
|
||||||
|
downcast_call(*broadcast, td::overloaded([&](auto &broadcast) { got_chain_hash = broadcast.chain_hash_; }));
|
||||||
|
if (got_chain_hash != last_block_hash_) {
|
||||||
|
status = Error(E::InvalidBroadcast_InvalidBlockHash);
|
||||||
|
}
|
||||||
|
if (status.is_ok()) {
|
||||||
downcast_call(
|
downcast_call(
|
||||||
*broadcast,
|
*broadcast,
|
||||||
td::overloaded([&](e2e::e2e_chain_groupBroadcastNonceCommit &commit) { status = process_broadcast(commit); },
|
td::overloaded([&](e2e::e2e_chain_groupBroadcastNonceCommit &commit) { status = process_broadcast(commit); },
|
||||||
[&](e2e::e2e_chain_groupBroadcastNonceReveal &reveal) { status = process_broadcast(reveal); }));
|
[&](e2e::e2e_chain_groupBroadcastNonceReveal &reveal) { status = process_broadcast(reveal); }));
|
||||||
if (status.is_ok()) {
|
|
||||||
received_messages_.push_back(std::move(message));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.is_error()) {
|
if (status.is_error()) {
|
||||||
LOG(ERROR) << "Failed broadcast\n" << to_short_string(broadcast) << "\n\t" << status;
|
LOG(ERROR) << "Failed broadcast\n" << to_short_string(broadcast) << "\n\t" << status;
|
||||||
} else {
|
} else {
|
||||||
LOG(INFO) << "Applied broadcast\n\t" << to_short_string(broadcast) << "\n\t" << *this;
|
LOG(DEBUG) << "Applied broadcast\n\t" << to_short_string(broadcast) << "\n\t" << *this;
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@ -132,28 +154,26 @@ CallVerificationState CallVerificationChain::get_verification_state() const {
|
|||||||
CallVerificationWords CallVerificationChain::get_verification_words() const {
|
CallVerificationWords CallVerificationChain::get_verification_words() const {
|
||||||
return verification_words_;
|
return verification_words_;
|
||||||
}
|
}
|
||||||
td::Span<std::string> CallVerificationChain::received_messages() const {
|
|
||||||
return received_messages_;
|
|
||||||
}
|
|
||||||
td::Status CallVerificationChain::process_broadcast(e2e::e2e_chain_groupBroadcastNonceCommit &nonce_commit) {
|
td::Status CallVerificationChain::process_broadcast(e2e::e2e_chain_groupBroadcastNonceCommit &nonce_commit) {
|
||||||
if (nonce_commit.chain_height_ != height_) {
|
CHECK(nonce_commit.chain_height_ == height_);
|
||||||
return td::Status::Error(PSLICE() << "Invalid height expected=" << height_
|
|
||||||
<< " received=" << nonce_commit.chain_height_);
|
|
||||||
}
|
|
||||||
if (state_ != Commit) {
|
if (state_ != Commit) {
|
||||||
return td::Status::Error("We are not in commit state");
|
return Error(E::InvalidBroadcast_NotInCommit);
|
||||||
}
|
}
|
||||||
auto public_key = PublicKey::from_u256(nonce_commit.public_key_);
|
auto user_id = nonce_commit.user_id_;
|
||||||
if (participant_keys_.count(public_key) == 0) {
|
auto it = participant_keys_.find(user_id);
|
||||||
return td::Status::Error("NonceCommit: unknown public key");
|
if (it == participant_keys_.end()) {
|
||||||
|
return Error(E::InvalidBroadcast_UnknownUserId);
|
||||||
}
|
}
|
||||||
|
auto public_key = it->second;
|
||||||
|
if (!may_skip_signatures_validation_) {
|
||||||
TRY_STATUS(verify_signature(public_key, nonce_commit));
|
TRY_STATUS(verify_signature(public_key, nonce_commit));
|
||||||
|
|
||||||
if (committed_.count(public_key) != 0) {
|
|
||||||
return td::Status::Error("NonceCommit: duplicate commit");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
committed_[public_key] = nonce_commit.nonce_hash_.as_slice().str();
|
if (committed_.count(user_id) != 0) {
|
||||||
|
return Error(E::InvalidBroadcast_AlreadyApplied);
|
||||||
|
}
|
||||||
|
|
||||||
|
committed_[user_id] = nonce_commit.nonce_hash_.as_slice().str();
|
||||||
|
|
||||||
if (committed_.size() == participant_keys_.size()) {
|
if (committed_.size() == participant_keys_.size()) {
|
||||||
state_ = Reveal;
|
state_ = Reveal;
|
||||||
@ -162,162 +182,237 @@ td::Status CallVerificationChain::process_broadcast(e2e::e2e_chain_groupBroadcas
|
|||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
td::Status CallVerificationChain::process_broadcast(e2e::e2e_chain_groupBroadcastNonceReveal &nonce_reveal) {
|
td::Status CallVerificationChain::process_broadcast(e2e::e2e_chain_groupBroadcastNonceReveal &nonce_reveal) {
|
||||||
if (nonce_reveal.chain_height_ != height_) {
|
CHECK(nonce_reveal.chain_height_ == height_);
|
||||||
return td::Status::Error("NonceReveal: Invalid height");
|
|
||||||
}
|
|
||||||
if (state_ != Reveal) {
|
if (state_ != Reveal) {
|
||||||
return td::Status::Error("We are not in reveal state");
|
return Error(E::InvalidBroadcast_NotInReveal);
|
||||||
}
|
}
|
||||||
auto public_key = PublicKey::from_u256(nonce_reveal.public_key_);
|
auto user_id = nonce_reveal.user_id_;
|
||||||
if (participant_keys_.count(public_key) == 0) {
|
auto user_id_it = participant_keys_.find(user_id);
|
||||||
return td::Status::Error("NonceReveal: unknown public key");
|
if (user_id_it == participant_keys_.end()) {
|
||||||
|
return Error(E::InvalidBroadcast_UnknownUserId);
|
||||||
}
|
}
|
||||||
|
auto public_key = user_id_it->second;
|
||||||
|
if (!may_skip_signatures_validation_) {
|
||||||
TRY_STATUS(verify_signature(public_key, nonce_reveal));
|
TRY_STATUS(verify_signature(public_key, nonce_reveal));
|
||||||
|
|
||||||
if (revealed_.count(public_key) != 0) {
|
|
||||||
return td::Status::Error("NonceReveal: duplicate reveal");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto it = committed_.find(public_key);
|
if (revealed_.count(user_id) != 0) {
|
||||||
|
return Error(E::InvalidBroadcast_AlreadyApplied);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = committed_.find(user_id);
|
||||||
CHECK(it != committed_.end());
|
CHECK(it != committed_.end());
|
||||||
auto expected_nonce_hash = it->second;
|
auto expected_nonce_hash = it->second;
|
||||||
auto received_nonce_hash = td::sha256(nonce_reveal.nonce_.as_slice());
|
auto received_nonce_hash = td::sha256(nonce_reveal.nonce_.as_slice());
|
||||||
if (expected_nonce_hash != received_nonce_hash) {
|
if (expected_nonce_hash != received_nonce_hash) {
|
||||||
return td::Status::Error("NonceReveal: hash(nonce) != nonce_hash");
|
return Error(E::InvalidBroadcast_InvalidReveal);
|
||||||
}
|
}
|
||||||
|
|
||||||
revealed_[public_key] = nonce_reveal.nonce_.as_slice().str();
|
revealed_[user_id] = nonce_reveal.nonce_.as_slice().str();
|
||||||
|
|
||||||
CHECK(!verification_state_.emoji_hash);
|
CHECK(!verification_state_.emoji_hash);
|
||||||
if (revealed_.size() == participant_keys_.size()) {
|
if (revealed_.size() == participant_keys_.size()) {
|
||||||
|
auto nonces = td::transform(revealed_, [](auto &p) { return p.second; });
|
||||||
|
std::sort(nonces.begin(), nonces.end());
|
||||||
|
|
||||||
std::string full_nonce;
|
std::string full_nonce;
|
||||||
for (auto &[key, nonce] : revealed_) {
|
for (auto &nonce : nonces) {
|
||||||
full_nonce += nonce;
|
full_nonce += nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
verification_state_.emoji_hash =
|
verification_state_.emoji_hash =
|
||||||
MessageEncryption::combine_secrets(last_block_hash_.as_slice(), full_nonce).as_slice().str();
|
MessageEncryption::hmac_sha512(full_nonce, last_block_hash_.as_slice()).as_slice().str();
|
||||||
state_ = End;
|
state_ = End;
|
||||||
}
|
}
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
CallEncryption::CallEncryption(PrivateKey private_key) : private_key_(std::move(private_key)) {
|
CallEncryption::CallEncryption(td::int64 user_id, PrivateKey private_key)
|
||||||
|
: user_id_(user_id), private_key_(std::move(private_key)) {
|
||||||
}
|
}
|
||||||
td::Status CallEncryption::add_shared_key(td::int32 epoch, td::SecureString key, GroupStateRef group_state) {
|
td::Status CallEncryption::add_shared_key(td::int32 epoch, td::SecureString key, GroupStateRef group_state) {
|
||||||
forget_old_epochs();
|
sync();
|
||||||
CHECK(!o_last_epoch_ || *o_last_epoch_ + 1 == epoch);
|
|
||||||
if (o_last_epoch_) {
|
|
||||||
epochs_to_forget_.emplace(td::Timestamp::in(10), *o_last_epoch_);
|
|
||||||
}
|
|
||||||
o_last_epoch_ = epoch;
|
|
||||||
|
|
||||||
TRY_RESULT(self, group_state->get_participant(private_key_.to_public_key()));
|
TRY_RESULT(self, group_state->get_participant(private_key_.to_public_key()));
|
||||||
|
if (self.user_id != user_id_) {
|
||||||
|
// should not happen
|
||||||
|
return td::Status::Error("Wrong user id in state");
|
||||||
|
}
|
||||||
|
|
||||||
auto added =
|
LOG(INFO) << "Add key from epoch: " << epoch;
|
||||||
encryptor_by_epoch_
|
auto added = epochs_.emplace(epoch, EpochInfo(epoch, self.user_id, std::move(key), std::move(group_state))).second;
|
||||||
.emplace(epoch, EpochEncryptor(epoch, self.user_id, std::move(key), std::move(group_state), private_key_))
|
|
||||||
.second;
|
|
||||||
CHECK(added);
|
CHECK(added);
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
td::Result<std::string> CallEncryption::decrypt(td::Slice encrypted_data) {
|
void CallEncryption::forget_shared_key(td::int32 epoch) {
|
||||||
forget_old_epochs();
|
sync();
|
||||||
td::TlParser parser(encrypted_data);
|
epochs_to_forget_.emplace(td::Timestamp::in(FORGET_EPOCH_DELAY), epoch);
|
||||||
auto epoch = parser.fetch_int();
|
|
||||||
TRY_STATUS(parser.get_status());
|
|
||||||
auto it = encryptor_by_epoch_.find(epoch);
|
|
||||||
if (it == encryptor_by_epoch_.end()) {
|
|
||||||
return Error(E::Decrypt_UnknownEpoch);
|
|
||||||
}
|
|
||||||
return it->second.decrypt(encrypted_data);
|
|
||||||
}
|
|
||||||
td::Result<std::string> CallEncryption::encrypt(td::Slice decrypted_data) {
|
|
||||||
CHECK(o_last_epoch_);
|
|
||||||
auto it = encryptor_by_epoch_.find(*o_last_epoch_);
|
|
||||||
if (it == encryptor_by_epoch_.end()) {
|
|
||||||
return Error(E::Encrypt_UnknownEpoch);
|
|
||||||
}
|
|
||||||
return it->second.encrypt(decrypted_data);
|
|
||||||
}
|
|
||||||
CallEncryption::EpochEncryptor::EpochEncryptor(td::int32 epoch, td::int64 user_id, td::SecureString secret,
|
|
||||||
GroupStateRef group_state, PrivateKey private_key)
|
|
||||||
: epoch_(epoch)
|
|
||||||
, user_id_(user_id)
|
|
||||||
, secret_(std::move(secret))
|
|
||||||
, group_state_(std::move(group_state))
|
|
||||||
, private_key_(std::move(private_key)) {
|
|
||||||
}
|
|
||||||
td::Result<std::string> CallEncryption::EpochEncryptor::decrypt(td::Slice encrypted_data) {
|
|
||||||
td::int32 epoch{};
|
|
||||||
using td::parse;
|
|
||||||
{
|
|
||||||
td::TlParser parser(encrypted_data);
|
|
||||||
parse(epoch, parser);
|
|
||||||
TRY_STATUS(parser.get_status());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TRY_RESULT(payload, MessageEncryption::decrypt_data(encrypted_data.substr(4), secret_));
|
td::Result<std::string> CallEncryption::decrypt(td::int64 user_id, td::int32 channel_id, td::Slice encrypted_data) {
|
||||||
|
sync();
|
||||||
|
if (user_id == user_id_) {
|
||||||
|
return td::Status::Error("Packet is encrypted by us");
|
||||||
|
}
|
||||||
|
td::TlParser parser(encrypted_data);
|
||||||
|
auto head = static_cast<td::uint32>(parser.fetch_int());
|
||||||
|
td::int32 epochs_n = head & 0xff;
|
||||||
|
auto version = (head >> 8) & 0xff;
|
||||||
|
auto reserved = head >> 16;
|
||||||
|
|
||||||
|
if (version != 0) {
|
||||||
|
return td::Status::Error("Unsupported protocol version");
|
||||||
|
}
|
||||||
|
if (reserved != 0) {
|
||||||
|
return td::Status::Error("reserved part of head is not zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (epochs_n > MAX_ACTIVE_EPOCHS) {
|
||||||
|
return td::Status::Error("Too many active epochs");
|
||||||
|
}
|
||||||
|
|
||||||
|
using td::parse;
|
||||||
|
|
||||||
|
std::vector<td::int32> epochs;
|
||||||
|
for (int i = 0; i < epochs_n; i++) {
|
||||||
|
epochs.push_back(parser.fetch_int());
|
||||||
|
}
|
||||||
|
auto unencrypted_header = encrypted_data.substr(0, encrypted_data.size() - parser.get_left_len());
|
||||||
|
|
||||||
|
std::vector<td::Slice> encrypted_headers;
|
||||||
|
for (int i = 0; i < epochs_n; i++) {
|
||||||
|
auto encrypted_header = parser.fetch_string_raw<td::Slice>(32);
|
||||||
|
encrypted_headers.emplace_back(encrypted_header);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto encrypted_packet = parser.fetch_string_raw<td::Slice>(parser.get_left_len());
|
||||||
|
parser.fetch_end();
|
||||||
|
TRY_STATUS(parser.get_status());
|
||||||
|
|
||||||
|
for (td::int32 i = 0; i < epochs_n; i++) {
|
||||||
|
auto epoch = epochs[i];
|
||||||
|
auto encrypted_header = encrypted_headers[i];
|
||||||
|
if (auto it = epochs_.find(epoch); it != epochs_.end()) {
|
||||||
|
auto &epoch_info = it->second;
|
||||||
|
TRY_RESULT(one_time_secret,
|
||||||
|
MessageEncryption::decrypt_header(encrypted_header, encrypted_packet, epoch_info.secret_));
|
||||||
|
return decrypt_packet_with_secret(user_id, channel_id, unencrypted_header, encrypted_packet, one_time_secret,
|
||||||
|
epoch_info.group_state_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Error(E::Decrypt_UnknownEpoch);
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Result<std::string> CallEncryption::encrypt(td::int32 channel_id, td::Slice decrypted_data) {
|
||||||
|
sync();
|
||||||
|
|
||||||
|
// use all active epochs
|
||||||
|
if (epochs_.empty()) {
|
||||||
|
return Error(E::Encrypt_UnknownEpoch);
|
||||||
|
}
|
||||||
|
auto epochs_n = td::narrow_cast<td::int32>(epochs_.size());
|
||||||
|
|
||||||
|
using td::store;
|
||||||
|
std::string header_a = lambda_serialize([&](auto &storer) {
|
||||||
|
store(epochs_n, storer);
|
||||||
|
for (auto &[epoch_i, epoch] : epochs_) {
|
||||||
|
store(epoch_i, storer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
td::SecureString one_time_secret(32, 0);
|
||||||
|
td::Random::secure_bytes(one_time_secret.as_mutable_slice());
|
||||||
|
TRY_RESULT(encrypted_packet, encrypt_packet_with_secret(channel_id, header_a, decrypted_data, one_time_secret));
|
||||||
|
|
||||||
|
std::vector<td::SecureString> encrypted_headers;
|
||||||
|
for (auto &[epoch_i, epoch] : epochs_) {
|
||||||
|
TRY_RESULT(encrypted_header, MessageEncryption::encrypt_header(one_time_secret, encrypted_packet, epoch.secret_));
|
||||||
|
encrypted_headers.emplace_back(std::move(encrypted_header));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string header_b = lambda_serialize([&](auto &storer) {
|
||||||
|
for (auto &encrypted_header : encrypted_headers) {
|
||||||
|
CHECK(encrypted_header.size() == 32);
|
||||||
|
storer.store_slice(encrypted_header);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//LOG(ERROR) << decrypted_data.size() << " -> " << header_a.size() << " + " << header_b.size() << " + " << encrypted_packet.size();
|
||||||
|
return header_a + header_b + encrypted_packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Result<std::string> CallEncryption::encrypt_packet_with_secret(td::int32 channel_id, td::Slice unencrypted_part,
|
||||||
|
td::Slice packet, td::Slice one_time_secret) {
|
||||||
|
TRY_STATUS(validate_channel_id(channel_id));
|
||||||
|
auto &seqno = seqno_[channel_id];
|
||||||
|
if (seqno == std::numeric_limits<td::uint32>::max()) {
|
||||||
|
return td::Status::Error("Seqno overflow");
|
||||||
|
}
|
||||||
|
seqno++;
|
||||||
|
|
||||||
|
td::UInt512 zero_signature{};
|
||||||
|
auto payload = lambda_serialize([&](auto &storer) {
|
||||||
|
using td::store;
|
||||||
|
store(static_cast<td::int32>(channel_id), storer);
|
||||||
|
store(seqno, storer);
|
||||||
|
storer.store_slice(packet);
|
||||||
|
storer.store_slice(zero_signature.as_mutable_slice());
|
||||||
|
});
|
||||||
|
CHECK(payload.size() >= 64);
|
||||||
|
auto signature_offset = payload.size() - 64;
|
||||||
|
auto encrypted_part = td::Slice(payload.data(), signature_offset);
|
||||||
|
auto to_sign = MessageEncryption::hmac_sha512(unencrypted_part, encrypted_part);
|
||||||
|
|
||||||
|
TRY_RESULT(signature, private_key_.sign(to_sign));
|
||||||
|
td::MutableSlice(payload).substr(signature_offset).copy_from(signature.to_slice());
|
||||||
|
|
||||||
|
// TODO: there is too much copies happening here. Almost all of them could be avoided
|
||||||
|
return MessageEncryption::encrypt_data(payload, one_time_secret).as_slice().str();
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Result<std::string> CallEncryption::decrypt_packet_with_secret(
|
||||||
|
td::int64 expected_user_id, td::int32 expected_channel_id, td::Slice unencrypted_header, td::Slice encrypted_packet,
|
||||||
|
td::Slice one_time_secret, const GroupStateRef &group_state) {
|
||||||
|
TRY_RESULT(participant, group_state->get_participant(expected_user_id));
|
||||||
|
TRY_RESULT(payload_str, MessageEncryption::decrypt_data(encrypted_packet, one_time_secret));
|
||||||
|
auto payload = td::Slice(payload_str);
|
||||||
|
if (payload.size() < 64) {
|
||||||
|
return td::Status::Error("Not enough encryption data");
|
||||||
|
}
|
||||||
|
auto signature_offset = payload.size() - 64;
|
||||||
|
|
||||||
|
auto encrypted_part = payload.substr(0, signature_offset);
|
||||||
|
auto to_verify = MessageEncryption::hmac_sha512(unencrypted_header, encrypted_part);
|
||||||
|
|
||||||
|
TRY_RESULT(signature, Signature::from_slice(payload.substr(signature_offset)));
|
||||||
|
TRY_STATUS(participant.public_key.verify(to_verify, signature));
|
||||||
|
|
||||||
|
// we know that this is packet create by some participant
|
||||||
td::TlParser parser(payload);
|
td::TlParser parser(payload);
|
||||||
td::int64 user_id;
|
td::int32 channel_id;
|
||||||
td::uint32 seqno{};
|
td::uint32 seqno{};
|
||||||
td::UInt512 signature{};
|
td::UInt512 signature_to_skip{};
|
||||||
parse(user_id, parser);
|
parse(channel_id, parser);
|
||||||
|
TRY_STATUS(validate_channel_id(channel_id));
|
||||||
parse(seqno, parser);
|
parse(seqno, parser);
|
||||||
if (parser.get_left_len() < 64) {
|
if (parser.get_left_len() < 64) {
|
||||||
return td::Status::Error("Message is too short");
|
return td::Status::Error("Message is too short");
|
||||||
}
|
}
|
||||||
// TODO: check replay
|
|
||||||
auto result = parser.template fetch_string_raw<std::string>(parser.get_left_len() - 64);
|
auto result = parser.template fetch_string_raw<std::string>(parser.get_left_len() - 64);
|
||||||
parse(signature, parser);
|
parse(signature_to_skip, parser);
|
||||||
parser.fetch_end();
|
parser.fetch_end();
|
||||||
TRY_STATUS(parser.get_status());
|
TRY_STATUS(parser.get_status());
|
||||||
|
|
||||||
TRY_STATUS(check_not_seen(user_id, seqno));
|
if (channel_id != expected_channel_id) {
|
||||||
|
// currently ignore expected_channel_id
|
||||||
// verify signature
|
// return td::Status::Error("Channel id mismatch");
|
||||||
TRY_RESULT(participant, group_state_->get_participant(user_id));
|
}
|
||||||
TRY_STATUS(
|
TRY_STATUS(check_not_seen(participant.public_key, channel_id, seqno));
|
||||||
participant.public_key.verify(td::Slice(payload.data(), payload.size() - 64), Signature::from_u512(signature)));
|
mark_as_seen(participant.public_key, channel_id, seqno);
|
||||||
|
|
||||||
mark_as_seen(user_id, seqno);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
td::Result<std::string> CallEncryption::EpochEncryptor::encrypt(td::Slice decrypted_data) {
|
|
||||||
if (seqno_ == std::numeric_limits<td::uint32>::max()) {
|
|
||||||
return td::Status::Error("Seqno overflow");
|
|
||||||
}
|
|
||||||
seqno_++;
|
|
||||||
|
|
||||||
auto store = [&](auto &storer) {
|
td::Status CallEncryption::check_not_seen(const PublicKey &public_key, td::int32 channel_id, td::uint32 seqno) {
|
||||||
using td::store;
|
auto &s = seen_[std::make_pair(public_key, channel_id)];
|
||||||
store(user_id_, storer);
|
|
||||||
store(seqno_, storer);
|
|
||||||
storer.store_slice(decrypted_data);
|
|
||||||
};
|
|
||||||
td::TlStorerCalcLength calc_length;
|
|
||||||
store(calc_length);
|
|
||||||
auto length = calc_length.get_length();
|
|
||||||
|
|
||||||
std::string payload(length + 64, '\0');
|
|
||||||
td::TlStorerUnsafe storer(td::MutableSlice(payload).ubegin());
|
|
||||||
store(storer);
|
|
||||||
|
|
||||||
TRY_RESULT(signature, private_key_.sign(td::Slice(payload.data(), length)));
|
|
||||||
td::store(signature.to_u512(), storer);
|
|
||||||
|
|
||||||
// TODO: there is too much copies happening here. Almost all of them could be avoided
|
|
||||||
auto encrypted = MessageEncryption::encrypt_data(payload, secret_);
|
|
||||||
std::string res(4 + encrypted.size(), '\0');
|
|
||||||
td::TlStorerUnsafe res_storer(td::MutableSlice(res).ubegin());
|
|
||||||
td::store(epoch_, res_storer);
|
|
||||||
res_storer.store_slice(encrypted);
|
|
||||||
|
|
||||||
// LOG(ERROR) << decrypted_data.size() << " +info-> " << length << " +signature-> " << payload.size()
|
|
||||||
// << " +padding&msg_id-> " << encrypted.size() << " +epoch-> " << res.size();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
td::Status CallEncryption::EpochEncryptor::check_not_seen(td::int64 user_id, td::uint32 seqno) {
|
|
||||||
auto &s = seen_[user_id];
|
|
||||||
if (s.empty()) {
|
if (s.empty()) {
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
@ -330,29 +425,40 @@ td::Status CallEncryption::EpochEncryptor::check_not_seen(td::int64 user_id, td:
|
|||||||
}
|
}
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
void CallEncryption::EpochEncryptor::mark_as_seen(td::int64 user_id, td::uint32 seqno) {
|
|
||||||
|
void CallEncryption::mark_as_seen(const PublicKey &public_key, td::int32 channel_id, td::uint32 seqno) {
|
||||||
auto value = seqno;
|
auto value = seqno;
|
||||||
auto &s = seen_[user_id];
|
auto &s = seen_[std::make_pair(public_key, channel_id)];
|
||||||
CHECK(s.insert(value).second);
|
CHECK(s.insert(value).second);
|
||||||
while (s.size() > 1024) {
|
while (s.size() > 1024 || (!s.empty() && *s.begin() + 1024 < seqno)) {
|
||||||
s.erase(s.begin());
|
s.erase(s.begin());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void CallEncryption::forget_old_epochs() {
|
|
||||||
if (epochs_to_forget_.empty()) {
|
void CallEncryption::sync() {
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto now = td::Timestamp::now();
|
auto now = td::Timestamp::now();
|
||||||
while (!epochs_to_forget_.empty() && epochs_to_forget_.front().first.is_in_past(now)) {
|
while (!epochs_to_forget_.empty() &&
|
||||||
encryptor_by_epoch_.erase(epochs_to_forget_.front().second);
|
(epochs_to_forget_.front().first.is_in_past(now) || epochs_.size() > MAX_ACTIVE_EPOCHS)) {
|
||||||
|
auto epoch = epochs_to_forget_.front().second;
|
||||||
|
LOG(INFO) << "Forget key from epoch: " << epoch;
|
||||||
|
epochs_.erase(epoch);
|
||||||
epochs_to_forget_.pop();
|
epochs_to_forget_.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CallVerification CallVerification::create(PrivateKey private_key, const Blockchain &blockchain) {
|
td::Status CallEncryption::validate_channel_id(td::int32 channel_id) {
|
||||||
|
if (channel_id < 0 || channel_id > 1023) {
|
||||||
|
return Error(E::InvalidCallChannelId);
|
||||||
|
}
|
||||||
|
return td::Status::OK();
|
||||||
|
}
|
||||||
|
|
||||||
|
CallVerification CallVerification::create(td::int64 user_id, PrivateKey private_key, const Blockchain &blockchain) {
|
||||||
CallVerification result;
|
CallVerification result;
|
||||||
|
result.user_id_ = user_id;
|
||||||
result.private_key_ = std::move(private_key);
|
result.private_key_ = std::move(private_key);
|
||||||
result.on_new_main_block(blockchain);
|
result.on_new_main_block(blockchain);
|
||||||
|
result.chain_.allow_delay();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,13 +468,13 @@ void CallVerification::on_new_main_block(const Blockchain &blockchain) {
|
|||||||
td::sha256(nonce.as_mutable_slice(), nonce_hash.as_mutable_slice());
|
td::sha256(nonce.as_mutable_slice(), nonce_hash.as_mutable_slice());
|
||||||
|
|
||||||
auto height = td::narrow_cast<td::int32>(blockchain.get_height());
|
auto height = td::narrow_cast<td::int32>(blockchain.get_height());
|
||||||
auto nonce_commit_tl =
|
auto last_block_hash = blockchain.last_block_hash_;
|
||||||
e2e::e2e_chain_groupBroadcastNonceCommit({}, private_key_.to_public_key().to_u256(), height, nonce_hash);
|
auto nonce_commit_tl = e2e::e2e_chain_groupBroadcastNonceCommit({}, user_id_, height, last_block_hash, nonce_hash);
|
||||||
nonce_commit_tl.signature_ = sign(private_key_, nonce_commit_tl).move_as_ok().to_u512();
|
nonce_commit_tl.signature_ = sign(private_key_, nonce_commit_tl).move_as_ok().to_u512();
|
||||||
auto nonce_commit = serialize_boxed(nonce_commit_tl);
|
auto nonce_commit = serialize_boxed(nonce_commit_tl);
|
||||||
|
|
||||||
height_ = height;
|
height_ = height;
|
||||||
;
|
last_block_hash_ = blockchain.last_block_hash_;
|
||||||
nonce_ = nonce;
|
nonce_ = nonce;
|
||||||
sent_commit_ = true;
|
sent_commit_ = true;
|
||||||
sent_reveal_ = false;
|
sent_reveal_ = false;
|
||||||
@ -394,21 +500,23 @@ td::Status CallVerification::receive_inbound_message(td::Slice message) {
|
|||||||
|
|
||||||
if (chain_.get_state() == CallVerificationChain::Reveal && !sent_reveal_) {
|
if (chain_.get_state() == CallVerificationChain::Reveal && !sent_reveal_) {
|
||||||
sent_reveal_ = true;
|
sent_reveal_ = true;
|
||||||
auto nonce_reveal_tl =
|
auto nonce_reveal_tl = e2e::e2e_chain_groupBroadcastNonceReveal({}, user_id_, height_, last_block_hash_, nonce_);
|
||||||
e2e::e2e_chain_groupBroadcastNonceReveal({}, private_key_.to_public_key().to_u256(), height_, nonce_);
|
|
||||||
nonce_reveal_tl.signature_ = sign(private_key_, nonce_reveal_tl).move_as_ok().to_u512();
|
nonce_reveal_tl.signature_ = sign(private_key_, nonce_reveal_tl).move_as_ok().to_u512();
|
||||||
auto nonce_reveal = serialize_boxed(nonce_reveal_tl);
|
auto nonce_reveal = serialize_boxed(nonce_reveal_tl);
|
||||||
pending_outbound_messages_.clear();
|
CHECK(pending_outbound_messages_.empty());
|
||||||
pending_outbound_messages_.push_back(nonce_reveal);
|
pending_outbound_messages_.push_back(nonce_reveal);
|
||||||
}
|
}
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
Call::Call(PrivateKey pk, ClientBlockchain blockchain)
|
Call::Call(td::int64 user_id, PrivateKey pk, ClientBlockchain blockchain)
|
||||||
: private_key_(std::move(pk)), blockchain_(std::move(blockchain)), call_encryption_(private_key_) {
|
: user_id_(user_id)
|
||||||
|
, private_key_(std::move(pk))
|
||||||
|
, blockchain_(std::move(blockchain))
|
||||||
|
, call_encryption_(user_id, private_key_) {
|
||||||
CHECK(private_key_);
|
CHECK(private_key_);
|
||||||
LOG(INFO) << "Create call \n" << *this;
|
LOG(INFO) << "Create call \n" << *this;
|
||||||
call_verification_ = CallVerification::create(private_key_, blockchain_.get_inner_chain());
|
call_verification_ = CallVerification::create(user_id_, private_key_, blockchain_.get_inner_chain());
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<std::string> Call::create_zero_block(const PrivateKey &private_key, GroupStateRef group_state) {
|
td::Result<std::string> Call::create_zero_block(const PrivateKey &private_key, GroupStateRef group_state) {
|
||||||
@ -416,8 +524,9 @@ td::Result<std::string> Call::create_zero_block(const PrivateKey &private_key, G
|
|||||||
TRY_RESULT(changes, make_changes_for_new_state(std::move(group_state)));
|
TRY_RESULT(changes, make_changes_for_new_state(std::move(group_state)));
|
||||||
return blockchain.build_block(changes, private_key);
|
return blockchain.build_block(changes, private_key);
|
||||||
}
|
}
|
||||||
td::Result<std::string> Call::create_self_add_block(const PrivateKey &private_key, td::Slice previous_block,
|
td::Result<std::string> Call::create_self_add_block(const PrivateKey &private_key, td::Slice previous_block_server,
|
||||||
const GroupParticipant &self) {
|
const GroupParticipant &self) {
|
||||||
|
TRY_RESULT(previous_block, Blockchain::from_server_to_local(previous_block_server.str()));
|
||||||
TRY_RESULT(blockchain, ClientBlockchain::create_from_block(previous_block, private_key.to_public_key()));
|
TRY_RESULT(blockchain, ClientBlockchain::create_from_block(previous_block, private_key.to_public_key()));
|
||||||
auto old_state = *blockchain.get_group_state();
|
auto old_state = *blockchain.get_group_state();
|
||||||
td::remove_if(old_state.participants,
|
td::remove_if(old_state.participants,
|
||||||
@ -428,14 +537,16 @@ td::Result<std::string> Call::create_self_add_block(const PrivateKey &private_ke
|
|||||||
return blockchain.build_block(changes, private_key);
|
return blockchain.build_block(changes, private_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<Call> Call::create(PrivateKey private_key, td::Slice last_block) {
|
td::Result<Call> Call::create(td::int64 user_id, PrivateKey private_key, td::Slice last_block_server) {
|
||||||
|
TRY_RESULT(last_block, Blockchain::from_server_to_local(last_block_server.str()));
|
||||||
TRY_RESULT(blockchain, ClientBlockchain::create_from_block(last_block, private_key.to_public_key()));
|
TRY_RESULT(blockchain, ClientBlockchain::create_from_block(last_block, private_key.to_public_key()));
|
||||||
auto call = Call(std::move(private_key), std::move(blockchain));
|
auto call = Call(user_id, std::move(private_key), std::move(blockchain));
|
||||||
TRY_STATUS(call.update_group_shared_key());
|
TRY_STATUS(call.update_group_shared_key());
|
||||||
return call;
|
return call;
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<std::string> Call::build_change_state(GroupStateRef new_group_state) const {
|
td::Result<std::string> Call::build_change_state(GroupStateRef new_group_state) const {
|
||||||
|
TRY_STATUS(get_status());
|
||||||
TRY_RESULT(changes, make_changes_for_new_state(std::move(new_group_state)));
|
TRY_RESULT(changes, make_changes_for_new_state(std::move(new_group_state)));
|
||||||
return blockchain_.build_block(changes, private_key_);
|
return blockchain_.build_block(changes, private_key_);
|
||||||
}
|
}
|
||||||
@ -456,7 +567,7 @@ td::Result<std::vector<Change>> Call::make_changes_for_new_state(GroupStateRef g
|
|||||||
auto public_key = participant.public_key;
|
auto public_key = participant.public_key;
|
||||||
TRY_RESULT(shared_key, e_private_key.compute_shared_secret(public_key));
|
TRY_RESULT(shared_key, e_private_key.compute_shared_secret(public_key));
|
||||||
dst_user_id.push_back(participant.user_id);
|
dst_user_id.push_back(participant.user_id);
|
||||||
auto header = MessageEncryption::encrypt_header(one_time_secret, encrypted_group_shared_key, shared_key);
|
TRY_RESULT(header, MessageEncryption::encrypt_header(one_time_secret, encrypted_group_shared_key, shared_key));
|
||||||
dst_header.push_back(header.as_slice().str());
|
dst_header.push_back(header.as_slice().str());
|
||||||
}
|
}
|
||||||
auto change_set_shared_key = Change{ChangeSetSharedKey{std::make_shared<GroupSharedKey>(
|
auto change_set_shared_key = Change{ChangeSetSharedKey{std::make_shared<GroupSharedKey>(
|
||||||
@ -467,65 +578,77 @@ td::Result<std::vector<Change>> Call::make_changes_for_new_state(GroupStateRef g
|
|||||||
return std::vector<Change>{std::move(change_set_group_state), std::move(change_set_shared_key)};
|
return std::vector<Change>{std::move(change_set_group_state), std::move(change_set_shared_key)};
|
||||||
}
|
}
|
||||||
|
|
||||||
td::int32 Call::get_height() const {
|
td::Result<td::int32> Call::get_height() const {
|
||||||
|
TRY_STATUS(get_status());
|
||||||
return td::narrow_cast<td::int32>(blockchain_.get_height());
|
return td::narrow_cast<td::int32>(blockchain_.get_height());
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<GroupStateRef> Call::get_group_state() const {
|
td::Result<GroupStateRef> Call::get_group_state() const {
|
||||||
|
TRY_STATUS(get_status());
|
||||||
return blockchain_.get_group_state();
|
return blockchain_.get_group_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Status Call::apply_block(td::Slice block) {
|
td::Status Call::apply_block(td::Slice server_block) {
|
||||||
|
TRY_STATUS(get_status());
|
||||||
|
TRY_RESULT(block, Blockchain::from_server_to_local(server_block.str()));
|
||||||
auto status = do_apply_block(block);
|
auto status = do_apply_block(block);
|
||||||
if (status.is_error()) {
|
if (status.is_error()) {
|
||||||
LOG(ERROR) << "Failed to apply block: " << status << "\n" << Block::from_tl_serialized(block);
|
LOG(ERROR) << "Failed to apply block: " << status << "\n" << Block::from_tl_serialized(block);
|
||||||
|
status_ = std::move(status);
|
||||||
} else {
|
} else {
|
||||||
LOG(INFO) << "Block has been applied\n" << *this;
|
LOG(INFO) << "Block has been applied\n" << *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return get_status();
|
||||||
}
|
}
|
||||||
td::Status Call::do_apply_block(td::Slice block) {
|
td::Status Call::do_apply_block(td::Slice block) {
|
||||||
TRY_RESULT(changes, blockchain_.try_apply_block(block));
|
TRY_RESULT(changes, blockchain_.try_apply_block(block));
|
||||||
bool changed_shared_key = false;
|
|
||||||
for (auto &change : changes) {
|
|
||||||
if (std::holds_alternative<ChangeSetSharedKey>(change.value)) {
|
|
||||||
changed_shared_key = true;
|
|
||||||
}
|
|
||||||
if (std::holds_alternative<ChangeSetGroupState>(change.value)) {
|
|
||||||
changed_shared_key = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
call_verification_.on_new_main_block(blockchain_.get_inner_chain());
|
call_verification_.on_new_main_block(blockchain_.get_inner_chain());
|
||||||
if (changed_shared_key) {
|
|
||||||
TRY_STATUS(update_group_shared_key());
|
TRY_STATUS(update_group_shared_key());
|
||||||
}
|
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Status Call::update_group_shared_key() {
|
td::Result<td::SecureString> Call::decrypt_shared_key() {
|
||||||
auto group_shared_key = blockchain_.get_group_shared_key();
|
auto group_shared_key = blockchain_.get_group_shared_key();
|
||||||
auto group_state = blockchain_.get_group_state();
|
|
||||||
TRY_RESULT(participant, group_state->get_participant(private_key_.to_public_key()));
|
|
||||||
|
|
||||||
for (size_t i = 0; i < group_shared_key->dest_user_id.size(); i++) {
|
for (size_t i = 0; i < group_shared_key->dest_user_id.size(); i++) {
|
||||||
if (group_shared_key->dest_user_id[i] == participant.user_id) {
|
if (group_shared_key->dest_user_id[i] == user_id_) {
|
||||||
TRY_RESULT(shared_key, private_key_.compute_shared_secret(group_shared_key->ek));
|
TRY_RESULT(shared_key, private_key_.compute_shared_secret(group_shared_key->ek));
|
||||||
TRY_RESULT(one_time_secret,
|
TRY_RESULT(one_time_secret,
|
||||||
MessageEncryption::decrypt_header(group_shared_key->dest_header[i],
|
MessageEncryption::decrypt_header(group_shared_key->dest_header[i],
|
||||||
group_shared_key->encrypted_shared_key, shared_key));
|
group_shared_key->encrypted_shared_key, shared_key));
|
||||||
TRY_RESULT(decrypted_group_shared_key,
|
TRY_RESULT(decrypted_shared_key,
|
||||||
MessageEncryption::decrypt_data(group_shared_key->encrypted_shared_key, one_time_secret));
|
MessageEncryption::decrypt_data(group_shared_key->encrypted_shared_key, one_time_secret));
|
||||||
group_shared_key_ = std::move(decrypted_group_shared_key);
|
if (decrypted_shared_key.size() != 32) {
|
||||||
return call_encryption_.add_shared_key(td::narrow_cast<td::int32>(blockchain_.get_height()),
|
return td::Status::Error("Invalid shared key (size != 32)");
|
||||||
group_shared_key_.copy(), group_state);
|
}
|
||||||
|
return decrypted_shared_key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
group_shared_key_ = td::SecureString();
|
|
||||||
|
|
||||||
return td::Status::Error("Could not find user_id in group_shared_key");
|
return td::Status::Error("Could not find user_id in group_shared_key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td::Status Call::update_group_shared_key() {
|
||||||
|
// NB: we drop key immediately, we don't want old key to be active due to some errors later
|
||||||
|
group_shared_key_ = {};
|
||||||
|
call_encryption_.forget_shared_key(td::narrow_cast<td::int32>(blockchain_.get_height() - 1));
|
||||||
|
|
||||||
|
auto group_state = blockchain_.get_group_state();
|
||||||
|
|
||||||
|
auto r_participant = group_state->get_participant(private_key_.to_public_key());
|
||||||
|
if (r_participant.is_error()) {
|
||||||
|
return Error(E::InvalidCallGroupState_NotParticipant);
|
||||||
|
}
|
||||||
|
auto participant = r_participant.move_as_ok();
|
||||||
|
if (participant.user_id != user_id_) {
|
||||||
|
return Error(E::InvalidCallGroupState_WrongUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
TRY_RESULT_ASSIGN(group_shared_key_, decrypt_shared_key());
|
||||||
|
|
||||||
|
return call_encryption_.add_shared_key(td::narrow_cast<td::int32>(blockchain_.get_height()), group_shared_key_.copy(),
|
||||||
|
group_state);
|
||||||
|
}
|
||||||
|
|
||||||
td::StringBuilder &operator<<(td::StringBuilder &sb, const CallVerificationChain &chain) {
|
td::StringBuilder &operator<<(td::StringBuilder &sb, const CallVerificationChain &chain) {
|
||||||
sb << "Verification {height=" << chain.height_ << " state=";
|
sb << "Verification {height=" << chain.height_ << " state=";
|
||||||
switch (chain.state_) {
|
switch (chain.state_) {
|
||||||
@ -559,12 +682,15 @@ td::StringBuilder &operator<<(td::StringBuilder &sb, const CallVerification &ver
|
|||||||
return sb << verification.chain_;
|
return sb << verification.chain_;
|
||||||
}
|
}
|
||||||
td::StringBuilder &operator<<(td::StringBuilder &sb, const Call &call) {
|
td::StringBuilder &operator<<(td::StringBuilder &sb, const Call &call) {
|
||||||
sb << "Call{" << call.get_height() << ":" << call.private_key_.to_public_key() << "}";
|
auto status = call.get_status();
|
||||||
auto group_state = call.get_group_state().move_as_ok();
|
sb << "Call{" << call.blockchain_.get_height() << ":" << call.private_key_.to_public_key() << "}";
|
||||||
|
if (status.is_error()) {
|
||||||
|
sb << "\nCALL_FAILED: " << call.status_;
|
||||||
|
}
|
||||||
|
auto group_state = call.blockchain_.get_group_state();
|
||||||
sb << "\n\tusers=" << td::transform(group_state->participants, [](auto &p) { return p.user_id; });
|
sb << "\n\tusers=" << td::transform(group_state->participants, [](auto &p) { return p.user_id; });
|
||||||
sb << "\n\tpkeys=" << td::transform(group_state->participants, [](auto &p) { return p.public_key; });
|
sb << "\n\tpkeys=" << td::transform(group_state->participants, [](auto &p) { return p.public_key; });
|
||||||
sb << "\n\t" << call.call_verification_;
|
sb << "\n\t" << call.call_verification_;
|
||||||
|
|
||||||
return sb;
|
return sb;
|
||||||
}
|
}
|
||||||
} // namespace tde2e_core
|
} // namespace tde2e_core
|
||||||
|
128
third-party/td/td/tde2e/td/e2e/Call.h
vendored
128
third-party/td/td/tde2e/td/e2e/Call.h
vendored
@ -39,16 +39,21 @@ struct CallVerificationChain {
|
|||||||
void on_new_main_block(const Blockchain &blockhain);
|
void on_new_main_block(const Blockchain &blockhain);
|
||||||
td::Status try_apply_block(td::Slice message);
|
td::Status try_apply_block(td::Slice message);
|
||||||
std::string to_short_string(e2e::object_ptr<e2e::e2e_chain_GroupBroadcast> &broadcast);
|
std::string to_short_string(e2e::object_ptr<e2e::e2e_chain_GroupBroadcast> &broadcast);
|
||||||
td::Status process_broadcast(std::string message, e2e::object_ptr<e2e::e2e_chain_GroupBroadcast> broadcast);
|
|
||||||
|
|
||||||
CallVerificationState get_verification_state() const;
|
CallVerificationState get_verification_state() const;
|
||||||
CallVerificationWords get_verification_words() const;
|
CallVerificationWords get_verification_words() const;
|
||||||
|
|
||||||
td::Span<std::string> received_messages() const;
|
void allow_delay() {
|
||||||
|
delay_allowed_ = true;
|
||||||
|
}
|
||||||
|
void skip_signatures_validation() {
|
||||||
|
may_skip_signatures_validation_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
friend td::StringBuilder &operator<<(td::StringBuilder &sb, const CallVerificationChain &chain);
|
friend td::StringBuilder &operator<<(td::StringBuilder &sb, const CallVerificationChain &chain);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
td::Status process_broadcast(std::string message, e2e::object_ptr<e2e::e2e_chain_GroupBroadcast> broadcast);
|
||||||
td::Status process_broadcast(e2e::e2e_chain_groupBroadcastNonceCommit &nonce_commit);
|
td::Status process_broadcast(e2e::e2e_chain_groupBroadcastNonceCommit &nonce_commit);
|
||||||
td::Status process_broadcast(e2e::e2e_chain_groupBroadcastNonceReveal &nonce_reveal);
|
td::Status process_broadcast(e2e::e2e_chain_groupBroadcastNonceReveal &nonce_reveal);
|
||||||
|
|
||||||
@ -57,60 +62,62 @@ struct CallVerificationChain {
|
|||||||
CallVerificationWords verification_words_;
|
CallVerificationWords verification_words_;
|
||||||
td::int32 height_{-1};
|
td::int32 height_{-1};
|
||||||
td::UInt256 last_block_hash_{};
|
td::UInt256 last_block_hash_{};
|
||||||
std::map<PublicKey, td::int64> participant_keys_;
|
std::map<td::int64, PublicKey> participant_keys_;
|
||||||
std::map<PublicKey, std::string> committed_;
|
std::map<td::int64, std::string> committed_;
|
||||||
std::map<PublicKey, std::string> revealed_;
|
std::map<td::int64, std::string> revealed_;
|
||||||
|
|
||||||
std::vector<std::string> received_messages_;
|
|
||||||
|
|
||||||
|
bool delay_allowed_{false};
|
||||||
|
bool may_skip_signatures_validation_{false};
|
||||||
std::map<td::int32, std::vector<std::pair<std::string, e2e::object_ptr<e2e::e2e_chain_GroupBroadcast>>>>
|
std::map<td::int32, std::vector<std::pair<std::string, e2e::object_ptr<e2e::e2e_chain_GroupBroadcast>>>>
|
||||||
delayed_broadcasts_;
|
delayed_broadcasts_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CallEncryption {
|
class CallEncryption {
|
||||||
public:
|
public:
|
||||||
explicit CallEncryption(PrivateKey private_key);
|
explicit CallEncryption(td::int64 user_id, PrivateKey private_key);
|
||||||
td::Status add_shared_key(td::int32 epoch, td::SecureString key, GroupStateRef group_state);
|
td::Status add_shared_key(td::int32 epoch, td::SecureString key, GroupStateRef group_state);
|
||||||
|
void forget_shared_key(td::int32 epoch);
|
||||||
|
|
||||||
td::Result<std::string> decrypt(td::Slice encrypted_data);
|
td::Result<std::string> decrypt(td::int64 expected_user_id, td::int32 expected_channel_id, td::Slice encrypted_data);
|
||||||
|
td::Result<std::string> encrypt(td::int32 channel_id, td::Slice decrypted_data);
|
||||||
td::Result<std::string> encrypt(td::Slice decrypted_data);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr double FORGET_EPOCH_DELAY = 10;
|
||||||
|
static constexpr int MAX_ACTIVE_EPOCHS = 15;
|
||||||
|
td::int64 user_id_{};
|
||||||
PrivateKey private_key_;
|
PrivateKey private_key_;
|
||||||
|
|
||||||
class EpochEncryptor {
|
struct EpochInfo {
|
||||||
public:
|
EpochInfo(td::int32 epoch, td::int64 user_id, td::SecureString secret, GroupStateRef group_state)
|
||||||
EpochEncryptor(td::int32 epoch, td::int64 user_id, td::SecureString secret, GroupStateRef group_state,
|
: epoch_(epoch), user_id_(user_id), secret_(std::move(secret)), group_state_(std::move(group_state)) {
|
||||||
PrivateKey private_key);
|
}
|
||||||
|
|
||||||
td::Result<std::string> decrypt(td::Slice encrypted_data);
|
td::int32 epoch_{};
|
||||||
td::Result<std::string> encrypt(td::Slice decrypted_data);
|
td::int64 user_id_{};
|
||||||
|
|
||||||
td::Status check_not_seen(td::int64 user_id, td::uint32 seqno);
|
|
||||||
void mark_as_seen(td::int64 user_id, td::uint32 seqno);
|
|
||||||
|
|
||||||
private:
|
|
||||||
td::int32 epoch_;
|
|
||||||
td::int64 user_id_;
|
|
||||||
td::uint32 seqno_{0};
|
|
||||||
td::SecureString secret_;
|
td::SecureString secret_;
|
||||||
GroupStateRef group_state_;
|
GroupStateRef group_state_;
|
||||||
PrivateKey private_key_;
|
|
||||||
|
|
||||||
std::unordered_map<td::int64, std::set<td::uint32>, td::Hash<td::int64>> seen_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
td::optional<td::int32> o_last_epoch_;
|
std::map<td::int32, td::uint32> seqno_;
|
||||||
std::map<td::int32, EpochEncryptor> encryptor_by_epoch_;
|
std::map<td::int32, EpochInfo> epochs_;
|
||||||
td::VectorQueue<std::pair<td::Timestamp, td::int32>> epochs_to_forget_;
|
td::VectorQueue<std::pair<td::Timestamp, td::int32>> epochs_to_forget_;
|
||||||
|
std::map<std::pair<PublicKey, td::int32>, std::set<td::uint32>> seen_;
|
||||||
|
|
||||||
void forget_old_epochs();
|
void sync();
|
||||||
|
|
||||||
|
td::Result<std::string> encrypt_packet_with_secret(td::int32 channel_id, td::Slice header, td::Slice packet,
|
||||||
|
td::Slice one_time_secret);
|
||||||
|
td::Result<std::string> decrypt_packet_with_secret(td::int64 expected_user_id, td::int32 expected_channel_id,
|
||||||
|
td::Slice unencrypted_packet, td::Slice encrypted_packet,
|
||||||
|
td::Slice one_time_secret, const GroupStateRef &group_state);
|
||||||
|
td::Status check_not_seen(const PublicKey &public_key, td::int32 channel_id, td::uint32 seqno);
|
||||||
|
void mark_as_seen(const PublicKey &public_key, td::int32 channel_id, td::uint32 seqno);
|
||||||
|
static td::Status validate_channel_id(td::int32 channel_id);
|
||||||
};
|
};
|
||||||
|
|
||||||
class CallVerification {
|
class CallVerification {
|
||||||
public:
|
public:
|
||||||
static CallVerification create(PrivateKey private_key, const Blockchain &blockchain);
|
static CallVerification create(td::int64 user_id, PrivateKey private_key, const Blockchain &blockchain);
|
||||||
void on_new_main_block(const Blockchain &blockhain);
|
void on_new_main_block(const Blockchain &blockhain);
|
||||||
CallVerificationState get_verification_state() const;
|
CallVerificationState get_verification_state() const;
|
||||||
std::vector<std::string> pull_outbound_messages();
|
std::vector<std::string> pull_outbound_messages();
|
||||||
@ -120,68 +127,89 @@ class CallVerification {
|
|||||||
friend td::StringBuilder &operator<<(td::StringBuilder &sb, const CallVerification &verification);
|
friend td::StringBuilder &operator<<(td::StringBuilder &sb, const CallVerification &verification);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
td::int64 user_id_{};
|
||||||
PrivateKey private_key_;
|
PrivateKey private_key_;
|
||||||
CallVerificationChain chain_;
|
CallVerificationChain chain_;
|
||||||
std::vector<tde2e_api::Bytes> pending_outbound_messages_;
|
std::vector<tde2e_api::Bytes> pending_outbound_messages_;
|
||||||
bool sent_commit_{false};
|
bool sent_commit_{false};
|
||||||
bool sent_reveal_{false};
|
bool sent_reveal_{false};
|
||||||
|
|
||||||
td::int32 height_{};
|
td::int32 height_{-1};
|
||||||
|
td::UInt256 last_block_hash_{};
|
||||||
td::UInt256 nonce_{};
|
td::UInt256 nonce_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Call {
|
struct Call {
|
||||||
Call(PrivateKey pk, ClientBlockchain blockchain);
|
|
||||||
static td::Result<std::string> create_zero_block(const PrivateKey &private_key, GroupStateRef group_state);
|
static td::Result<std::string> create_zero_block(const PrivateKey &private_key, GroupStateRef group_state);
|
||||||
static td::Result<std::string> create_self_add_block(const PrivateKey &private_key, td::Slice previous_block,
|
static td::Result<std::string> create_self_add_block(const PrivateKey &private_key, td::Slice previous_block,
|
||||||
const GroupParticipant &self);
|
const GroupParticipant &self);
|
||||||
|
|
||||||
static td::Result<Call> create(PrivateKey private_key, td::Slice last_block);
|
static td::Result<Call> create(td::int64 user_id, PrivateKey private_key, td::Slice last_block);
|
||||||
td::Result<std::string> build_change_state(GroupStateRef new_group_state) const;
|
|
||||||
static td::Result<std::vector<Change>> make_changes_for_new_state(GroupStateRef group_state);
|
static td::Result<std::vector<Change>> make_changes_for_new_state(GroupStateRef group_state);
|
||||||
td::int32 get_height() const;
|
|
||||||
|
td::Result<std::string> build_change_state(GroupStateRef new_group_state) const;
|
||||||
|
td::Result<td::int32> get_height() const;
|
||||||
td::Result<GroupStateRef> get_group_state() const;
|
td::Result<GroupStateRef> get_group_state() const;
|
||||||
|
|
||||||
// TODO: add self user_id
|
|
||||||
// TODO: verify that call contains us
|
|
||||||
// changes CallVerificationState
|
|
||||||
td::Status apply_block(td::Slice block);
|
td::Status apply_block(td::Slice block);
|
||||||
|
|
||||||
td::Slice shared_key() const {
|
td::Status get_status() const {
|
||||||
return group_shared_key_;
|
if (status_.is_error()) {
|
||||||
|
return Error(E::CallFailed, PSLICE() << status_);
|
||||||
}
|
}
|
||||||
td::Result<std::string> decrypt(td::Slice encrypted_data) {
|
return td::Status::OK();
|
||||||
return call_encryption_.decrypt(encrypted_data);
|
|
||||||
}
|
|
||||||
td::Result<std::string> encrypt(td::Slice decrypted_data) {
|
|
||||||
return call_encryption_.encrypt(decrypted_data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> pull_outbound_messages() {
|
td::Result<td::SecureString> shared_key() const {
|
||||||
|
TRY_STATUS(get_status());
|
||||||
|
return group_shared_key_.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Result<std::string> decrypt(td::int64 user_id, td::int32 channel_id, td::Slice encrypted_data) {
|
||||||
|
TRY_STATUS(get_status());
|
||||||
|
return call_encryption_.decrypt(user_id, channel_id, encrypted_data);
|
||||||
|
}
|
||||||
|
td::Result<std::string> encrypt(td::int32 channel_id, td::Slice decrypted_data) {
|
||||||
|
TRY_STATUS(get_status());
|
||||||
|
return call_encryption_.encrypt(channel_id, decrypted_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Result<std::vector<std::string>> pull_outbound_messages() {
|
||||||
|
TRY_STATUS(get_status());
|
||||||
return call_verification_.pull_outbound_messages();
|
return call_verification_.pull_outbound_messages();
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<CallVerificationState> get_verification_state() const {
|
td::Result<CallVerificationState> get_verification_state() const {
|
||||||
|
TRY_STATUS(get_status());
|
||||||
return call_verification_.get_verification_state();
|
return call_verification_.get_verification_state();
|
||||||
}
|
}
|
||||||
td::Result<CallVerificationWords> get_verification_words() const {
|
td::Result<CallVerificationWords> get_verification_words() const {
|
||||||
|
TRY_STATUS(get_status());
|
||||||
return call_verification_.get_verification_words();
|
return call_verification_.get_verification_words();
|
||||||
}
|
}
|
||||||
td::Result<CallVerificationState> receive_inbound_message(td::Slice verification_message) {
|
td::Result<CallVerificationState> receive_inbound_message(td::Slice verification_message) {
|
||||||
TRY_STATUS(call_verification_.receive_inbound_message(verification_message));
|
TRY_STATUS(get_status());
|
||||||
|
// For now, don't fail the call in case of some errors
|
||||||
|
TRY_RESULT(local_verification_message, Blockchain::from_server_to_local(verification_message.str()));
|
||||||
|
TRY_STATUS(call_verification_.receive_inbound_message(local_verification_message));
|
||||||
return get_verification_state();
|
return get_verification_state();
|
||||||
}
|
}
|
||||||
friend td::StringBuilder &operator<<(td::StringBuilder &builder, const Call &call);
|
friend td::StringBuilder &operator<<(td::StringBuilder &builder, const Call &call);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
td::Status status_{td::Status::OK()};
|
||||||
|
td::int64 user_id_{0};
|
||||||
PrivateKey private_key_;
|
PrivateKey private_key_;
|
||||||
ClientBlockchain blockchain_;
|
ClientBlockchain blockchain_;
|
||||||
td::SecureString group_shared_key_;
|
|
||||||
CallVerification call_verification_;
|
CallVerification call_verification_;
|
||||||
CallEncryption call_encryption_;
|
CallEncryption call_encryption_;
|
||||||
|
td::SecureString group_shared_key_;
|
||||||
|
|
||||||
|
Call(td::int64 user_id, PrivateKey pk, ClientBlockchain blockchain);
|
||||||
|
|
||||||
td::Status update_group_shared_key();
|
td::Status update_group_shared_key();
|
||||||
td::Status do_apply_block(td::Slice block);
|
td::Status do_apply_block(td::Slice block);
|
||||||
|
td::Result<td::SecureString> decrypt_shared_key();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tde2e_core
|
} // namespace tde2e_core
|
||||||
|
@ -24,7 +24,7 @@ DecryptedKey::DecryptedKey(RawDecryptedKey key)
|
|||||||
}
|
}
|
||||||
|
|
||||||
EncryptedKey DecryptedKey::encrypt(td::Slice local_password, td::Slice secret) const {
|
EncryptedKey DecryptedKey::encrypt(td::Slice local_password, td::Slice secret) const {
|
||||||
td::SecureString decrypted_secret = MessageEncryption::combine_secrets(secret, local_password);
|
td::SecureString decrypted_secret = MessageEncryption::hmac_sha512(secret, local_password);
|
||||||
|
|
||||||
td::SecureString encryption_secret =
|
td::SecureString encryption_secret =
|
||||||
MessageEncryption::kdf(as_slice(decrypted_secret), "tde2e local key", EncryptedKey::PBKDF_ITERATIONS);
|
MessageEncryption::kdf(as_slice(decrypted_secret), "tde2e local key", EncryptedKey::PBKDF_ITERATIONS);
|
||||||
|
@ -19,7 +19,7 @@ td::Result<DecryptedKey> EncryptedKey::decrypt(td::Slice local_password, bool ch
|
|||||||
return td::Status::Error("Failed to decrypt key: invalid secret size");
|
return td::Status::Error("Failed to decrypt key: invalid secret size");
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
auto decrypted_secret = MessageEncryption::combine_secrets(secret, local_password);
|
auto decrypted_secret = MessageEncryption::hmac_sha512(secret, local_password);
|
||||||
|
|
||||||
td::SecureString encryption_secret =
|
td::SecureString encryption_secret =
|
||||||
MessageEncryption::kdf(as_slice(decrypted_secret), "tde2e local key", EncryptedKey::PBKDF_ITERATIONS);
|
MessageEncryption::kdf(as_slice(decrypted_secret), "tde2e local key", EncryptedKey::PBKDF_ITERATIONS);
|
||||||
|
@ -18,6 +18,7 @@ namespace tde2e_core {
|
|||||||
struct DecryptedKey;
|
struct DecryptedKey;
|
||||||
struct EncryptedKey {
|
struct EncryptedKey {
|
||||||
static constexpr int PBKDF_ITERATIONS = 100000;
|
static constexpr int PBKDF_ITERATIONS = 100000;
|
||||||
|
static constexpr int PBKDF_FAST_ITERATIONS = 1;
|
||||||
td::SecureString encrypted_data;
|
td::SecureString encrypted_data;
|
||||||
td::optional<PublicKey> o_public_key;
|
td::optional<PublicKey> o_public_key;
|
||||||
td::SecureString secret;
|
td::SecureString secret;
|
||||||
|
@ -323,9 +323,9 @@ struct EncryptedStorage {
|
|||||||
|
|
||||||
static td::Result<EncryptedStorage> create(td::Slice last_block, PrivateKey pk) {
|
static td::Result<EncryptedStorage> create(td::Slice last_block, PrivateKey pk) {
|
||||||
auto public_key = pk.to_public_key();
|
auto public_key = pk.to_public_key();
|
||||||
auto secret_for_key = MessageEncryption::combine_secrets("EncryptedStorage::secret_for_key", pk.to_secure_string());
|
auto secret_for_key = MessageEncryption::hmac_sha512(pk.to_secure_string(), "EncryptedStorage::secret_for_key");
|
||||||
auto secret_for_value =
|
auto secret_for_value =
|
||||||
MessageEncryption::combine_secrets("EncryptedStorage::secret_for_value", pk.to_secure_string());
|
MessageEncryption::hmac_sha512(pk.to_secure_string(), "EncryptedStorage::secret_for_value");
|
||||||
ClientBlockchain blockchain;
|
ClientBlockchain blockchain;
|
||||||
if (last_block.empty()) {
|
if (last_block.empty()) {
|
||||||
TRY_RESULT_ASSIGN(blockchain, ClientBlockchain::create_empty());
|
TRY_RESULT_ASSIGN(blockchain, ClientBlockchain::create_empty());
|
||||||
|
62
third-party/td/td/tde2e/td/e2e/Encryption.md
vendored
62
third-party/td/td/tde2e/td/e2e/Encryption.md
vendored
@ -31,38 +31,54 @@ We use several encryption primitives similar to MTProto 2.0. Here are the key fu
|
|||||||
1) padding_size = ((16 + payload.size + 15) & -16) - payload.size
|
1) padding_size = ((16 + payload.size + 15) & -16) - payload.size
|
||||||
2) padding = random_bytes(padding_size) with padding[0] = padding_size
|
2) padding = random_bytes(padding_size) with padding[0] = padding_size
|
||||||
3) padded_data = padding || payload
|
3) padded_data = padding || payload
|
||||||
4) data_hash = SHA256(padded_data)
|
4) large_secret = KDF(secret, "tde2e_encrypt_data")
|
||||||
5) (aes_key, aes_iv) = SHA512(data_hash || secret)[0:48]
|
5) encrypt_secret = larges_secret[0:32]
|
||||||
6) encrypted = aes_cbc(aes_key, aes_iv, padded_data)
|
6) hmac_secret = large_secret[32:64]
|
||||||
7) result = data_hash || encrypted
|
7) msg_id = HMAC-SHA512(hmac_secret, padded_data)[0:16]
|
||||||
|
8) (aes_key, aes_iv) = HMAC-SHA512(encrypt_secret, msg_id)[0:48]
|
||||||
|
9) encrypted = aes_cbc(aes_key, aes_iv, padded_data)
|
||||||
|
10) result = msg_id || encrypted
|
||||||
|
|
||||||
#### encrypt_header(header, encrypted_msg, secret) - encrypts 32-byte header
|
#### encrypt_header(header, encrypted_msg, secret) - encrypts 32-byte header
|
||||||
|
|
||||||
1) msg_hash = encrypted_msg[0:32] // First 32 bytes
|
1) msg_id = encrypted_msg[0:16] // First 16 bytes
|
||||||
2) (aes_key, aes_iv) = SHA512(msg_hash || secret)[0:48]
|
2) encrypt_secret = KDF(secret, "tde2e_encrypt_header")[0:32]
|
||||||
3) encrypted_header = aes_cbc(aes_key, aes_iv, header)
|
3) (aes_key, aes_iv) = HMAC-SHA512(encrypt_secret, msg_id)[0:48]
|
||||||
|
4) encrypted_header = aes_cbc(aes_key, aes_iv, header)
|
||||||
|
|
||||||
Decryption must verify `data_hash` before any further checks.
|
KDF = HMAC-SHA512, also
|
||||||
|
|
||||||
|
Decryption must verify that the message has a valid `msg_id` by computing HMAC over the decrypted data and comparing with the message's msg_id before accepting the payload.
|
||||||
|
|
||||||
### Packet encryption
|
### Packet encryption
|
||||||
|
|
||||||
This is how we encrypt actual packets:
|
This is how we encrypt actual packets:
|
||||||
|
|
||||||
#### encrypt_packet(payload, epoch_shared_key, user_id, seq_num, private_key) - encrypts a packet
|
#### encrypt_packet(payload, active_epochs, user_id, channel_id, seq_num, private_key) - encrypts a packet
|
||||||
|
|
||||||
1) packet_header = epoch (4 bytes)
|
First, we generate header_a describing epochs (blockchain heights) used
|
||||||
2) packet_payload = user_id (8 bytes) || seq_num (4 bytes) || payload
|
1) epoch_id[i] = active_epochs[i].epoch (4 bytes)
|
||||||
3) signature = sign(packet_payload, private_key) // 64 bytes
|
2) header_a = active_epochs.size (4 bytes) || epoch_id[0] || epoch_id[1] || ...
|
||||||
4) signed_payload = packet_payload || signature
|
|
||||||
5) encrypted_payload = encrypt_data(signed_payload, epoch_shared_key)
|
|
||||||
6) result = packet_header || encrypted_payload
|
|
||||||
|
|
||||||
Key properties:
|
Then, we encrypt payload with one_time_key. Signature in payload includes unencrypted header
|
||||||
- Each packet includes 4-byte epoch to identify encryption key
|
1) one_time_key = random(32)
|
||||||
- Payload contains 8-byte user ID and 4-byte sequence number
|
2) packet_payload = channel_id (4 bytes) || seq_num (4 bytes) || payload
|
||||||
- Sequence numbers track last 1024 per user to prevent replays
|
3) to_sign = HMAC-SHA512(header_a, packet_payload)
|
||||||
- Payload is signed with sender's private key (64-byte signature)
|
4) signature = sign(to_sign, private_key) // 64 bytes
|
||||||
- Full payload is encrypted using encrypt_data() with epoch's shared key
|
5) signed_payload = packet_payload || signature
|
||||||
|
6) encrypted_payload = encrypt_data(signed_payload, one_time_key)
|
||||||
|
|
||||||
|
Finally, encrypt one_time_key with shared secret from each active epoch
|
||||||
|
1) encrypted_key[i] = encrypt_header(one_time_key, encrypted_payload, active_epochs[i].shared_key) (32 bytes)
|
||||||
|
2) header_b = encrypted_key[0] || encrypted_key[1] || ...
|
||||||
|
|
||||||
|
|
||||||
|
result = header_a || header_b || encrypted_payload
|
||||||
|
|
||||||
|
- seqno is unique for each pair (public key; channel_id), so it is used as protection from replay attacks
|
||||||
|
- list of active epochs is also signed
|
||||||
|
|
||||||
|
During decryption, we must take public key from blockchain's state. We also must verify user_id and channel_id. Expected user_id and channel_id are known externally
|
||||||
|
|
||||||
### Encryption of shared key
|
### Encryption of shared key
|
||||||
|
|
||||||
@ -111,8 +127,8 @@ The emoji hash generation uses a two-phase commit-reveal protocol to prevent bru
|
|||||||
|
|
||||||
Tl schema used for such broadcast is the following
|
Tl schema used for such broadcast is the following
|
||||||
```
|
```
|
||||||
e2e.chain.groupBroadcastNonceCommit signature:int512 public_key:int256 chain_height:int32 nonce_hash:int256 = e2e.chain.GroupBroadcast;
|
e2e.chain.groupBroadcastNonceCommit signature:int512 public_key:int256 chain_height:int32 chain_hash:int256 nonce_hash:int256 = e2e.chain.GroupBroadcast;
|
||||||
e2e.chain.groupBroadcastNonceReveal signature:int512 public_key:int256 chain_height:int32 nonce:int256 = e2e.chain.GroupBroadcast;
|
e2e.chain.groupBroadcastNonceReveal signature:int512 public_key:int256 chain_height:int32 chain_hash:int256 nonce:int256 = e2e.chain.GroupBroadcast;
|
||||||
```
|
```
|
||||||
|
|
||||||
The signature is for the TL serialization of the same object with zeroed signature
|
The signature is for the TL serialization of the same object with zeroed signature
|
5
third-party/td/td/tde2e/td/e2e/Keys.cpp
vendored
5
third-party/td/td/tde2e/td/e2e/Keys.cpp
vendored
@ -4,6 +4,7 @@
|
|||||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
//
|
//
|
||||||
|
#include "MessageEncryption.h"
|
||||||
#include "td/e2e/Keys.h"
|
#include "td/e2e/Keys.h"
|
||||||
|
|
||||||
#include "td/utils/Ed25519.h"
|
#include "td/utils/Ed25519.h"
|
||||||
@ -107,7 +108,9 @@ td::Result<PrivateKey> PrivateKey::from_slice(const td::Slice &slice) {
|
|||||||
return std::make_shared<PrivateKeyRaw>(PrivateKeyRaw{{std::move(public_key)}, std::move(private_key)});
|
return std::make_shared<PrivateKeyRaw>(PrivateKeyRaw{{std::move(public_key)}, std::move(private_key)});
|
||||||
}
|
}
|
||||||
td::Result<td::SecureString> PrivateKey::compute_shared_secret(const PublicKey &public_key) const {
|
td::Result<td::SecureString> PrivateKey::compute_shared_secret(const PublicKey &public_key) const {
|
||||||
return td::Ed25519::compute_shared_secret(public_key.raw().public_key, raw_->private_key);
|
TRY_RESULT(x25519_shared_secret, td::Ed25519::compute_shared_secret(public_key.raw().public_key, raw_->private_key));
|
||||||
|
return td::SecureString(
|
||||||
|
MessageEncryption::hmac_sha512("tde2e_shared_secret", x25519_shared_secret).as_slice().substr(0, 32));
|
||||||
}
|
}
|
||||||
td::Result<Signature> PrivateKey::sign(const td::Slice &data) const {
|
td::Result<Signature> PrivateKey::sign(const td::Slice &data) const {
|
||||||
CHECK(raw_);
|
CHECK(raw_);
|
||||||
|
102
third-party/td/td/tde2e/td/e2e/MessageEncryption.cpp
vendored
102
third-party/td/td/tde2e/td/e2e/MessageEncryption.cpp
vendored
@ -28,26 +28,22 @@ td::AesCbcState MessageEncryption::calc_aes_cbc_state_from_hash(td::Slice hash)
|
|||||||
return td::AesCbcState{key, iv};
|
return td::AesCbcState{key, iv};
|
||||||
}
|
}
|
||||||
|
|
||||||
td::AesCbcState MessageEncryption::calc_aes_cbc_state_from_secret(td::Slice seed) {
|
|
||||||
td::SecureString hash(64);
|
|
||||||
sha512(seed, hash.as_mutable_slice());
|
|
||||||
return calc_aes_cbc_state_from_hash(hash.as_slice().substr(0, 48));
|
|
||||||
}
|
|
||||||
|
|
||||||
td::SecureString MessageEncryption::gen_random_prefix(td::int64 data_size, td::int64 min_padding) {
|
td::SecureString MessageEncryption::gen_random_prefix(td::int64 data_size, td::int64 min_padding) {
|
||||||
td::SecureString buff(td::narrow_cast<size_t>(((min_padding + 15 + data_size) & -16) - data_size), 0);
|
td::SecureString buff(td::narrow_cast<size_t>(((min_padding + 15 + data_size) & ~static_cast<td::int64>(15)) - data_size), '\0');
|
||||||
td::Random::secure_bytes(buff.as_mutable_slice());
|
td::Random::secure_bytes(buff.as_mutable_slice());
|
||||||
buff.as_mutable_slice()[0] = td::narrow_cast<td::uint8>(buff.size());
|
buff.as_mutable_slice().ubegin()[0] = td::narrow_cast<td::uint8>(buff.size());
|
||||||
CHECK((buff.size() + data_size) % 16 == 0);
|
CHECK((buff.size() + data_size) % 16 == 0);
|
||||||
return buff;
|
return buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
td::SecureString MessageEncryption::combine_secrets(td::Slice a, td::Slice b) {
|
td::SecureString MessageEncryption::gen_deterministic_prefix(td::int64 data_size, td::int64 min_padding) {
|
||||||
td::SecureString res(64, 0);
|
td::SecureString buff(td::narrow_cast<size_t>(((min_padding + 15 + data_size) & ~static_cast<td::int64>(15)) - data_size), '\0');
|
||||||
hmac_sha512(a, b, res.as_mutable_slice());
|
buff.as_mutable_slice().ubegin()[0] = td::narrow_cast<td::uint8>(buff.size());
|
||||||
return res;
|
CHECK((buff.size() + data_size) % 16 == 0);
|
||||||
|
return buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
td::SecureString MessageEncryption::kdf(td::Slice secret, td::Slice password, int iterations) {
|
td::SecureString MessageEncryption::kdf(td::Slice secret, td::Slice password, int iterations) {
|
||||||
td::SecureString new_secret(64);
|
td::SecureString new_secret(64);
|
||||||
pbkdf2_sha512(secret, password, iterations, new_secret.as_mutable_slice());
|
pbkdf2_sha512(secret, password, iterations, new_secret.as_mutable_slice());
|
||||||
@ -56,17 +52,25 @@ td::SecureString MessageEncryption::kdf(td::Slice secret, td::Slice password, in
|
|||||||
|
|
||||||
td::SecureString MessageEncryption::encrypt_data_with_prefix(td::Slice data, td::Slice secret) {
|
td::SecureString MessageEncryption::encrypt_data_with_prefix(td::Slice data, td::Slice secret) {
|
||||||
CHECK(data.size() % 16 == 0);
|
CHECK(data.size() % 16 == 0);
|
||||||
auto data_hash = sha256(data);
|
auto large_secret = kdf_expand(secret, "tde2e_encrypt_data");
|
||||||
|
auto encrypt_secret = large_secret.as_slice().substr(0, 32);
|
||||||
|
auto hmac_secret = large_secret.as_mutable_slice().substr(32, 32);
|
||||||
|
|
||||||
td::SecureString res_buf(data.size() + 32, 0);
|
auto large_msg_id = hmac_sha512(hmac_secret, data);
|
||||||
|
auto msg_id = large_msg_id.as_slice().substr(0, 16);
|
||||||
|
|
||||||
|
td::SecureString res_buf(data.size() + 16, '\0');
|
||||||
auto res = res_buf.as_mutable_slice();
|
auto res = res_buf.as_mutable_slice();
|
||||||
res.copy_from(data_hash);
|
res.copy_from(msg_id);
|
||||||
|
|
||||||
auto cbc_state = calc_aes_cbc_state_from_hash(combine_secrets(data_hash, secret));
|
auto cbc_state = calc_aes_cbc_state_from_hash(hmac_sha512(encrypt_secret, msg_id));
|
||||||
cbc_state.encrypt(data, res.substr(32));
|
cbc_state.encrypt(data, res.substr(16));
|
||||||
|
|
||||||
return res_buf;
|
return res_buf;
|
||||||
}
|
}
|
||||||
|
td::SecureString MessageEncryption::kdf_expand(td::Slice random_secret, td::Slice info) {
|
||||||
|
return hmac_sha512(random_secret, info);
|
||||||
|
}
|
||||||
|
|
||||||
td::SecureString MessageEncryption::encrypt_data(td::Slice data, td::Slice secret) {
|
td::SecureString MessageEncryption::encrypt_data(td::Slice data, td::Slice secret) {
|
||||||
auto prefix = gen_random_prefix(data.size(), MIN_PADDING);
|
auto prefix = gen_random_prefix(data.size(), MIN_PADDING);
|
||||||
@ -77,22 +81,34 @@ td::SecureString MessageEncryption::encrypt_data(td::Slice data, td::Slice secre
|
|||||||
}
|
}
|
||||||
|
|
||||||
td::Result<td::SecureString> MessageEncryption::decrypt_data(td::Slice encrypted_data, td::Slice secret) {
|
td::Result<td::SecureString> MessageEncryption::decrypt_data(td::Slice encrypted_data, td::Slice secret) {
|
||||||
if (encrypted_data.size() < 33) {
|
if (encrypted_data.size() < 16) {
|
||||||
return td::Status::Error("Failed to decrypt: data is too small");
|
return td::Status::Error("Failed to decrypt: encrypted_data is less than 16 bytes");
|
||||||
}
|
}
|
||||||
if (encrypted_data.size() % 16 != 0) {
|
if (encrypted_data.size() % 16 != 0) {
|
||||||
return td::Status::Error("Failed to decrypt: data size is not divisible by 16");
|
return td::Status::Error("Failed to decrypt: data size is not divisible by 16");
|
||||||
}
|
}
|
||||||
auto data_hash = encrypted_data.substr(0, 32);
|
|
||||||
encrypted_data = encrypted_data.substr(32);
|
|
||||||
|
|
||||||
auto cbc_state = calc_aes_cbc_state_from_hash(combine_secrets(data_hash, secret));
|
auto large_secret = kdf_expand(secret, "tde2e_encrypt_data");
|
||||||
td::SecureString decrypted_data(encrypted_data.size(), 0);
|
auto encrypt_secret = large_secret.as_slice().substr(0, 32);
|
||||||
|
auto hmac_secret = large_secret.as_mutable_slice().substr(32, 32);
|
||||||
|
|
||||||
|
auto msg_id = encrypted_data.substr(0, 16);
|
||||||
|
encrypted_data = encrypted_data.substr(16);
|
||||||
|
|
||||||
|
auto cbc_state = calc_aes_cbc_state_from_hash(hmac_sha512(encrypt_secret, msg_id));
|
||||||
|
td::SecureString decrypted_data(encrypted_data.size(), '\0');
|
||||||
cbc_state.decrypt(encrypted_data, decrypted_data.as_mutable_slice());
|
cbc_state.decrypt(encrypted_data, decrypted_data.as_mutable_slice());
|
||||||
|
|
||||||
|
auto got_large_msg_id = hmac_sha512(hmac_secret, decrypted_data);
|
||||||
|
auto got_msg_id = got_large_msg_id.as_slice().substr(0, 16);
|
||||||
|
|
||||||
// check hash
|
// check hash
|
||||||
if (data_hash != td::sha256(decrypted_data)) {
|
int is_mac_bad = 0;
|
||||||
return td::Status::Error("Failed to decrypt: hash mismatch");
|
for (size_t i = 0; i < 16; i++) {
|
||||||
|
is_mac_bad |= got_msg_id[i] ^ msg_id[i];
|
||||||
|
}
|
||||||
|
if (is_mac_bad != 0) {
|
||||||
|
return td::Status::Error("Failed to decrypt: msg_id mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto prefix_size = static_cast<td::uint8>(decrypted_data[0]);
|
auto prefix_size = static_cast<td::uint8>(decrypted_data[0]);
|
||||||
@ -103,12 +119,25 @@ td::Result<td::SecureString> MessageEncryption::decrypt_data(td::Slice encrypted
|
|||||||
return td::SecureString(decrypted_data.as_slice().substr(prefix_size));
|
return td::SecureString(decrypted_data.as_slice().substr(prefix_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
td::SecureString MessageEncryption::encrypt_header(td::Slice decrypted_header, td::Slice encrypted_message,
|
td::SecureString MessageEncryption::hmac_sha512(td::Slice key, td::Slice message) {
|
||||||
|
td::SecureString res(64, 0);
|
||||||
|
td::hmac_sha512(key, message, res.as_mutable_slice());
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Result<td::SecureString> MessageEncryption::encrypt_header(td::Slice decrypted_header, td::Slice encrypted_message,
|
||||||
td::Slice secret) {
|
td::Slice secret) {
|
||||||
CHECK(encrypted_message.size() >= 32);
|
if (encrypted_message.size() < 16) {
|
||||||
CHECK(decrypted_header.size() == 32);
|
return td::Status::Error("Failed to encrypt header: encrypted_message is too small");
|
||||||
auto data_hash = encrypted_message.substr(0, 32);
|
}
|
||||||
auto cbc_state = calc_aes_cbc_state_from_hash(combine_secrets(data_hash, secret));
|
if (decrypted_header.size() != 32) {
|
||||||
|
return td::Status::Error("Failed to encrypt header: header must be 32 bytes");
|
||||||
|
}
|
||||||
|
auto large_key = kdf_expand(secret, "tde2e_encrypt_header");
|
||||||
|
auto encryption_key = large_key.as_slice().substr(0, 32);
|
||||||
|
|
||||||
|
auto msg_id = encrypted_message.substr(0, 16);
|
||||||
|
auto cbc_state = calc_aes_cbc_state_from_hash(kdf_expand(encryption_key, msg_id));
|
||||||
|
|
||||||
td::SecureString encrypted_header(32, 0);
|
td::SecureString encrypted_header(32, 0);
|
||||||
cbc_state.encrypt(decrypted_header, encrypted_header.as_mutable_slice());
|
cbc_state.encrypt(decrypted_header, encrypted_header.as_mutable_slice());
|
||||||
@ -117,15 +146,18 @@ td::SecureString MessageEncryption::encrypt_header(td::Slice decrypted_header, t
|
|||||||
|
|
||||||
td::Result<td::SecureString> MessageEncryption::decrypt_header(td::Slice encrypted_header, td::Slice encrypted_message,
|
td::Result<td::SecureString> MessageEncryption::decrypt_header(td::Slice encrypted_header, td::Slice encrypted_message,
|
||||||
td::Slice secret) {
|
td::Slice secret) {
|
||||||
|
if (encrypted_message.size() < 16) {
|
||||||
|
return td::Status::Error("Failed to decrypt: invalid message size");
|
||||||
|
}
|
||||||
if (encrypted_header.size() != 32) {
|
if (encrypted_header.size() != 32) {
|
||||||
return td::Status::Error("Failed to decrypt: invalid header size");
|
return td::Status::Error("Failed to decrypt: invalid header size");
|
||||||
}
|
}
|
||||||
if (encrypted_message.size() < 32) {
|
|
||||||
return td::Status::Error("Failed to decrypt: invalid message size");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto data_hash = encrypted_message.substr(0, 32);
|
auto large_key = kdf_expand(secret, "tde2e_encrypt_header");
|
||||||
auto cbc_state = calc_aes_cbc_state_from_hash(combine_secrets(data_hash, secret));
|
auto encryption_key = large_key.as_slice().substr(0, 32);
|
||||||
|
|
||||||
|
auto msg_id = encrypted_message.substr(0, 16);
|
||||||
|
auto cbc_state = calc_aes_cbc_state_from_hash(hmac_sha512(encryption_key, msg_id));
|
||||||
|
|
||||||
td::SecureString decrypted_header(32, 0);
|
td::SecureString decrypted_header(32, 0);
|
||||||
cbc_state.decrypt(encrypted_header, decrypted_header.as_mutable_slice());
|
cbc_state.decrypt(encrypted_header, decrypted_header.as_mutable_slice());
|
||||||
|
@ -17,20 +17,23 @@ class MessageEncryption {
|
|||||||
public:
|
public:
|
||||||
static td::SecureString encrypt_data(td::Slice data, td::Slice secret);
|
static td::SecureString encrypt_data(td::Slice data, td::Slice secret);
|
||||||
static td::Result<td::SecureString> decrypt_data(td::Slice encrypted_data, td::Slice secret);
|
static td::Result<td::SecureString> decrypt_data(td::Slice encrypted_data, td::Slice secret);
|
||||||
static td::SecureString combine_secrets(td::Slice a, td::Slice b);
|
static td::SecureString hmac_sha512(td::Slice key, td::Slice message);
|
||||||
static td::SecureString kdf(td::Slice secret, td::Slice password, int iterations);
|
static td::SecureString kdf(td::Slice secret, td::Slice password, int iterations);
|
||||||
static td::SecureString encrypt_header(td::Slice decrypted_header, td::Slice encrypted_message, td::Slice secret);
|
static td::Result<td::SecureString> encrypt_header(td::Slice decrypted_header, td::Slice encrypted_message, td::Slice secret);
|
||||||
static td::Result<td::SecureString> decrypt_header(td::Slice encrypted_header, td::Slice encrypted_message,
|
static td::Result<td::SecureString> decrypt_header(td::Slice encrypted_header, td::Slice encrypted_message,
|
||||||
td::Slice secret);
|
td::Slice secret);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static td::AesCbcState calc_aes_cbc_state_from_hash(td::Slice hash);
|
static td::AesCbcState calc_aes_cbc_state_from_hash(td::Slice hash);
|
||||||
static td::AesCbcState calc_aes_cbc_state_from_secret(td::Slice seed);
|
|
||||||
static td::SecureString gen_random_prefix(td::int64 data_size, td::int64 min_padding);
|
static td::SecureString gen_random_prefix(td::int64 data_size, td::int64 min_padding);
|
||||||
|
static td::SecureString gen_deterministic_prefix(td::int64 data_size, td::int64 min_padding);
|
||||||
|
|
||||||
static td::SecureString encrypt_data_with_prefix(td::Slice data, td::Slice secret);
|
static td::SecureString encrypt_data_with_prefix(td::Slice data, td::Slice secret);
|
||||||
|
|
||||||
|
static td::SecureString kdf_expand(td::Slice random_secret, td::Slice info);
|
||||||
|
|
||||||
friend class SimpleEncryptionV2;
|
friend class SimpleEncryptionV2;
|
||||||
|
friend class EncryptionTest;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tde2e_core
|
} // namespace tde2e_core
|
||||||
|
2
third-party/td/td/tde2e/td/e2e/Mnemonic.cpp
vendored
2
third-party/td/td/tde2e/td/e2e/Mnemonic.cpp
vendored
@ -173,7 +173,7 @@ std::vector<std::string> Mnemonic::generate_verification_words(td::Slice data) {
|
|||||||
static auto bip_words = Mnemonic::normalize_and_split(td::SecureString(bip39_english()));
|
static auto bip_words = Mnemonic::normalize_and_split(td::SecureString(bip39_english()));
|
||||||
CHECK(bip_words.size() == BIP_WORD_COUNT);
|
CHECK(bip_words.size() == BIP_WORD_COUNT);
|
||||||
|
|
||||||
auto hash = MessageEncryption::combine_secrets("MnemonicVerificationWords", data);
|
auto hash = MessageEncryption::hmac_sha512("MnemonicVerificationWords", data);
|
||||||
CHECK(hash.size() == HASH_SIZE);
|
CHECK(hash.size() == HASH_SIZE);
|
||||||
|
|
||||||
std::vector<std::string> verification_words;
|
std::vector<std::string> verification_words;
|
||||||
|
@ -49,7 +49,7 @@ td::Result<td::SecureString> QRHandshakeBob::receive_accept(td::int64 alice_user
|
|||||||
|
|
||||||
TRY_RESULT_ASSIGN(o_ephemeral_shared_secret_, bob_ephemeral_private_key_.compute_shared_secret(*o_alice_public_key_));
|
TRY_RESULT_ASSIGN(o_ephemeral_shared_secret_, bob_ephemeral_private_key_.compute_shared_secret(*o_alice_public_key_));
|
||||||
TRY_RESULT(shared_secret_tmp, bob_private_key_.compute_shared_secret(*o_alice_public_key_));
|
TRY_RESULT(shared_secret_tmp, bob_private_key_.compute_shared_secret(*o_alice_public_key_));
|
||||||
o_shared_secret_ = MessageEncryption::combine_secrets(o_ephemeral_shared_secret_.value(), shared_secret_tmp);
|
o_shared_secret_ = MessageEncryption::hmac_sha512(o_ephemeral_shared_secret_.value(), shared_secret_tmp);
|
||||||
|
|
||||||
TRY_RESULT(decrypted_accept, decrypt_ephemeral(encrypted_accept));
|
TRY_RESULT(decrypted_accept, decrypt_ephemeral(encrypted_accept));
|
||||||
td::TlParser parser(decrypted_accept);
|
td::TlParser parser(decrypted_accept);
|
||||||
@ -136,7 +136,7 @@ td::Result<QRHandshakeAlice> QRHandshakeAlice::create(td::int64 alice_user_id, P
|
|||||||
auto bob_ephemeral_public_key = PublicKey::from_u256(qr->bob_ephemeral_PK_);
|
auto bob_ephemeral_public_key = PublicKey::from_u256(qr->bob_ephemeral_PK_);
|
||||||
TRY_RESULT(ephemeral_shared_secret, alice_private_key.compute_shared_secret(bob_ephemeral_public_key));
|
TRY_RESULT(ephemeral_shared_secret, alice_private_key.compute_shared_secret(bob_ephemeral_public_key));
|
||||||
TRY_RESULT(shared_secret_tmp, alice_private_key.compute_shared_secret(bob_public_key));
|
TRY_RESULT(shared_secret_tmp, alice_private_key.compute_shared_secret(bob_public_key));
|
||||||
auto shared_secret = MessageEncryption::combine_secrets(ephemeral_shared_secret, shared_secret_tmp);
|
auto shared_secret = MessageEncryption::hmac_sha512(ephemeral_shared_secret, shared_secret_tmp);
|
||||||
return QRHandshakeAlice{alice_user_id,
|
return QRHandshakeAlice{alice_user_id,
|
||||||
std::move(alice_private_key),
|
std::move(alice_private_key),
|
||||||
bob_user_id,
|
bob_user_id,
|
||||||
|
@ -23,6 +23,9 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
int VERBOSITY_NAME(blkch) = VERBOSITY_NAME(INFO);
|
int VERBOSITY_NAME(blkch) = VERBOSITY_NAME(INFO);
|
||||||
|
namespace tde2e_api {
|
||||||
|
Result<SecureBytes> call_export_shared_key(CallId call_id);
|
||||||
|
} // namespace tde2e_api
|
||||||
namespace tde2e_core {
|
namespace tde2e_core {
|
||||||
|
|
||||||
// BlockchainLogger implementation
|
// BlockchainLogger implementation
|
||||||
@ -64,6 +67,7 @@ void BlockchainLogger::log_try_apply_block(td::Slice block_slice, Height height,
|
|||||||
|
|
||||||
log_file_ << "TRY_APPLY_BLOCK\n";
|
log_file_ << "TRY_APPLY_BLOCK\n";
|
||||||
log_file_ << base64_encode(block_slice) << "\n";
|
log_file_ << base64_encode(block_slice) << "\n";
|
||||||
|
log_file_ << base64_encode(Blockchain::from_local_to_server(block_slice.str()).move_as_ok()) << "\n";
|
||||||
log_file_ << height.height << "\n";
|
log_file_ << height.height << "\n";
|
||||||
log_file_ << height.broadcast_height << "\n";
|
log_file_ << height.broadcast_height << "\n";
|
||||||
if (result.is_ok()) {
|
if (result.is_ok()) {
|
||||||
@ -80,6 +84,7 @@ void BlockchainLogger::log_try_apply_broadcast_block(td::Slice block_slice, Heig
|
|||||||
|
|
||||||
log_file_ << "TRY_APPLY_BROADCAST_BLOCK\n";
|
log_file_ << "TRY_APPLY_BROADCAST_BLOCK\n";
|
||||||
log_file_ << base64_encode(block_slice) << "\n";
|
log_file_ << base64_encode(block_slice) << "\n";
|
||||||
|
log_file_ << base64_encode(Blockchain::from_local_to_server(block_slice.str()).move_as_ok()) << "\n";
|
||||||
log_file_ << height.height << "\n";
|
log_file_ << height.height << "\n";
|
||||||
log_file_ << height.broadcast_height << "\n";
|
log_file_ << height.broadcast_height << "\n";
|
||||||
if (result.is_ok()) {
|
if (result.is_ok()) {
|
||||||
@ -155,7 +160,11 @@ void BlockchainLogger::log_get_proof(td::int64 height, const std::vector<std::st
|
|||||||
// ServerBlockchain implementation with logging
|
// ServerBlockchain implementation with logging
|
||||||
td::Status ServerBlockchain::try_apply_block(td::Slice block_slice) {
|
td::Status ServerBlockchain::try_apply_block(td::Slice block_slice) {
|
||||||
TRY_RESULT(block, Block::from_tl_serialized(block_slice));
|
TRY_RESULT(block, Block::from_tl_serialized(block_slice));
|
||||||
auto status = blockchain_.try_apply_block(block);
|
ValidateOptions validate_options;
|
||||||
|
validate_options.permissions = GroupParticipantFlags::AllPermissions;
|
||||||
|
validate_options.validate_signature = true;
|
||||||
|
validate_options.validate_state_hash = true;
|
||||||
|
auto status = blockchain_.try_apply_block(block, validate_options);
|
||||||
if (status.is_ok()) {
|
if (status.is_ok()) {
|
||||||
blocks_.push_back(block);
|
blocks_.push_back(block);
|
||||||
broadcast_chain_.on_new_main_block(blockchain_);
|
broadcast_chain_.on_new_main_block(blockchain_);
|
||||||
@ -192,13 +201,13 @@ td::Result<std::string> ServerBlockchain::get_block(size_t height, int sub_chain
|
|||||||
result = td::Status::Error(PSLICE() << "Invalid height " << height);
|
result = td::Status::Error(PSLICE() << "Invalid height " << height);
|
||||||
} else {
|
} else {
|
||||||
CHECK(blocks_[height].height_ == static_cast<td::int64>(height));
|
CHECK(blocks_[height].height_ == static_cast<td::int64>(height));
|
||||||
result = blocks_[height].to_tl_serialized();
|
result = Blockchain::from_local_to_server(blocks_[height].to_tl_serialized());
|
||||||
}
|
}
|
||||||
} else if (sub_chain == 1) {
|
} else if (sub_chain == 1) {
|
||||||
if (height >= broadcast_blocks_.size()) {
|
if (height >= broadcast_blocks_.size()) {
|
||||||
result = td::Status::Error(PSLICE() << "Invalid height " << height);
|
result = td::Status::Error(PSLICE() << "Invalid height " << height);
|
||||||
} else {
|
} else {
|
||||||
result = broadcast_blocks_[height];
|
result = Blockchain::from_local_to_server(broadcast_blocks_[height]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,7 +371,10 @@ BlockBuilder &BlockBuilder::skip_public_key() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BlockBuilder &BlockBuilder::set_value_raw(td::Slice key, td::Slice value) {
|
BlockBuilder &BlockBuilder::set_value_raw(td::Slice key, td::Slice value) {
|
||||||
block.changes_.push_back(Change{ChangeSetValue{key.str(), key.str()}});
|
kv_state_.set_value(key, value).ensure();
|
||||||
|
block.state_proof_.kv_hash = KeyValueHash{kv_state_.get_hash()};
|
||||||
|
block.changes_.push_back(Change{ChangeSetValue{key.str(), value.str()}});
|
||||||
|
has_hash_proof = true;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,7 +521,7 @@ td::Result<std::vector<std::string>> BlockchainTester::get_values(const std::vec
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<std::string> BlockchainTester::get_block(td::int64 height, int sub_chain) {
|
td::Result<std::string> BlockchainTester::get_block_from_server(td::int64 height, int sub_chain) {
|
||||||
return server_.get_block(static_cast<std::size_t>(height), sub_chain);
|
return server_.get_block(static_cast<std::size_t>(height), sub_chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +560,7 @@ td::Result<ApplyResult> BlockchainTester::apply(const Block &block, td::Slice bl
|
|||||||
auto server_status = server_.try_apply_block(block_str);
|
auto server_status = server_.try_apply_block(block_str);
|
||||||
auto client_status = client_.try_apply_block(block_str);
|
auto client_status = client_.try_apply_block(block_str);
|
||||||
if (server_status.is_error() != client_status.is_error()) {
|
if (server_status.is_error() != client_status.is_error()) {
|
||||||
return td::Status::Error(PSLICE() << "Server and client return different answers:\n\tsever:" << server_status
|
return td::Status::Error(PSLICE() << "Server and client return different answers:\n\tserver:" << server_status
|
||||||
<< "\n\tclient:" << client_status);
|
<< "\n\tclient:" << client_status);
|
||||||
}
|
}
|
||||||
if (server_status.is_error()) {
|
if (server_status.is_error()) {
|
||||||
@ -718,8 +730,8 @@ td::Result<bool> CallTester::user_full_sync(User &user) {
|
|||||||
td::Status CallTester::user_init_call(User &user) {
|
td::Status CallTester::user_init_call(User &user) {
|
||||||
CHECK(user.call_id == 0);
|
CHECK(user.call_id == 0);
|
||||||
CHECK(user.in_call);
|
CHECK(user.in_call);
|
||||||
TEST_TRY_RESULT(block, bt.get_block(++user.height.height));
|
TEST_TRY_RESULT(block, bt.get_block_from_server(++user.height.height));
|
||||||
TRY_RESULT(call_id, to_td(tde2e_api::call_create(user.private_key_id, block)));
|
TRY_RESULT(call_id, to_td(tde2e_api::call_create(user.user_id, user.private_key_id, block)));
|
||||||
user.call_id = call_id;
|
user.call_id = call_id;
|
||||||
return td::Status::OK();
|
return td::Status::OK();
|
||||||
}
|
}
|
||||||
@ -737,7 +749,7 @@ td::Result<bool> CallTester::user_sync_chain_step(User &user) {
|
|||||||
if (user.height.height == height.height) {
|
if (user.height.height == height.height) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TEST_TRY_RESULT(block, bt.get_block(++user.height.height));
|
TEST_TRY_RESULT(block, bt.get_block_from_server(++user.height.height));
|
||||||
TEST_TRY_STATUS(to_td(tde2e_api::call_apply_block(user.call_id, block)));
|
TEST_TRY_STATUS(to_td(tde2e_api::call_apply_block(user.call_id, block)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -748,10 +760,10 @@ td::Result<bool> CallTester::user_sync_broadcast_step(User &user) {
|
|||||||
if (user.height.broadcast_height == height.broadcast_height) {
|
if (user.height.broadcast_height == height.broadcast_height) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TEST_TRY_RESULT(block, bt.get_block(++user.height.broadcast_height, 1));
|
TEST_TRY_RESULT(block, bt.get_block_from_server(++user.height.broadcast_height, 1));
|
||||||
auto r = tde2e_api::call_receive_inbound_message(user.call_id, block);
|
auto r = tde2e_api::call_receive_inbound_message(user.call_id, block);
|
||||||
if (!r.is_ok()) {
|
if (!r.is_ok()) {
|
||||||
return td::Status::Error("Failed to call apply broadcast");
|
return td::Status::Error(PSLICE() << "Failed to call apply broadcast: " << r.error().message);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,8 @@ struct BlockBuilder {
|
|||||||
bool has_signature{false};
|
bool has_signature{false};
|
||||||
tde2e_core::Block block;
|
tde2e_core::Block block;
|
||||||
|
|
||||||
|
KeyValueState kv_state_;
|
||||||
|
|
||||||
void sign(const PrivateKey &private_key);
|
void sign(const PrivateKey &private_key);
|
||||||
void zero_sign();
|
void zero_sign();
|
||||||
std::string hash_key(td::Slice key) const;
|
std::string hash_key(td::Slice key) const;
|
||||||
@ -162,7 +164,7 @@ struct BlockchainTester {
|
|||||||
void reindex();
|
void reindex();
|
||||||
td::Result<std::vector<std::string>> get_values(const std::vector<std::string> &keys);
|
td::Result<std::vector<std::string>> get_values(const std::vector<std::string> &keys);
|
||||||
|
|
||||||
td::Result<std::string> get_block(td::int64 height, int sub_chain = 0);
|
td::Result<std::string> get_block_from_server(td::int64 height, int sub_chain = 0);
|
||||||
|
|
||||||
td::Result<std::string> get_value(td::Slice key);
|
td::Result<std::string> get_value(td::Slice key);
|
||||||
|
|
||||||
|
161
third-party/td/td/tde2e/td/e2e/e2e_api.cpp
vendored
161
third-party/td/td/tde2e/td/e2e/e2e_api.cpp
vendored
@ -31,29 +31,6 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace tde2e_api {
|
namespace tde2e_api {
|
||||||
ErrorCode to_error_code(td::int32) {
|
|
||||||
return ErrorCode::UnknownError;
|
|
||||||
}
|
|
||||||
template <class T>
|
|
||||||
Result<T> to_result(const td::Status &status) {
|
|
||||||
return Result<T>(Error{to_error_code(status.code()), status.message().str()});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
Result<T> to_result(td::Result<T> &value) {
|
|
||||||
if (value.is_ok()) {
|
|
||||||
return Result<T>(value.move_as_ok());
|
|
||||||
}
|
|
||||||
return to_result<T>(value.error());
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
Result<T>::Result(td::Result<T> &&value) : Result(to_result(value)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
Result<T>::Result(td::Status &&status) : Result(to_result<T>(status)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace tde2e_api
|
} // namespace tde2e_api
|
||||||
|
|
||||||
namespace tde2e_core {
|
namespace tde2e_core {
|
||||||
@ -66,7 +43,7 @@ using StorageRef = UniqueRef<EncryptedStorage>;
|
|||||||
using CallRef = UniqueRef<Call>;
|
using CallRef = UniqueRef<Call>;
|
||||||
|
|
||||||
td::UInt256 to_hash(td::Slice tag, td::Slice serialization) {
|
td::UInt256 to_hash(td::Slice tag, td::Slice serialization) {
|
||||||
auto res = MessageEncryption::combine_secrets(tag, serialization);
|
auto res = MessageEncryption::hmac_sha512(tag, serialization);
|
||||||
td::UInt256 hash;
|
td::UInt256 hash;
|
||||||
hash.as_mutable_slice().copy_from(res.as_slice().substr(0, 32));
|
hash.as_mutable_slice().copy_from(res.as_slice().substr(0, 32));
|
||||||
return hash;
|
return hash;
|
||||||
@ -74,6 +51,17 @@ td::UInt256 to_hash(td::Slice tag, td::Slice serialization) {
|
|||||||
|
|
||||||
class KeyChain {
|
class KeyChain {
|
||||||
public:
|
public:
|
||||||
|
KeyChain() {
|
||||||
|
set_log_verbosity_level(1).ignore();
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Result<api::Ok> set_log_verbosity_level(td::int32 new_verbosity_level) {
|
||||||
|
if (0 <= new_verbosity_level && new_verbosity_level <= VERBOSITY_NAME(NEVER)) {
|
||||||
|
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + new_verbosity_level);
|
||||||
|
return api::Ok{};
|
||||||
|
}
|
||||||
|
return td::Status::Error("Wrong new verbosity level specified");
|
||||||
|
}
|
||||||
td::Result<api::PrivateKeyId> generate_private_key() {
|
td::Result<api::PrivateKeyId> generate_private_key() {
|
||||||
TRY_RESULT(mnemonic, Mnemonic::create_new({}));
|
TRY_RESULT(mnemonic, Mnemonic::create_new({}));
|
||||||
return from_words(mnemonic.get_words_string());
|
return from_words(mnemonic.get_words_string());
|
||||||
@ -94,11 +82,12 @@ class KeyChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
td::Result<api::SymmetricKeyId> derive_secret(api::PrivateKeyId key_id, td::Slice tag) {
|
td::Result<api::SymmetricKeyId> derive_secret(api::PrivateKeyId key_id, td::Slice tag) {
|
||||||
TRY_RESULT(pk, to_private_key_with_memonic(key_id));
|
TRY_RESULT(pk, to_private_key_with_mnemonic(key_id));
|
||||||
auto hash = to_hash(PSLICE() << "derive secret with tag: " << td::base64_encode(tag),
|
auto hash = to_hash(PSLICE() << "derive secret with tag: " << td::base64_encode(tag),
|
||||||
pk.to_public_key().to_u256().as_slice());
|
pk.to_public_key().to_u256().as_slice());
|
||||||
return container_.try_build<Key>(hash, [&]() -> td::Result<td::SecureString> {
|
return container_.try_build<Key>(hash, [&]() -> td::Result<td::SecureString> {
|
||||||
return MessageEncryption::combine_secrets(tag, pk.to_private_key().to_secure_string());
|
// TODO: this is probably wrong and should be changed
|
||||||
|
return MessageEncryption::hmac_sha512(pk.to_private_key().to_secure_string(), tag);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +101,7 @@ class KeyChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
td::Result<api::Bytes> to_encrypted_private_key(api::PrivateKeyId key_id, api::SymmetricKeyId secret_id) {
|
td::Result<api::Bytes> to_encrypted_private_key(api::PrivateKeyId key_id, api::SymmetricKeyId secret_id) {
|
||||||
TRY_RESULT(pk, to_private_key_with_memonic(key_id));
|
TRY_RESULT(pk, to_private_key_with_mnemonic(key_id));
|
||||||
TRY_RESULT(secret, to_secret_ref(secret_id));
|
TRY_RESULT(secret, to_secret_ref(secret_id));
|
||||||
auto decrypted_key =
|
auto decrypted_key =
|
||||||
DecryptedKey(td::transform(pk.words(), [](const auto &m) { return m.copy(); }), pk.to_private_key());
|
DecryptedKey(td::transform(pk.words(), [](const auto &m) { return m.copy(); }), pk.to_private_key());
|
||||||
@ -133,6 +122,23 @@ class KeyChain {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td::Result<api::Bytes> to_encrypted_private_key_internal(api::PrivateKeyId key_id, api::SymmetricKeyId secret_id) {
|
||||||
|
TRY_RESULT(pk, to_private_key_with_mnemonic(key_id));
|
||||||
|
TRY_RESULT(secret, to_secret_ref(secret_id));
|
||||||
|
return MessageEncryption::encrypt_data(pk.to_private_key().to_secure_string(), *secret).as_slice().str();
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Result<api::PrivateKeyId> from_encrypted_private_key_internal(td::Slice encrypted_private_key,
|
||||||
|
api::SymmetricKeyId secret_id) {
|
||||||
|
TRY_RESULT(secret, to_secret_ref(secret_id));
|
||||||
|
auto hash = to_hash(PSLICE() << "encrypted private ed25519 key internal " << encrypted_private_key.str(), *secret);
|
||||||
|
return container_.try_build<Key>(hash, [&]() -> td::Result<PrivateKeyWithMnemonic> {
|
||||||
|
TRY_RESULT(raw_pk, MessageEncryption::decrypt_data(encrypted_private_key, *secret));
|
||||||
|
TRY_RESULT(pk, PrivateKey::from_slice(raw_pk));
|
||||||
|
return PrivateKeyWithMnemonic::from_private_key(pk, {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
td::Result<api::PublicKeyId> from_public_key(td::Slice public_key) {
|
td::Result<api::PublicKeyId> from_public_key(td::Slice public_key) {
|
||||||
TRY_RESULT(key, PublicKey::from_slice(public_key));
|
TRY_RESULT(key, PublicKey::from_slice(public_key));
|
||||||
auto hash = to_hash("public ed25519 key", public_key);
|
auto hash = to_hash("public ed25519 key", public_key);
|
||||||
@ -141,7 +147,7 @@ class KeyChain {
|
|||||||
|
|
||||||
td::Result<api::SymmetricKeyId> from_ecdh(api::PrivateKeyId private_key_id, api::PublicKeyId public_key_id) {
|
td::Result<api::SymmetricKeyId> from_ecdh(api::PrivateKeyId private_key_id, api::PublicKeyId public_key_id) {
|
||||||
TRY_RESULT(public_key, to_public_key(public_key_id));
|
TRY_RESULT(public_key, to_public_key(public_key_id));
|
||||||
TRY_RESULT(private_key, to_private_key_with_memonic(private_key_id));
|
TRY_RESULT(private_key, to_private_key_with_mnemonic(private_key_id));
|
||||||
auto hash = to_hash("x25519 shared secret",
|
auto hash = to_hash("x25519 shared secret",
|
||||||
public_key.to_u256().as_slice().str() + private_key.to_public_key().to_u256().as_slice().str());
|
public_key.to_u256().as_slice().str() + private_key.to_public_key().to_u256().as_slice().str());
|
||||||
return container_.try_build<Key>(hash, [&]() -> td::Result<td::SecureString> {
|
return container_.try_build<Key>(hash, [&]() -> td::Result<td::SecureString> {
|
||||||
@ -155,7 +161,7 @@ class KeyChain {
|
|||||||
return container_.try_build<Key>(hash, [&]() -> td::Result<td::SecureString> { return td::SecureString(secret); });
|
return container_.try_build<Key>(hash, [&]() -> td::Result<td::SecureString> { return td::SecureString(secret); });
|
||||||
}
|
}
|
||||||
td::Result<api::SecureBytes> to_words(api::PrivateKeyId private_key_id) {
|
td::Result<api::SecureBytes> to_words(api::PrivateKeyId private_key_id) {
|
||||||
TRY_RESULT(private_key, to_private_key_with_memonic(private_key_id));
|
TRY_RESULT(private_key, to_private_key_with_mnemonic(private_key_id));
|
||||||
api::SecureBytes res;
|
api::SecureBytes res;
|
||||||
auto words = private_key.words();
|
auto words = private_key.words();
|
||||||
for (size_t i = 0; i < words.size(); ++i) {
|
for (size_t i = 0; i < words.size(); ++i) {
|
||||||
@ -168,7 +174,7 @@ class KeyChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
td::Result<api::Int512> sign(api::PrivateKeyId key, td::Slice data) {
|
td::Result<api::Int512> sign(api::PrivateKeyId key, td::Slice data) {
|
||||||
TRY_RESULT(private_key_ref, to_private_key_with_memonic(key));
|
TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(key));
|
||||||
TRY_RESULT(signature, private_key_ref.sign(td::Slice(data.data(), data.size())));
|
TRY_RESULT(signature, private_key_ref.sign(td::Slice(data.data(), data.size())));
|
||||||
CHECK(signature.to_slice().size() == 64);
|
CHECK(signature.to_slice().size() == 64);
|
||||||
api::Int512 result;
|
api::Int512 result;
|
||||||
@ -193,10 +199,9 @@ class KeyChain {
|
|||||||
api::EncryptedMessageForMany res;
|
api::EncryptedMessageForMany res;
|
||||||
res.encrypted_message = MessageEncryption::encrypt_data(message, one_time_secret).as_slice().str();
|
res.encrypted_message = MessageEncryption::encrypt_data(message, one_time_secret).as_slice().str();
|
||||||
for (auto &secret : secrets) {
|
for (auto &secret : secrets) {
|
||||||
res.encrypted_headers.emplace_back(
|
TRY_RESULT(encrypted_header,
|
||||||
MessageEncryption::encrypt_header(one_time_secret, res.encrypted_message, secret->as_slice())
|
MessageEncryption::encrypt_header(one_time_secret, res.encrypted_message, secret->as_slice()));
|
||||||
.as_slice()
|
res.encrypted_headers.emplace_back(encrypted_header.as_slice().str());
|
||||||
.str());
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -214,8 +219,9 @@ class KeyChain {
|
|||||||
|
|
||||||
api::EncryptedMessageForMany res;
|
api::EncryptedMessageForMany res;
|
||||||
for (auto &secret : secrets) {
|
for (auto &secret : secrets) {
|
||||||
res.encrypted_headers.emplace_back(
|
TRY_RESULT(new_encrypted_header,
|
||||||
MessageEncryption::encrypt_header(header, secret->as_slice(), encrypted_message).as_slice().str());
|
MessageEncryption::encrypt_header(header, secret->as_slice(), encrypted_message));
|
||||||
|
res.encrypted_headers.emplace_back(new_encrypted_header.as_slice().str());
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -241,7 +247,7 @@ class KeyChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
td::Result<api::HandshakeId> handshake_create_for_bob(api::UserId bob_user_id, api::PrivateKeyId bob_private_key_id) {
|
td::Result<api::HandshakeId> handshake_create_for_bob(api::UserId bob_user_id, api::PrivateKeyId bob_private_key_id) {
|
||||||
TRY_RESULT(private_key_ref, to_private_key_with_memonic(bob_private_key_id));
|
TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(bob_private_key_id));
|
||||||
return container_.try_build<Handshake>({}, [&]() -> td::Result<QRHandshakeBob> {
|
return container_.try_build<Handshake>({}, [&]() -> td::Result<QRHandshakeBob> {
|
||||||
return QRHandshakeBob::create(bob_user_id, private_key_ref.to_private_key());
|
return QRHandshakeBob::create(bob_user_id, private_key_ref.to_private_key());
|
||||||
});
|
});
|
||||||
@ -254,7 +260,7 @@ class KeyChain {
|
|||||||
api::PrivateKeyId alice_private_key_id,
|
api::PrivateKeyId alice_private_key_id,
|
||||||
api::UserId bob_user_id, td::Slice bob_public_key,
|
api::UserId bob_user_id, td::Slice bob_public_key,
|
||||||
td::Slice start) {
|
td::Slice start) {
|
||||||
TRY_RESULT(private_key_ref, to_private_key_with_memonic(alice_private_key_id));
|
TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(alice_private_key_id));
|
||||||
TRY_RESULT(bob_public_key_internal, PublicKey::from_slice(bob_public_key));
|
TRY_RESULT(bob_public_key_internal, PublicKey::from_slice(bob_public_key));
|
||||||
return container_.try_build<Handshake>({}, [&] {
|
return container_.try_build<Handshake>({}, [&] {
|
||||||
return QRHandshakeAlice::create(alice_user_id, private_key_ref.to_private_key(), bob_user_id,
|
return QRHandshakeAlice::create(alice_user_id, private_key_ref.to_private_key(), bob_user_id,
|
||||||
@ -339,7 +345,7 @@ class KeyChain {
|
|||||||
return handshake_destroy({});
|
return handshake_destroy({});
|
||||||
}
|
}
|
||||||
td::Result<api::StorageId> storage_create(api::PrivateKeyId key_id, td::Slice last_block) {
|
td::Result<api::StorageId> storage_create(api::PrivateKeyId key_id, td::Slice last_block) {
|
||||||
TRY_RESULT(private_key_ref, to_private_key_with_memonic(key_id));
|
TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(key_id));
|
||||||
|
|
||||||
TRY_RESULT(storage, EncryptedStorage::create(last_block, private_key_ref.to_private_key()));
|
TRY_RESULT(storage, EncryptedStorage::create(last_block, private_key_ref.to_private_key()));
|
||||||
return container_.emplace<EncryptedStorage>(std::move(storage));
|
return container_.emplace<EncryptedStorage>(std::move(storage));
|
||||||
@ -364,7 +370,7 @@ class KeyChain {
|
|||||||
}
|
}
|
||||||
template <class T>
|
template <class T>
|
||||||
td::Result<api::SignedEntry<T>> storage_sign_entry(api::PrivateKeyId key, api::Entry<T> entry) {
|
td::Result<api::SignedEntry<T>> storage_sign_entry(api::PrivateKeyId key, api::Entry<T> entry) {
|
||||||
TRY_RESULT(private_key_ref, to_private_key_with_memonic(key));
|
TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(key));
|
||||||
return EncryptedStorage::sign_entry(private_key_ref.to_private_key(), std::move(entry));
|
return EncryptedStorage::sign_entry(private_key_ref.to_private_key(), std::move(entry));
|
||||||
}
|
}
|
||||||
td::Result<std::optional<api::Contact>> storage_get_contact(api::StorageId storage_id, api::PublicKeyId key) {
|
td::Result<std::optional<api::Contact>> storage_get_contact(api::StorageId storage_id, api::PublicKeyId key) {
|
||||||
@ -409,7 +415,7 @@ class KeyChain {
|
|||||||
for (auto &participant : call_state.participants) {
|
for (auto &participant : call_state.participants) {
|
||||||
TRY_RESULT(public_key, to_public_key(participant.public_key_id));
|
TRY_RESULT(public_key, to_public_key(participant.public_key_id));
|
||||||
group_state.participants.push_back(
|
group_state.participants.push_back(
|
||||||
GroupParticipant{participant.user_id, participant.permissions & 3, public_key});
|
GroupParticipant{participant.user_id, participant.permissions & 3, public_key, 0});
|
||||||
}
|
}
|
||||||
return std::make_shared<GroupState>(std::move(group_state));
|
return std::make_shared<GroupState>(std::move(group_state));
|
||||||
}
|
}
|
||||||
@ -424,24 +430,30 @@ class KeyChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
td::Result<api::Bytes> call_create_zero_block(api::PrivateKeyId private_key_id, const api::CallState &initial_state) {
|
td::Result<api::Bytes> call_create_zero_block(api::PrivateKeyId private_key_id, const api::CallState &initial_state) {
|
||||||
TRY_RESULT(private_key_ref, to_private_key_with_memonic(private_key_id));
|
TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(private_key_id));
|
||||||
TRY_RESULT(group_state, to_group_state(initial_state));
|
TRY_RESULT(group_state, to_group_state(initial_state));
|
||||||
return Call::create_zero_block(private_key_ref.to_private_key(), group_state);
|
return Call::create_zero_block(private_key_ref.to_private_key(), group_state);
|
||||||
}
|
}
|
||||||
tde2e_api::Result<std::string> call_create_self_add_block(api::PrivateKeyId private_key_id, td::Slice previous_block,
|
tde2e_api::Result<std::string> call_create_self_add_block(api::PrivateKeyId private_key_id, td::Slice previous_block,
|
||||||
const tde2e_api::CallParticipant &self) {
|
const tde2e_api::CallParticipant &self) {
|
||||||
TRY_RESULT(private_key_ref, to_private_key_with_memonic(private_key_id));
|
TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(private_key_id));
|
||||||
TRY_RESULT(public_key, to_public_key(self.public_key_id));
|
TRY_RESULT(public_key, to_public_key(self.public_key_id));
|
||||||
return Call::create_self_add_block(private_key_ref.to_private_key(), previous_block,
|
return Call::create_self_add_block(private_key_ref.to_private_key(), previous_block,
|
||||||
tde2e_core::GroupParticipant{self.user_id, 3, public_key});
|
tde2e_core::GroupParticipant{self.user_id, 3, public_key, 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<api::CallId> call_create(api::PrivateKeyId private_key_id, td::Slice last_block) {
|
td::Result<api::CallId> call_create(api::UserId user_id, api::PrivateKeyId private_key_id, td::Slice last_block) {
|
||||||
TRY_RESULT(private_key_ref, to_private_key_with_memonic(private_key_id));
|
TRY_RESULT(private_key_ref, to_private_key_with_mnemonic(private_key_id));
|
||||||
|
|
||||||
TRY_RESULT(call, Call::create(private_key_ref.to_private_key(), last_block));
|
TRY_RESULT(call, Call::create(user_id, private_key_ref.to_private_key(), last_block));
|
||||||
return container_.emplace<Call>(std::move(call));
|
return container_.emplace<Call>(std::move(call));
|
||||||
}
|
}
|
||||||
|
td::Result<api::Bytes> call_describe(api::CallId call_id) {
|
||||||
|
TRY_RESULT(call_ref, to_call_ref(call_id));
|
||||||
|
td::StringBuilder sb;
|
||||||
|
sb << *call_ref;
|
||||||
|
return sb.as_cslice().str();
|
||||||
|
}
|
||||||
|
|
||||||
td::Result<api::Bytes> call_create_change_state_block(api::CallId call_id, const api::CallState &new_state) {
|
td::Result<api::Bytes> call_create_change_state_block(api::CallId call_id, const api::CallState &new_state) {
|
||||||
TRY_RESULT(call_ref, to_call_ref(call_id));
|
TRY_RESULT(call_ref, to_call_ref(call_id));
|
||||||
@ -450,15 +462,16 @@ class KeyChain {
|
|||||||
}
|
}
|
||||||
td::Result<api::SecureBytes> call_export_shared_key(api::CallId call_id) {
|
td::Result<api::SecureBytes> call_export_shared_key(api::CallId call_id) {
|
||||||
TRY_RESULT(call_ref, to_call_ref(call_id));
|
TRY_RESULT(call_ref, to_call_ref(call_id));
|
||||||
return call_ref->shared_key().str();
|
TRY_RESULT(shared_key, call_ref->shared_key());
|
||||||
|
return shared_key.as_slice().str();
|
||||||
}
|
}
|
||||||
td::Result<api::Bytes> call_encrypt(api::CallId call_id, td::Slice message) {
|
td::Result<api::Bytes> call_encrypt(api::CallId call_id, api::CallChannelId channel_id, td::Slice message) {
|
||||||
TRY_RESULT(call_ref, to_call_ref(call_id));
|
TRY_RESULT(call_ref, to_call_ref(call_id));
|
||||||
return call_ref->encrypt(message);
|
return call_ref->encrypt(channel_id, message);
|
||||||
}
|
}
|
||||||
td::Result<api::SecureBytes> call_decrypt(api::CallId call_id, td::Slice message) {
|
td::Result<api::SecureBytes> call_decrypt(api::CallId call_id, api::UserId user_id, api::CallChannelId channel_id, td::Slice message) {
|
||||||
TRY_RESULT(call_ref, to_call_ref(call_id));
|
TRY_RESULT(call_ref, to_call_ref(call_id));
|
||||||
return call_ref->decrypt(message);
|
return call_ref->decrypt(user_id, channel_id, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<int> call_get_height(api::CallId call_id) {
|
td::Result<int> call_get_height(api::CallId call_id) {
|
||||||
@ -525,7 +538,7 @@ class KeyChain {
|
|||||||
*key);
|
*key);
|
||||||
}
|
}
|
||||||
|
|
||||||
td::Result<PrivateKeyWithMnemonic> to_private_key_with_memonic(api::AnyKeyId key_id) const {
|
td::Result<PrivateKeyWithMnemonic> to_private_key_with_mnemonic(api::AnyKeyId key_id) const {
|
||||||
TRY_RESULT(key, container_.get_shared<Key>(key_id));
|
TRY_RESULT(key, container_.get_shared<Key>(key_id));
|
||||||
TRY_RESULT(ref, convert<PrivateKeyWithMnemonic>(std::move(key)));
|
TRY_RESULT(ref, convert<PrivateKeyWithMnemonic>(std::move(key)));
|
||||||
return *ref;
|
return *ref;
|
||||||
@ -564,6 +577,9 @@ td::Slice to_slice(std::string_view s) {
|
|||||||
}
|
}
|
||||||
return td::Slice(s.data(), s.size());
|
return td::Slice(s.data(), s.size());
|
||||||
}
|
}
|
||||||
|
Result<Ok> set_log_verbosity_level(int new_verbosity_level) {
|
||||||
|
return get_default_keychain().set_log_verbosity_level(new_verbosity_level);
|
||||||
|
}
|
||||||
Result<PrivateKeyId> key_generate_private_key() {
|
Result<PrivateKeyId> key_generate_private_key() {
|
||||||
return get_default_keychain().generate_private_key();
|
return get_default_keychain().generate_private_key();
|
||||||
}
|
}
|
||||||
@ -576,12 +592,17 @@ Result<PrivateKeyId> key_derive_secret(PrivateKeyId key_id, Slice tag) {
|
|||||||
Result<Bytes> key_to_encrypted_private_key(PrivateKeyId key_id, SymmetricKeyId secret_id) {
|
Result<Bytes> key_to_encrypted_private_key(PrivateKeyId key_id, SymmetricKeyId secret_id) {
|
||||||
return get_default_keychain().to_encrypted_private_key(key_id, secret_id);
|
return get_default_keychain().to_encrypted_private_key(key_id, secret_id);
|
||||||
}
|
}
|
||||||
|
Result<PrivateKeyId> key_from_encrypted_private_key(Slice encrypted_key, SymmetricKeyId secret_id) {
|
||||||
|
return get_default_keychain().from_encrypted_private_key(to_slice(encrypted_key), secret_id);
|
||||||
|
}
|
||||||
Result<SymmetricKeyId> key_from_bytes(SecureSlice secret) {
|
Result<SymmetricKeyId> key_from_bytes(SecureSlice secret) {
|
||||||
return get_default_keychain().from_bytes(to_slice(secret));
|
return get_default_keychain().from_bytes(to_slice(secret));
|
||||||
}
|
}
|
||||||
|
Result<Bytes> key_to_encrypted_private_key_internal(PrivateKeyId key_id, SymmetricKeyId secret_id) {
|
||||||
Result<PrivateKeyId> key_from_encrypted_private_key(Slice encrypted_key, SymmetricKeyId secret_id) {
|
return get_default_keychain().to_encrypted_private_key_internal(key_id, secret_id);
|
||||||
return get_default_keychain().from_encrypted_private_key(to_slice(encrypted_key), secret_id);
|
}
|
||||||
|
Result<PrivateKeyId> key_from_encrypted_private_key_internal(Slice encrypted_key, SymmetricKeyId secret_id) {
|
||||||
|
return get_default_keychain().from_encrypted_private_key_internal(to_slice(encrypted_key), secret_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<PublicKeyId> key_from_public_key(Slice public_key) {
|
Result<PublicKeyId> key_from_public_key(Slice public_key) {
|
||||||
@ -733,11 +754,16 @@ Result<Bytes> call_create_self_add_block(PrivateKeyId private_key_id, Slice prev
|
|||||||
const CallParticipant &self) {
|
const CallParticipant &self) {
|
||||||
return get_default_keychain().call_create_self_add_block(private_key_id, to_slice(previous_block), self);
|
return get_default_keychain().call_create_self_add_block(private_key_id, to_slice(previous_block), self);
|
||||||
}
|
}
|
||||||
Result<CallId> call_create(PrivateKeyId private_key_id, Slice last_block) {
|
Result<CallId> call_create(UserId user_id, PrivateKeyId private_key_id, Slice last_block) {
|
||||||
return get_default_keychain().call_create(private_key_id, to_slice(last_block));
|
return get_default_keychain().call_create(user_id, private_key_id, to_slice(last_block));
|
||||||
|
}
|
||||||
|
Result<std::string> call_describe(CallId call_id) {
|
||||||
|
return get_default_keychain().call_describe(call_id);
|
||||||
}
|
}
|
||||||
Result<std::string> call_describe_block(Slice block_slice) {
|
Result<std::string> call_describe_block(Slice block_slice) {
|
||||||
td::TlParser parser(to_slice(block_slice));
|
bool is_server = tde2e_core::Blockchain::is_from_server(to_slice(block_slice));
|
||||||
|
TRY_RESULT(block_str, tde2e_core::Blockchain::from_any_to_local(std::string(block_slice)));
|
||||||
|
td::TlParser parser(block_str);
|
||||||
auto magic = parser.fetch_int();
|
auto magic = parser.fetch_int();
|
||||||
if (magic != td::e2e_api::e2e_chain_block::ID) {
|
if (magic != td::e2e_api::e2e_chain_block::ID) {
|
||||||
return td::Status::Error("wrong magic");
|
return td::Status::Error("wrong magic");
|
||||||
@ -745,14 +771,17 @@ Result<std::string> call_describe_block(Slice block_slice) {
|
|||||||
auto block = td::e2e_api::e2e_chain_block::fetch(parser);
|
auto block = td::e2e_api::e2e_chain_block::fetch(parser);
|
||||||
parser.fetch_end();
|
parser.fetch_end();
|
||||||
TRY_STATUS(parser.get_status());
|
TRY_STATUS(parser.get_status());
|
||||||
return to_string(block);
|
return PSTRING() << (is_server ? "Server:" : "Local:") << to_string(block);
|
||||||
}
|
}
|
||||||
Result<std::string> call_describe_message(Slice broadcast_slice) {
|
Result<std::string> call_describe_message(Slice broadcast_slice) {
|
||||||
td::TlParser parser(to_slice(broadcast_slice));
|
bool is_server = tde2e_core::Blockchain::is_from_server(to_slice(broadcast_slice));
|
||||||
|
TRY_RESULT(broadcast_str, tde2e_core::Blockchain::from_any_to_local(std::string(broadcast_slice)));
|
||||||
|
|
||||||
|
td::TlParser parser(broadcast_str);
|
||||||
auto broadcast = td::e2e_api::e2e_chain_GroupBroadcast::fetch(parser);
|
auto broadcast = td::e2e_api::e2e_chain_GroupBroadcast::fetch(parser);
|
||||||
parser.fetch_end();
|
parser.fetch_end();
|
||||||
TRY_STATUS(parser.get_status());
|
TRY_STATUS(parser.get_status());
|
||||||
return to_string(broadcast);
|
return PSTRING() << (is_server ? "Server:" : "Local:") << to_string(broadcast);
|
||||||
}
|
}
|
||||||
Result<Bytes> call_create_change_state_block(CallId call_id, const CallState &new_state) {
|
Result<Bytes> call_create_change_state_block(CallId call_id, const CallState &new_state) {
|
||||||
return get_default_keychain().call_create_change_state_block(call_id, new_state);
|
return get_default_keychain().call_create_change_state_block(call_id, new_state);
|
||||||
@ -760,11 +789,11 @@ Result<Bytes> call_create_change_state_block(CallId call_id, const CallState &ne
|
|||||||
Result<SecureBytes> call_export_shared_key(CallId call_id) {
|
Result<SecureBytes> call_export_shared_key(CallId call_id) {
|
||||||
return get_default_keychain().call_export_shared_key(call_id);
|
return get_default_keychain().call_export_shared_key(call_id);
|
||||||
}
|
}
|
||||||
Result<Bytes> call_encrypt(CallId call_id, SecureSlice message) {
|
Result<Bytes> call_encrypt(CallId call_id, CallChannelId channel_id, SecureSlice message) {
|
||||||
return get_default_keychain().call_encrypt(call_id, to_slice(message));
|
return get_default_keychain().call_encrypt(call_id, channel_id, to_slice(message));
|
||||||
}
|
}
|
||||||
Result<SecureBytes> call_decrypt(CallId call_id, Slice message) {
|
Result<SecureBytes> call_decrypt(CallId call_id, UserId user_id, CallChannelId channel_id, Slice message) {
|
||||||
return get_default_keychain().call_decrypt(call_id, to_slice(message));
|
return get_default_keychain().call_decrypt(call_id, user_id, channel_id, to_slice(message));
|
||||||
}
|
}
|
||||||
Result<int> call_get_height(CallId call_id) {
|
Result<int> call_get_height(CallId call_id) {
|
||||||
return get_default_keychain().call_get_height(call_id);
|
return get_default_keychain().call_get_height(call_id);
|
||||||
|
44
third-party/td/td/tde2e/td/e2e/e2e_api.h
vendored
44
third-party/td/td/tde2e/td/e2e/e2e_api.h
vendored
@ -98,6 +98,8 @@ struct EncryptedMessageForMany {
|
|||||||
std::string encrypted_message;
|
std::string encrypted_message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Result<Ok> set_log_verbosity_level(int level);
|
||||||
|
|
||||||
// Keys management
|
// Keys management
|
||||||
// private keys will stay inside the library when it is possible
|
// private keys will stay inside the library when it is possible
|
||||||
// all keys are stored only in memory and should be created or imported before usage
|
// all keys are stored only in memory and should be created or imported before usage
|
||||||
@ -116,6 +118,10 @@ Result<Int512> key_sign(PrivateKeyId key, Slice data);
|
|||||||
Result<Ok> key_destroy(AnyKeyId key_id);
|
Result<Ok> key_destroy(AnyKeyId key_id);
|
||||||
Result<Ok> key_destroy_all();
|
Result<Ok> key_destroy_all();
|
||||||
|
|
||||||
|
// Used to encrypt key between processes, secret_id must be generated with key_from_ecdh
|
||||||
|
Result<Bytes> key_to_encrypted_private_key_internal(PrivateKeyId key_id, SymmetricKeyId secret_id);
|
||||||
|
Result<PrivateKeyId> key_from_encrypted_private_key_internal(Slice encrypted_key, SymmetricKeyId secret_id);
|
||||||
|
|
||||||
Result<EncryptedMessageForMany> encrypt_message_for_many(const std::vector<SymmetricKeyId> &key_ids,
|
Result<EncryptedMessageForMany> encrypt_message_for_many(const std::vector<SymmetricKeyId> &key_ids,
|
||||||
SecureSlice message);
|
SecureSlice message);
|
||||||
// keeps encrypted_message empty in result
|
// keeps encrypted_message empty in result
|
||||||
@ -168,6 +174,7 @@ Result<PrivateKeyId> login_finish_for_bob(LoginId bob_login_id, UserId alice_use
|
|||||||
Result<Ok> login_destroy(LoginId login_id);
|
Result<Ok> login_destroy(LoginId login_id);
|
||||||
Result<Ok> login_destroy_all();
|
Result<Ok> login_destroy_all();
|
||||||
|
|
||||||
|
|
||||||
// Personal info
|
// Personal info
|
||||||
// TODO: UserId
|
// TODO: UserId
|
||||||
|
|
||||||
@ -276,6 +283,7 @@ Result<Ok> storage_blockchain_add_proof(StorageId storage_id, Slice proof, const
|
|||||||
Result<StorageBlockchainState> storage_get_blockchain_state(StorageId);
|
Result<StorageBlockchainState> storage_get_blockchain_state(StorageId);
|
||||||
|
|
||||||
using CallId = std::int64_t;
|
using CallId = std::int64_t;
|
||||||
|
using CallChannelId = std::int32_t;
|
||||||
struct CallParticipant {
|
struct CallParticipant {
|
||||||
UserId user_id;
|
UserId user_id;
|
||||||
PublicKeyId public_key_id;
|
PublicKeyId public_key_id;
|
||||||
@ -289,15 +297,15 @@ struct CallState {
|
|||||||
|
|
||||||
Result<Bytes> call_create_zero_block(PrivateKeyId private_key_id, const CallState &initial_state);
|
Result<Bytes> call_create_zero_block(PrivateKeyId private_key_id, const CallState &initial_state);
|
||||||
Result<Bytes> call_create_self_add_block(PrivateKeyId private_key_id, Slice previous_block, const CallParticipant &self);
|
Result<Bytes> call_create_self_add_block(PrivateKeyId private_key_id, Slice previous_block, const CallParticipant &self);
|
||||||
Result<CallId> call_create(PrivateKeyId private_key_id, Slice last_block);
|
Result<CallId> call_create(UserId user_id, PrivateKeyId private_key_id, Slice last_block);
|
||||||
|
|
||||||
|
Result<std::string> call_describe(CallId call);
|
||||||
Result<std::string> call_describe_block(Slice block);
|
Result<std::string> call_describe_block(Slice block);
|
||||||
Result<std::string> call_describe_message(Slice message);
|
Result<std::string> call_describe_message(Slice message);
|
||||||
|
|
||||||
Result<Bytes> call_create_change_state_block(CallId call_id, const CallState &new_state);
|
Result<Bytes> call_create_change_state_block(CallId call_id, const CallState &new_state);
|
||||||
Result<SecureBytes> call_export_shared_key(CallId call_id);
|
Result<Bytes> call_encrypt(CallId call_id, CallChannelId channel_id, SecureSlice message);
|
||||||
Result<Bytes> call_encrypt(CallId call_id, SecureSlice message);
|
Result<SecureBytes> call_decrypt(CallId call_id, UserId user_id, CallChannelId channel_id, Slice message);
|
||||||
Result<SecureBytes> call_decrypt(CallId call_id, Slice message);
|
|
||||||
|
|
||||||
Result<int> call_get_height(CallId call_id);
|
Result<int> call_get_height(CallId call_id);
|
||||||
Result<CallState> call_apply_block(CallId call_id, Slice block);
|
Result<CallState> call_apply_block(CallId call_id, Slice block);
|
||||||
@ -324,32 +332,4 @@ struct CallVerificationWords {
|
|||||||
Result<CallVerificationWords> call_get_verification_words(CallId call_id);
|
Result<CallVerificationWords> call_get_verification_words(CallId call_id);
|
||||||
Result<Ok> call_destroy(CallId call_id);
|
Result<Ok> call_destroy(CallId call_id);
|
||||||
Result<Ok> call_destroy_all();
|
Result<Ok> call_destroy_all();
|
||||||
|
|
||||||
// TODO: pull values from blockchain state
|
|
||||||
|
|
||||||
// 1. we have a lot of contacts with information about contacts
|
|
||||||
// 2. PublicKey -> info
|
|
||||||
// 3. UserId -> List<PublicKey>
|
|
||||||
// 4. api should check all signatures before saving data
|
|
||||||
// 5. it should also handle timestamp in signature to apply the latest info
|
|
||||||
// 6. Changes will be synchronized with blockchain
|
|
||||||
// 7. blockchain logic will be mostly hidden
|
|
||||||
|
|
||||||
// 1. One must synchronize state of blockchain
|
|
||||||
// 2. One may need to get proofs for keys from the server
|
|
||||||
// 3. One must save changes and handle races
|
|
||||||
|
|
||||||
// interesting moment - we may speculatively return new data if signature is checked
|
|
||||||
// But it becomes complicated, when we are talking about public key itself.
|
|
||||||
// one MUST NOT use public key in any way before it is saved into blockchain
|
|
||||||
// It is more or less OK do decrypt.
|
|
||||||
// It is NOT OK to encrypt.
|
|
||||||
// Case I am worried about - sever may send public keys of user and then it may refuse to save it into blockchain.
|
|
||||||
// What it means - we must persist public key before it is used
|
|
||||||
// TODO: maybe we could forbid usage of unchecked public keys?
|
|
||||||
//
|
|
||||||
// TODO: backup OR recovery key?
|
|
||||||
// Maybe we shouldn't keep the key on device AT ALL?
|
|
||||||
//
|
|
||||||
|
|
||||||
} // namespace tde2e_api
|
} // namespace tde2e_api
|
||||||
|
66
third-party/td/td/tde2e/td/e2e/e2e_errors.h
vendored
66
third-party/td/td/tde2e/td/e2e/e2e_errors.h
vendored
@ -11,21 +11,33 @@
|
|||||||
namespace tde2e_api {
|
namespace tde2e_api {
|
||||||
|
|
||||||
enum class ErrorCode : int {
|
enum class ErrorCode : int {
|
||||||
UnknownError = 1,
|
UnknownError = 100,
|
||||||
Any,
|
Any = 101,
|
||||||
InvalidInput,
|
InvalidInput = 102,
|
||||||
InvalidKeyId,
|
InvalidKeyId = 103,
|
||||||
InvalidId,
|
InvalidId = 104,
|
||||||
InvalidBlock,
|
InvalidBlock = 200,
|
||||||
InvalidBlock_InvalidSignature,
|
InvalidBlock_NoChanges = 201,
|
||||||
InvalidBlock_HashMismatch,
|
InvalidBlock_InvalidSignature = 202,
|
||||||
InvalidBlock_HeightMismatch,
|
InvalidBlock_HashMismatch = 203,
|
||||||
InvalidBlock_InvalidStateProof_Group,
|
InvalidBlock_HeightMismatch = 204,
|
||||||
InvalidBlock_InvalidStateProof_Secret,
|
InvalidBlock_InvalidStateProof_Group = 205,
|
||||||
InvalidBlock_NoPermissions,
|
InvalidBlock_InvalidStateProof_Secret = 206,
|
||||||
InvalidBlock_InvalidGroupState,
|
InvalidBlock_NoPermissions = 207,
|
||||||
Decrypt_UnknownEpoch,
|
InvalidBlock_InvalidGroupState = 208,
|
||||||
Encrypt_UnknownEpoch,
|
InvalidCallGroupState_NotParticipant = 300,
|
||||||
|
InvalidCallGroupState_WrongUserId = 301,
|
||||||
|
Decrypt_UnknownEpoch = 400,
|
||||||
|
Encrypt_UnknownEpoch = 401,
|
||||||
|
InvalidBroadcast_InFuture = 500,
|
||||||
|
InvalidBroadcast_NotInCommit = 501,
|
||||||
|
InvalidBroadcast_NotInReveal = 502,
|
||||||
|
InvalidBroadcast_UnknownUserId = 503,
|
||||||
|
InvalidBroadcast_AlreadyApplied = 504,
|
||||||
|
InvalidBroadcast_InvalidReveal = 505,
|
||||||
|
InvalidBroadcast_InvalidBlockHash = 506,
|
||||||
|
InvalidCallChannelId = 600,
|
||||||
|
CallFailed = 601
|
||||||
};
|
};
|
||||||
inline std::string_view error_string(ErrorCode error_code) {
|
inline std::string_view error_string(ErrorCode error_code) {
|
||||||
switch (error_code) {
|
switch (error_code) {
|
||||||
@ -41,6 +53,8 @@ inline std::string_view error_string(ErrorCode error_code) {
|
|||||||
return "INVALID_ID";
|
return "INVALID_ID";
|
||||||
case ErrorCode::InvalidBlock:
|
case ErrorCode::InvalidBlock:
|
||||||
return "INVALID_BLOCK";
|
return "INVALID_BLOCK";
|
||||||
|
case ErrorCode::InvalidBlock_NoChanges:
|
||||||
|
return "INVALID_BLOCK__NO_CHANGES";
|
||||||
case ErrorCode::InvalidBlock_InvalidSignature:
|
case ErrorCode::InvalidBlock_InvalidSignature:
|
||||||
return "INVALID_BLOCK__INVALID_SIGNATURE";
|
return "INVALID_BLOCK__INVALID_SIGNATURE";
|
||||||
case ErrorCode::InvalidBlock_HashMismatch:
|
case ErrorCode::InvalidBlock_HashMismatch:
|
||||||
@ -55,10 +69,32 @@ inline std::string_view error_string(ErrorCode error_code) {
|
|||||||
return "INVALID_BLOCK__INVALID_GROUP_STATE";
|
return "INVALID_BLOCK__INVALID_GROUP_STATE";
|
||||||
case ErrorCode::InvalidBlock_NoPermissions:
|
case ErrorCode::InvalidBlock_NoPermissions:
|
||||||
return "INVALID_BLOCK__NO_PERMISSIONS";
|
return "INVALID_BLOCK__NO_PERMISSIONS";
|
||||||
|
case ErrorCode::InvalidCallGroupState_NotParticipant:
|
||||||
|
return "INVALID_CALL_GROUP_STATE__NOT_PARTICIPANT";
|
||||||
|
case ErrorCode::InvalidCallGroupState_WrongUserId:
|
||||||
|
return "INVALID_CALL_GROUP_STATE__WRONG_USER_ID";
|
||||||
case ErrorCode::Decrypt_UnknownEpoch:
|
case ErrorCode::Decrypt_UnknownEpoch:
|
||||||
return "DECRYPT__UNKNOWN_EPOCH";
|
return "DECRYPT__UNKNOWN_EPOCH";
|
||||||
case ErrorCode::Encrypt_UnknownEpoch:
|
case ErrorCode::Encrypt_UnknownEpoch:
|
||||||
return "ENCRYPT__UNKNOWN_EPOCH";
|
return "ENCRYPT__UNKNOWN_EPOCH";
|
||||||
|
case ErrorCode::InvalidBroadcast_InFuture:
|
||||||
|
return "INVALID_BROADCAST__IN_FUTURE";
|
||||||
|
case ErrorCode::InvalidBroadcast_NotInCommit:
|
||||||
|
return "INVALID_BROADCAST__NOT_IN_COMMIT";
|
||||||
|
case ErrorCode::InvalidBroadcast_NotInReveal:
|
||||||
|
return "INVALID_BROADCAST__NOT_IN_REVEAL";
|
||||||
|
case ErrorCode::InvalidBroadcast_UnknownUserId:
|
||||||
|
return "INVALID_BROADCAST__UNKNOWN_USER_ID";
|
||||||
|
case ErrorCode::InvalidBroadcast_AlreadyApplied:
|
||||||
|
return "INVALID_BROADCAST__ALREADY_APPLIED";
|
||||||
|
case ErrorCode::InvalidBroadcast_InvalidReveal:
|
||||||
|
return "INVALID_BROADCAST__INVALID_REVEAL";
|
||||||
|
case ErrorCode::InvalidBroadcast_InvalidBlockHash:
|
||||||
|
return "INVALID_BROADCAST__INVALID_BLOCK_HASH";
|
||||||
|
case ErrorCode::CallFailed:
|
||||||
|
return "CALL_FAILED";
|
||||||
|
case ErrorCode::InvalidCallChannelId:
|
||||||
|
return "INVALID_CALL_CHANNEL_ID";
|
||||||
}
|
}
|
||||||
return "UNKNOWN_ERROR";
|
return "UNKNOWN_ERROR";
|
||||||
}
|
}
|
||||||
|
279
third-party/td/td/tde2e/td/e2e/encryption_test.py
vendored
Normal file
279
third-party/td/td/tde2e/td/e2e/encryption_test.py
vendored
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
import os
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Hash import SHA256, HMAC, SHA512
|
||||||
|
import binascii
|
||||||
|
import random
|
||||||
|
|
||||||
|
def generate_deterministic_padding(data_size, min_padding):
|
||||||
|
# Calculate padding size to make total size multiple of 16
|
||||||
|
padding_size = ((min_padding + 15 + data_size) & -16) - data_size
|
||||||
|
padding = bytearray(padding_size)
|
||||||
|
|
||||||
|
# Only set the first byte to padding size, leave rest as zeros
|
||||||
|
padding[0] = padding_size
|
||||||
|
|
||||||
|
return padding
|
||||||
|
|
||||||
|
|
||||||
|
def hmac_sha512(a, b):
|
||||||
|
# Combine two secrets using HMAC-SHA512
|
||||||
|
if isinstance(a, str):
|
||||||
|
a = a.encode('utf-8')
|
||||||
|
if isinstance(b, str):
|
||||||
|
b = b.encode('utf-8')
|
||||||
|
hmac = HMAC.new(a, b, SHA512)
|
||||||
|
return hmac.digest()
|
||||||
|
|
||||||
|
def kdf(key, info):
|
||||||
|
return hmac_sha512(key, info)
|
||||||
|
|
||||||
|
def encrypt_data_with_prefix(data, secret):
|
||||||
|
# Ensure data is multiple of 16 bytes
|
||||||
|
assert len(data) % 16 == 0
|
||||||
|
|
||||||
|
# Generate encryption and HMAC secrets
|
||||||
|
large_secret = kdf(secret, "tde2e_encrypt_data")
|
||||||
|
encrypt_secret = large_secret[:32]
|
||||||
|
hmac_secret = large_secret[32:64]
|
||||||
|
|
||||||
|
# Generate message ID using HMAC
|
||||||
|
large_msg_id = hmac_sha512(hmac_secret, data)
|
||||||
|
msg_id = large_msg_id[:16] # Use first 16 bytes as message ID
|
||||||
|
|
||||||
|
# Create result buffer
|
||||||
|
result = bytearray(len(data) + 16)
|
||||||
|
result[0:16] = msg_id
|
||||||
|
|
||||||
|
# Generate key and IV for encryption
|
||||||
|
encryption_secret = hmac_sha512(encrypt_secret, msg_id)
|
||||||
|
key = encryption_secret[:32]
|
||||||
|
iv = encryption_secret[32:48]
|
||||||
|
|
||||||
|
# Encrypt data
|
||||||
|
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||||
|
encrypted = cipher.encrypt(data)
|
||||||
|
result[16:] = encrypted
|
||||||
|
|
||||||
|
return bytes(result)
|
||||||
|
|
||||||
|
def encrypt_data_with_deterministic_padding(data, secret):
|
||||||
|
# Generate deterministic padding
|
||||||
|
padding = generate_deterministic_padding(len(data), 16)
|
||||||
|
|
||||||
|
# Combine padding and data
|
||||||
|
combined = bytearray(len(padding) + len(data))
|
||||||
|
combined[0:len(padding)] = padding
|
||||||
|
combined[len(padding):] = data
|
||||||
|
|
||||||
|
# Encrypt the combined data
|
||||||
|
return encrypt_data_with_prefix(combined, secret)
|
||||||
|
|
||||||
|
def encrypt_header(header, encrypted_message, secret):
|
||||||
|
# Verify inputs
|
||||||
|
assert len(header) == 32
|
||||||
|
assert len(encrypted_message) >= 16
|
||||||
|
|
||||||
|
# Get msg_id from the beginning of encrypted message
|
||||||
|
msg_id = encrypted_message[0:16]
|
||||||
|
|
||||||
|
encryption_key = kdf(secret, "tde2e_encrypt_header")[:32]
|
||||||
|
|
||||||
|
# Generate encryption key and IV from secret and message ID
|
||||||
|
encryption_secret = kdf(encryption_key, msg_id)
|
||||||
|
key = encryption_secret[:32]
|
||||||
|
iv = encryption_secret[32:48]
|
||||||
|
|
||||||
|
# Encrypt header with AES-CBC
|
||||||
|
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||||
|
encrypted_header = cipher.encrypt(header)
|
||||||
|
|
||||||
|
return encrypted_header
|
||||||
|
|
||||||
|
def decrypt_data(encrypted_data, secret):
|
||||||
|
# Verify input size
|
||||||
|
if len(encrypted_data) < 17:
|
||||||
|
raise ValueError("Failed to decrypt: data is too small")
|
||||||
|
if len(encrypted_data) % 16 != 0:
|
||||||
|
raise ValueError("Failed to decrypt: data size is not divisible by 16")
|
||||||
|
|
||||||
|
# Extract msg_id and encrypted part
|
||||||
|
msg_id = encrypted_data[0:16]
|
||||||
|
encrypted_part = encrypted_data[16:]
|
||||||
|
|
||||||
|
# Generate encryption and HMAC secrets
|
||||||
|
large_secret = kdf(secret, "tde2e_encrypt_data")
|
||||||
|
hmac_secret = large_secret[32:64]
|
||||||
|
encrypt_secret = large_secret[:32]
|
||||||
|
|
||||||
|
# Generate key and IV for decryption
|
||||||
|
encryption_secret = hmac_sha512(encrypt_secret, msg_id)
|
||||||
|
key = encryption_secret[:32]
|
||||||
|
iv = encryption_secret[32:48]
|
||||||
|
|
||||||
|
# Decrypt with AES-CBC
|
||||||
|
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||||
|
decrypted_data = cipher.decrypt(encrypted_part)
|
||||||
|
|
||||||
|
# Verify msg_id
|
||||||
|
large_msg_id = hmac_sha512(hmac_secret, decrypted_data)
|
||||||
|
expected_msg_id = large_msg_id[:16]
|
||||||
|
if msg_id != expected_msg_id:
|
||||||
|
raise ValueError("Failed to decrypt: msg_id mismatch")
|
||||||
|
|
||||||
|
# Extract actual data by removing padding
|
||||||
|
prefix_size = decrypted_data[0]
|
||||||
|
if prefix_size > len(decrypted_data) or prefix_size < 16:
|
||||||
|
raise ValueError("Failed to decrypt: invalid prefix size")
|
||||||
|
|
||||||
|
return decrypted_data[prefix_size:]
|
||||||
|
|
||||||
|
def decrypt_header(encrypted_header, encrypted_message, secret):
|
||||||
|
# Verify inputs
|
||||||
|
if len(encrypted_header) != 32:
|
||||||
|
raise ValueError("Failed to decrypt: invalid header size")
|
||||||
|
if len(encrypted_message) < 16:
|
||||||
|
raise ValueError("Failed to decrypt: invalid message size")
|
||||||
|
|
||||||
|
# Get msg_id from the beginning of encrypted message
|
||||||
|
msg_id = encrypted_message[0:16]
|
||||||
|
encryption_key = kdf(secret, "tde2e_encrypt_header")[:32]
|
||||||
|
|
||||||
|
# Generate encryption key and IV from secret and msg_id
|
||||||
|
encryption_secret = kdf(encryption_key, msg_id)
|
||||||
|
key = encryption_secret[:32]
|
||||||
|
iv = encryption_secret[32:48]
|
||||||
|
|
||||||
|
# Decrypt header with AES-CBC
|
||||||
|
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||||
|
decrypted_header = cipher.decrypt(encrypted_header)
|
||||||
|
|
||||||
|
return decrypted_header
|
||||||
|
|
||||||
|
def generate_random_bytes(length):
|
||||||
|
return bytes(random.getrandbits(8) for _ in range(length))
|
||||||
|
|
||||||
|
def generate_test_vectors():
|
||||||
|
# Generate random secrets and headers for each test
|
||||||
|
secret = generate_random_bytes(32)
|
||||||
|
header = generate_random_bytes(32)
|
||||||
|
|
||||||
|
# Test vectors with different data patterns
|
||||||
|
test_vectors = [
|
||||||
|
{
|
||||||
|
"name": "empty_message",
|
||||||
|
"secret": binascii.hexlify(secret).decode('ascii'),
|
||||||
|
"data": "",
|
||||||
|
"header": binascii.hexlify(header).decode('ascii')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "simple_message",
|
||||||
|
"secret": binascii.hexlify(secret).decode('ascii'),
|
||||||
|
"data": binascii.hexlify(b"Hello, World!").decode('ascii'),
|
||||||
|
"header": binascii.hexlify(header).decode('ascii')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "long_message",
|
||||||
|
"secret": binascii.hexlify(secret).decode('ascii'),
|
||||||
|
"data": binascii.hexlify(b"x" * 200).decode('ascii'),
|
||||||
|
"header": binascii.hexlify(header).decode('ascii')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "random_message",
|
||||||
|
"secret": binascii.hexlify(secret).decode('ascii'),
|
||||||
|
"data": binascii.hexlify(generate_random_bytes(64)).decode('ascii'),
|
||||||
|
"header": binascii.hexlify(header).decode('ascii')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "very_long_message",
|
||||||
|
"secret": binascii.hexlify(secret).decode('ascii'),
|
||||||
|
"data": binascii.hexlify(generate_random_bytes(300)).decode('ascii'),
|
||||||
|
"header": binascii.hexlify(header).decode('ascii')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "message_with_special_chars",
|
||||||
|
"secret": binascii.hexlify(secret).decode('ascii'),
|
||||||
|
"data": binascii.hexlify(bytes([i for i in range(33, 64)])).decode('ascii'),
|
||||||
|
"header": binascii.hexlify(header).decode('ascii')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "message_with_unicode",
|
||||||
|
"secret": binascii.hexlify(secret).decode('ascii'),
|
||||||
|
"data": binascii.hexlify("Hello, 世界!".encode('utf-8')).decode('ascii'),
|
||||||
|
"header": binascii.hexlify(header).decode('ascii')
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Generate encrypted data and headers for each test vector
|
||||||
|
for vec in test_vectors:
|
||||||
|
secret = binascii.unhexlify(vec["secret"])
|
||||||
|
data = binascii.unhexlify(vec["data"])
|
||||||
|
header = binascii.unhexlify(vec["header"])
|
||||||
|
|
||||||
|
encrypted = encrypt_data_with_deterministic_padding(data, secret)
|
||||||
|
encrypted_header = encrypt_header(header, encrypted, secret)
|
||||||
|
|
||||||
|
# Test decryption
|
||||||
|
decrypted = decrypt_data(encrypted, secret)
|
||||||
|
if decrypted != data:
|
||||||
|
raise ValueError(f"Decryption failed for test vector: {vec['name']}")
|
||||||
|
|
||||||
|
# Test header decryption
|
||||||
|
decrypted_header = decrypt_header(encrypted_header, encrypted, secret)
|
||||||
|
if decrypted_header != header:
|
||||||
|
raise ValueError(f"Header decryption failed for test vector: {vec['name']}")
|
||||||
|
|
||||||
|
vec["encrypted"] = binascii.hexlify(encrypted).decode('ascii')
|
||||||
|
vec["encrypted_header"] = binascii.hexlify(encrypted_header).decode('ascii')
|
||||||
|
|
||||||
|
return test_vectors
|
||||||
|
|
||||||
|
def print_cpp_header(test_vectors):
|
||||||
|
print("#pragma once")
|
||||||
|
print("\n#include <string>")
|
||||||
|
print("#include <vector>")
|
||||||
|
print("\nnamespace tde2e_core {")
|
||||||
|
print("\nstruct TestVector {")
|
||||||
|
print(" std::string name;")
|
||||||
|
print(" std::string secret;")
|
||||||
|
print(" std::string data;")
|
||||||
|
print(" std::string header;")
|
||||||
|
print(" std::string encrypted;")
|
||||||
|
print(" std::string encrypted_header;")
|
||||||
|
print("};")
|
||||||
|
print("\ninline std::vector<TestVector> get_test_vectors() {")
|
||||||
|
print(" return {")
|
||||||
|
|
||||||
|
for vec in test_vectors:
|
||||||
|
print(" {")
|
||||||
|
print(f' "{vec["name"]}",')
|
||||||
|
print(f' "{vec["secret"]}",')
|
||||||
|
print(f' "{vec["data"]}",')
|
||||||
|
print(f' "{vec["header"]}",')
|
||||||
|
print(f' "{vec["encrypted"]}",')
|
||||||
|
print(f' "{vec["encrypted_header"]}"')
|
||||||
|
print(" },")
|
||||||
|
|
||||||
|
print(" };")
|
||||||
|
print("}")
|
||||||
|
print("\n} // namespace tde2e_core")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_vectors = generate_test_vectors()
|
||||||
|
|
||||||
|
# Get the directory of the current script
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# Go up two levels to reach the tde2e directory
|
||||||
|
tde2e_dir = os.path.dirname(os.path.dirname(script_dir))
|
||||||
|
# Create the test directory if it doesn't exist
|
||||||
|
test_dir = os.path.join(tde2e_dir, "test")
|
||||||
|
os.makedirs(test_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Write the header file
|
||||||
|
header_path = os.path.join(test_dir, "EncryptionTestVectors.h")
|
||||||
|
with open(header_path, "w") as f:
|
||||||
|
# Redirect print output to the file
|
||||||
|
import sys
|
||||||
|
old_stdout = sys.stdout
|
||||||
|
sys.stdout = f
|
||||||
|
print_cpp_header(test_vectors)
|
||||||
|
sys.stdout = old_stdout
|
25
third-party/td/td/tde2e/td/e2e/utils.h
vendored
25
third-party/td/td/tde2e/td/e2e/utils.h
vendored
@ -23,6 +23,31 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
namespace tde2e_api {
|
||||||
|
inline Error to_error(const td::Status &status) {
|
||||||
|
auto error_code = ErrorCode(status.code());
|
||||||
|
if (error_string(error_code) == "UNKNOWN_ERROR") {
|
||||||
|
return Error{ErrorCode::UnknownError, status.message().str()};
|
||||||
|
}
|
||||||
|
return Error{error_code, status.message().str()};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Result<T> to_result(td::Result<T> &value) {
|
||||||
|
if (value.is_ok()) {
|
||||||
|
return Result<T>(value.move_as_ok());
|
||||||
|
}
|
||||||
|
return Result<T>(to_error(value.error()));
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
Result<T>::Result(td::Result<T> &&value) : Result(to_result(value)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Result<T>::Result(td::Status &&status) : Result(to_error(status)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tde2e_api
|
||||||
|
|
||||||
namespace tde2e_core {
|
namespace tde2e_core {
|
||||||
using E = tde2e_api::ErrorCode;
|
using E = tde2e_api::ErrorCode;
|
||||||
|
78
third-party/td/td/tde2e/test/EncryptionTestVectors.h
vendored
Normal file
78
third-party/td/td/tde2e/test/EncryptionTestVectors.h
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace tde2e_core {
|
||||||
|
|
||||||
|
struct TestVector {
|
||||||
|
std::string name;
|
||||||
|
std::string secret;
|
||||||
|
std::string data;
|
||||||
|
std::string header;
|
||||||
|
std::string encrypted;
|
||||||
|
std::string encrypted_header;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::vector<TestVector> get_test_vectors() {
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
"empty_message",
|
||||||
|
"5a08a19b447df98136a4502e01b286011b2d148084a7ca17e3a93616d279eb2a",
|
||||||
|
"",
|
||||||
|
"a36c57ad5e8d6a30e80e010ab903b60da0206db1b4fd981cd61e059bbd8c0d4f",
|
||||||
|
"9e2476ad849d22a44d9135c5c3c5e8b52d4f88473ae8745f3a9cec4d54780caf",
|
||||||
|
"de3539a5e10b20a3a0cffc24dbbd76b3a7e0eeab402cb38396d64785a3ab7c25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"simple_message",
|
||||||
|
"5a08a19b447df98136a4502e01b286011b2d148084a7ca17e3a93616d279eb2a",
|
||||||
|
"48656c6c6f2c20576f726c6421",
|
||||||
|
"a36c57ad5e8d6a30e80e010ab903b60da0206db1b4fd981cd61e059bbd8c0d4f",
|
||||||
|
"872b141f6d1e3554ead471dffd0fee5c04a0b04260eafcca9187158ce84c4487e9429df876706753913de61029402478",
|
||||||
|
"013037e02dc8dbf13598d96eb333a69a930efe043bac7dce0d6edfd1abc6bd2f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long_message",
|
||||||
|
"5a08a19b447df98136a4502e01b286011b2d148084a7ca17e3a93616d279eb2a",
|
||||||
|
"7878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878",
|
||||||
|
"a36c57ad5e8d6a30e80e010ab903b60da0206db1b4fd981cd61e059bbd8c0d4f",
|
||||||
|
"0fd497802c755b9c6fb9d58d38b9fb5fbd7dc9ccf78af3eb003a22d46bfa38894135d976bbec0f3cadc2b61a4d8648120dada26b2c3153def20fb9e370def31c802e202846946b5cb1bc2c01a7b46605292d6043ffb8f4040aaf18914e1c93fe9e683f088e23ee5e1551f00068a23fa3ebb8d6b9dcbf7a9072b12323b1a64247ba9bc7d277b08cfcc37387a0d24afca170dc027d8f0212eb62bccd9555de98936047c9bfc6a03aa539073f458795bd94b9b43003fe2299805f90c1d30ca631c8054242687e41e890bf4d744b529d7e96ea48a5bcec700993b5e980173049cf9df6f93d62ccc06d933fd6d7063890fa2d",
|
||||||
|
"697558ba60e789622ee90dffc55f1f1148cfea568c573b257fcf2083cf8aad0a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"random_message",
|
||||||
|
"5a08a19b447df98136a4502e01b286011b2d148084a7ca17e3a93616d279eb2a",
|
||||||
|
"e61d2b05302c49faa9fac6a893957a846a2b30801dc171cf62ffec92297b10bfc4a82445839ecb4c5800ffb37c5356d4b95fc565ddb2e7f3e21f2936a952373c",
|
||||||
|
"a36c57ad5e8d6a30e80e010ab903b60da0206db1b4fd981cd61e059bbd8c0d4f",
|
||||||
|
"515f0223f27302ca5e952ec978ec66bcdf04e7f72ae3a8e011e21457b355891d12e158c9b2dfb921520a0e5f531e6a20e95d42635b084a0b38a6e658f4a4181f85ea83756d316cea538cc34592491eef3e3530c34c63a693e3372cbdd0076628",
|
||||||
|
"b5bdfe6a3400c5d299d94756af4c18bbb8cca4a2635beeba3d89bbaa0025d9d3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"very_long_message",
|
||||||
|
"5a08a19b447df98136a4502e01b286011b2d148084a7ca17e3a93616d279eb2a",
|
||||||
|
"de6e0dbde23e04b6be3d4c46868a1171c6b879227b19e370765823390501b195783c356af2156e828e511473d5698c9bd1cb4a70b7e209bbb02f8dda044af02ab6869478b211e3a17d72fbe6289c1b2e6132cb141e89cf72cf70defd67a23fbfe8e718a09a9c6a345565ebdc73e1f59927744801eb4e6f0b30d2705ea181e02a4030252330ac73bc4a4d51d2ccafb2f3f62abe3e81163be325ac823571c8dfae739f70bff39164e3cec7f53b88f97735dc25ac0c0630b1b41a131a979ba5164ab92e103716e9096e2fb5a6434b31d2c3673fca7e54dcdcce3807bfd43ee7bc3422868094305a9847ce7666bf57e49fb3c2009cb30eb3ba955942b923ec2c2a4e0341d86b524b198974bdce9cf863ab3526e9e03e53399dad20fc218554567c440536a31e05573f4cb930ba6a",
|
||||||
|
"a36c57ad5e8d6a30e80e010ab903b60da0206db1b4fd981cd61e059bbd8c0d4f",
|
||||||
|
"e296a98cd96154b27d4b94cc520a8f028c61bd5ea69d22f0c3c13658c4bd5db0bd6661d0d17fac791c5cc06b3a54a853916bd7bf8a00644acc53cca43b6f51c66bc6a6cb98cf8d9f23dcd639089d1d9d3dfbc8829a1a81638317bbd3edb070c1dae181d97605eb42a6111b8696a16cc3e42639e38e93f872111fd67e934740f73a57df0e6edc6726c9aff99682bfcb7ddd99a3bef30da70d3c21e590fd02defc23be9f7f243e45a56b13562b8ecea09a14ac5af3a0500cb52f73f1bffea4a6624644da0bc4d112e5ee684b13a2ae8dbf401a5a8e581295a9dc876eaeb8ae4d732fb78d50a92c302d15c0a2308e43fc6e147ec162b28a534d6c95a2020fa141f3ce7f7dd25ff000d35f732a145abf31b3ff4d0da015d39da3b0fc70e692b567a9507be59e8c91a63fe809c495d76e70ec857cc24978771fc9314251e2bc4b1b24e0448514f97a6d438255cba8c1854019",
|
||||||
|
"8dab56b50ce6a4315b6e0b4eb32c39dfe817cb462ee9e070e2a60bb843c16107"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_with_special_chars",
|
||||||
|
"5a08a19b447df98136a4502e01b286011b2d148084a7ca17e3a93616d279eb2a",
|
||||||
|
"2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
||||||
|
"a36c57ad5e8d6a30e80e010ab903b60da0206db1b4fd981cd61e059bbd8c0d4f",
|
||||||
|
"2014d1125081316a334896cbe5aed251d9fba6f3422afbc7e7bc9019e10e184241d18f71ef86603b5ce03e1c351e3dfff8adfb8f3498300a712ec134b367c533",
|
||||||
|
"6f5a5de410db3606ff94fa14d0e084a6ba5a51241a2abf45ac593e4748c477ce"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_with_unicode",
|
||||||
|
"5a08a19b447df98136a4502e01b286011b2d148084a7ca17e3a93616d279eb2a",
|
||||||
|
"48656c6c6f2c20e4b896e7958c21",
|
||||||
|
"a36c57ad5e8d6a30e80e010ab903b60da0206db1b4fd981cd61e059bbd8c0d4f",
|
||||||
|
"7ae70cb905f23109477b4d758d3907238ff4c37e2f351f086268ba3e85cef0257af58a70d8838c7b9c044f30382c2ccf",
|
||||||
|
"7dfb2d2e76df39b2fafdf01de009088a7e4d045b8630941986111ef2010d7c4a"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tde2e_core
|
10
third-party/td/td/tde2e/test/blockchain.cpp
vendored
10
third-party/td/td/tde2e/test/blockchain.cpp
vendored
@ -30,6 +30,7 @@ S_TEST(BlockchainValidation, ZeroBlock) {
|
|||||||
TEST_DEBUG_VALUE(description, "Valid: zero block with group state only in proof");
|
TEST_DEBUG_VALUE(description, "Valid: zero block with group state only in proof");
|
||||||
auto block = BB().with_height(0)
|
auto block = BB().with_height(0)
|
||||||
.with_block_hash({})
|
.with_block_hash({})
|
||||||
|
.set_value("a", "b") // need some changes
|
||||||
.with_group_state({}, false, true, 7)
|
.with_group_state({}, false, true, 7)
|
||||||
.with_shared_key({}, false, true)
|
.with_shared_key({}, false, true)
|
||||||
.build(alice_pk);
|
.build(alice_pk);
|
||||||
@ -68,8 +69,12 @@ S_TEST(BlockchainValidation, ZeroBlock) {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
TEST_DEBUG_VALUE(description, "Invalid: zero block with skipped group state proof");
|
TEST_DEBUG_VALUE(description, "Invalid: zero block with skipped group state proof");
|
||||||
auto block =
|
auto block = BB().with_height(0)
|
||||||
BB().with_height(0).with_block_hash({}).skip_group_state_proof().skip_shared_key_proof().build(alice_pk);
|
.set_value("a", "b")
|
||||||
|
.with_block_hash({})
|
||||||
|
.skip_group_state_proof()
|
||||||
|
.skip_shared_key_proof()
|
||||||
|
.build(alice_pk);
|
||||||
TEST_DEBUG_VALUE(block, block);
|
TEST_DEBUG_VALUE(block, block);
|
||||||
TEST_TRY_STATUS(BT().expect_error(E::InvalidBlock_InvalidStateProof_Group, block));
|
TEST_TRY_STATUS(BT().expect_error(E::InvalidBlock_InvalidStateProof_Group, block));
|
||||||
}
|
}
|
||||||
@ -77,6 +82,7 @@ S_TEST(BlockchainValidation, ZeroBlock) {
|
|||||||
TEST_DEBUG_VALUE(description, "Invalid: zero block with wrong user_id in group state proof");
|
TEST_DEBUG_VALUE(description, "Invalid: zero block with wrong user_id in group state proof");
|
||||||
auto block = BB().with_height(0)
|
auto block = BB().with_height(0)
|
||||||
.with_block_hash({})
|
.with_block_hash({})
|
||||||
|
.set_value("a", "b")
|
||||||
.with_group_state({{1, 3, alice_pk.to_public_key()}}, false, true)
|
.with_group_state({{1, 3, alice_pk.to_public_key()}}, false, true)
|
||||||
.skip_shared_key_proof()
|
.skip_shared_key_proof()
|
||||||
.build(alice_pk);
|
.build(alice_pk);
|
||||||
|
103
third-party/td/td/tde2e/test/e2e.cpp
vendored
103
third-party/td/td/tde2e/test/e2e.cpp
vendored
@ -43,6 +43,42 @@
|
|||||||
|
|
||||||
using namespace tde2e_core;
|
using namespace tde2e_core;
|
||||||
namespace api = tde2e_api;
|
namespace api = tde2e_api;
|
||||||
|
template <class T>
|
||||||
|
td::Status expect_error(td::Result<T> got) {
|
||||||
|
if (got.is_ok()) {
|
||||||
|
return td::Status::Error("Got Ok, instead of Error");
|
||||||
|
}
|
||||||
|
return td::Status::OK();
|
||||||
|
}
|
||||||
|
S_TEST(MessageEncryption, simple) {
|
||||||
|
std::string secret = "secret";
|
||||||
|
{
|
||||||
|
std::string data = "some private data";
|
||||||
|
std::string wrong_secret = "wrong secret";
|
||||||
|
auto encrypted_data = MessageEncryption::encrypt_data(data, secret);
|
||||||
|
LOG(ERROR) << encrypted_data.size();
|
||||||
|
TEST_TRY_RESULT(decrypted_data, MessageEncryption::decrypt_data(encrypted_data, secret));
|
||||||
|
TEST_ASSERT_EQ(data, decrypted_data, "decryption");
|
||||||
|
TEST_TRY_STATUS(expect_error(MessageEncryption::decrypt_data(encrypted_data, wrong_secret)));
|
||||||
|
TEST_TRY_STATUS(expect_error(MessageEncryption::decrypt_data("", secret)));
|
||||||
|
TEST_TRY_STATUS(expect_error(MessageEncryption::decrypt_data(std::string(32, 'a'), secret)));
|
||||||
|
TEST_TRY_STATUS(expect_error(MessageEncryption::decrypt_data(std::string(33, 'a'), secret)));
|
||||||
|
TEST_TRY_STATUS(expect_error(MessageEncryption::decrypt_data(std::string(64, 'a'), secret)));
|
||||||
|
TEST_TRY_STATUS(expect_error(MessageEncryption::decrypt_data(std::string(128, 'a'), secret)));
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Random::Xorshift128plus rnd(123);
|
||||||
|
for (size_t i = 0; i < 255; i++) {
|
||||||
|
std::string data;
|
||||||
|
for (size_t j = 0; j < i; j++) {
|
||||||
|
data += static_cast<char>(rnd.fast('a', 'z'));
|
||||||
|
}
|
||||||
|
auto encrypted_data = MessageEncryption::encrypt_data(data, secret);
|
||||||
|
TEST_TRY_RESULT(decrypted_data, MessageEncryption::decrypt_data(encrypted_data, secret));
|
||||||
|
TEST_ASSERT_EQ(data, decrypted_data, "decryption");
|
||||||
|
}
|
||||||
|
return td::Status::OK();
|
||||||
|
}
|
||||||
|
|
||||||
struct E2eHandshakeTest {
|
struct E2eHandshakeTest {
|
||||||
td::Ed25519::PrivateKey alice;
|
td::Ed25519::PrivateKey alice;
|
||||||
@ -141,12 +177,12 @@ TEST(MiniBlockchain, Basic) {
|
|||||||
Blockchain remote_blockchain = Blockchain::create_empty();
|
Blockchain remote_blockchain = Blockchain::create_empty();
|
||||||
Blockchain local_blockchain = Blockchain::create_empty();
|
Blockchain local_blockchain = Blockchain::create_empty();
|
||||||
|
|
||||||
auto block = local_blockchain.set_value("a", "b", private_key);
|
auto block = local_blockchain.set_value(std::string(32, 'a'), "b", private_key);
|
||||||
remote_blockchain.try_apply_block(block).ensure();
|
remote_blockchain.try_apply_block(block, {}).ensure();
|
||||||
local_blockchain.try_apply_block(block).ensure();
|
local_blockchain.try_apply_block(block, {}).ensure();
|
||||||
block = local_blockchain.set_value("b", "c", private_key);
|
block = local_blockchain.set_value(std::string(32, 'b'), "c", private_key);
|
||||||
remote_blockchain.try_apply_block(block).ensure();
|
remote_blockchain.try_apply_block(block, {}).ensure();
|
||||||
local_blockchain.try_apply_block(block).ensure();
|
local_blockchain.try_apply_block(block, {}).ensure();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example usage
|
// Example usage
|
||||||
@ -674,6 +710,12 @@ S_TEST(E2E_Blockchain, Call) {
|
|||||||
|
|
||||||
TEST(Call, Basic_API) {
|
TEST(Call, Basic_API) {
|
||||||
using namespace tde2e_api;
|
using namespace tde2e_api;
|
||||||
|
auto F = [](Result<std::string> block) -> Result<std::string> {
|
||||||
|
if (block.is_ok()) {
|
||||||
|
return Blockchain::from_local_to_server(block.value());
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
};
|
||||||
|
|
||||||
auto key0 = key_generate_temporary_private_key().value();
|
auto key0 = key_generate_temporary_private_key().value();
|
||||||
auto pkey0 = key_from_public_key(key_to_public_key(key0).value()).value();
|
auto pkey0 = key_from_public_key(key_to_public_key(key0).value()).value();
|
||||||
@ -684,22 +726,22 @@ TEST(Call, Basic_API) {
|
|||||||
auto key3 = key_generate_temporary_private_key().value();
|
auto key3 = key_generate_temporary_private_key().value();
|
||||||
auto pkey3 = key_from_public_key(key_to_public_key(key3).value()).value();
|
auto pkey3 = key_from_public_key(key_to_public_key(key3).value()).value();
|
||||||
|
|
||||||
auto zero_block = call_create_zero_block(key0, CallState{0, {CallParticipant{1, pkey0, 3}}}).value();
|
auto zero_block = F(call_create_zero_block(key0, CallState{0, {CallParticipant{-1, pkey0, 3}}})).value();
|
||||||
|
|
||||||
auto call1 = call_create(key0, zero_block).value();
|
auto call1 = call_create(-1, key0, zero_block).value();
|
||||||
auto block0 = call_create_self_add_block(key1, zero_block, CallParticipant{1, pkey1, 3}).value();
|
auto block0 = F(call_create_self_add_block(key1, zero_block, CallParticipant{1, pkey1, 3})).value();
|
||||||
call1 = call_create(key1, block0).value();
|
call1 = call_create(1, key1, block0).value();
|
||||||
|
|
||||||
auto block1 = call_create_self_add_block(key2, block0, CallParticipant{2, pkey2, 3}).value();
|
auto block1 = F(call_create_self_add_block(key2, block0, CallParticipant{2, pkey2, 3})).value();
|
||||||
call_apply_block(call1, block1).value();
|
call_apply_block(call1, block1).value();
|
||||||
auto call2 = call_create(key2, block1).value();
|
auto call2 = call_create(2, key2, block1).value();
|
||||||
ASSERT_EQ(call_get_verification_words(call2).value().words, call_get_verification_words(call1).value().words);
|
ASSERT_EQ(call_get_verification_words(call2).value().words, call_get_verification_words(call1).value().words);
|
||||||
|
|
||||||
auto block2 =
|
auto block2 =
|
||||||
call_create_change_state_block(call2, CallState{0, {CallParticipant{2, pkey2, 3}, CallParticipant{3, pkey3, 3}}})
|
F(call_create_change_state_block(call2, CallState{0, {CallParticipant{2, pkey2, 3}, CallParticipant{3, pkey3, 3}}}))
|
||||||
.value();
|
.value();
|
||||||
call_describe_block(block2).value();
|
call_describe_block(block2).value();
|
||||||
auto call3 = call_create(key3, block2).value();
|
auto call3 = call_create(3, key3, block2).value();
|
||||||
|
|
||||||
call_apply_block(call2, block2).value();
|
call_apply_block(call2, block2).value();
|
||||||
CHECK(!call_apply_block(call1, block2).is_ok());
|
CHECK(!call_apply_block(call1, block2).is_ok());
|
||||||
@ -708,11 +750,11 @@ TEST(Call, Basic_API) {
|
|||||||
ASSERT_EQ(call_get_verification_words(call2).value().words, call_get_verification_words(call3).value().words);
|
ASSERT_EQ(call_get_verification_words(call2).value().words, call_get_verification_words(call3).value().words);
|
||||||
|
|
||||||
auto block31 =
|
auto block31 =
|
||||||
call_create_change_state_block(call2, CallState{0, {CallParticipant{2, pkey2, 3}, CallParticipant{3, pkey3, 3}}})
|
F(call_create_change_state_block(call2, CallState{0, {CallParticipant{2, pkey2, 3}, CallParticipant{3, pkey3, 3}}}))
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
call_apply_block(call2, block31).value();
|
call_apply_block(call2, block31).value();
|
||||||
auto commit2 = call_pull_outbound_messages(call2).value().at(0);
|
auto commit2 = F(call_pull_outbound_messages(call2).value().at(0)).value();
|
||||||
|
|
||||||
call_describe_message(commit2).value();
|
call_describe_message(commit2).value();
|
||||||
|
|
||||||
@ -720,14 +762,14 @@ TEST(Call, Basic_API) {
|
|||||||
call_receive_inbound_message(call3, commit2).value();
|
call_receive_inbound_message(call3, commit2).value();
|
||||||
|
|
||||||
call_apply_block(call3, block31).value();
|
call_apply_block(call3, block31).value();
|
||||||
auto commit3 = call_pull_outbound_messages(call3).value().at(0);
|
auto commit3 = F(call_pull_outbound_messages(call3).value().at(0)).value();
|
||||||
|
|
||||||
CHECK(commit2 != commit3);
|
CHECK(commit2 != commit3);
|
||||||
call_receive_inbound_message(call2, commit3).value();
|
call_receive_inbound_message(call2, commit3).value();
|
||||||
call_receive_inbound_message(call3, commit3).value();
|
call_receive_inbound_message(call3, commit3).value();
|
||||||
|
|
||||||
auto reveal2 = call_pull_outbound_messages(call2).value().at(0);
|
auto reveal2 = F(call_pull_outbound_messages(call2).value().at(0)).value();
|
||||||
auto reveal3 = call_pull_outbound_messages(call3).value().at(0);
|
auto reveal3 = F(call_pull_outbound_messages(call3).value().at(0)).value();
|
||||||
call_receive_inbound_message(call2, reveal2).value();
|
call_receive_inbound_message(call2, reveal2).value();
|
||||||
call_receive_inbound_message(call2, reveal3).value();
|
call_receive_inbound_message(call2, reveal3).value();
|
||||||
call_receive_inbound_message(call3, reveal2).value();
|
call_receive_inbound_message(call3, reveal2).value();
|
||||||
@ -736,21 +778,26 @@ TEST(Call, Basic_API) {
|
|||||||
ASSERT_EQ(call_get_verification_state(call2).value().emoji_hash.value(),
|
ASSERT_EQ(call_get_verification_state(call2).value().emoji_hash.value(),
|
||||||
call_get_verification_state(call3).value().emoji_hash.value());
|
call_get_verification_state(call3).value().emoji_hash.value());
|
||||||
|
|
||||||
auto e = call_encrypt(call2, "hello").value();
|
auto e = call_encrypt(call2, 1, "hello").value();
|
||||||
auto e2 = call_encrypt(call2, "hello").value();
|
auto e2 = call_encrypt(call2, 1, "hello").value();
|
||||||
CHECK(e != "hello");
|
CHECK(e != "hello");
|
||||||
LOG(ERROR) << e.size();
|
LOG(ERROR) << e.size();
|
||||||
ASSERT_EQ("hello", call_decrypt(call2, e).value());
|
ASSERT_TRUE(!call_decrypt(call2, 2, 1, e).is_ok());
|
||||||
// ASSERT_TRUE(!call_decrypt(call2, e).is_ok()); // uncomment when replay protection is written
|
// ASSERT_TRUE(!call_decrypt(call3, 2, 2, e).is_ok()); Uncomment if we will validate channel_id
|
||||||
ASSERT_EQ("hello", call_decrypt(call3, e).value());
|
ASSERT_TRUE(!call_decrypt(call3, 1, 1, e).is_ok());
|
||||||
|
ASSERT_EQ("hello", call_decrypt(call3, 2, 1, e).value());
|
||||||
|
ASSERT_TRUE(!call_decrypt(call3, 2, 1, e).is_ok());
|
||||||
|
|
||||||
auto block3 =
|
auto block3 =
|
||||||
call_create_change_state_block(call2, CallState{0, {CallParticipant{2, pkey2, 3}, CallParticipant{3, pkey3, 3}}})
|
F(call_create_change_state_block(call2, CallState{0, {CallParticipant{2, pkey2, 3}, CallParticipant{3, pkey3, 3}}}))
|
||||||
.value();
|
.value();
|
||||||
call_apply_block(call3, block3).value();
|
call_apply_block(call3, block3).value();
|
||||||
ASSERT_TRUE(!call_decrypt(call3, e).is_ok());
|
ASSERT_TRUE(!call_decrypt(call3, 2, 1, e).is_ok());
|
||||||
ASSERT_EQ("hello", call_decrypt(call3, e2).value());
|
ASSERT_EQ("hello", call_decrypt(call3, 2, 1, e2).value());
|
||||||
ASSERT_TRUE(!call_decrypt(call2, call_encrypt(call3, "bye").value()).is_ok());
|
ASSERT_TRUE(call_decrypt(call2, 3, 1, call_encrypt(call3, 1, "bye").value()).is_ok());
|
||||||
|
|
||||||
|
LOG(ERROR) << call_describe(call1).value();
|
||||||
|
LOG(ERROR) << call_describe(call2).value();
|
||||||
|
|
||||||
key_destroy_all();
|
key_destroy_all();
|
||||||
call_destroy_all();
|
call_destroy_all();
|
||||||
|
51
third-party/td/td/tde2e/test/encryption.cpp
vendored
Normal file
51
third-party/td/td/tde2e/test/encryption.cpp
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#include "EncryptionTestVectors.h"
|
||||||
|
#include "td/e2e/MessageEncryption.h"
|
||||||
|
#include "td/utils/misc.h"
|
||||||
|
#include "td/utils/simple_tests.h"
|
||||||
|
#include "td/utils/tests.h"
|
||||||
|
|
||||||
|
namespace tde2e_core {
|
||||||
|
class EncryptionTest {
|
||||||
|
public:
|
||||||
|
static td::SecureString encrypt_data_with_deterministic_padding(td::Slice data, td::Slice secret) {
|
||||||
|
auto prefix = MessageEncryption::gen_deterministic_prefix(data.size(), 16);
|
||||||
|
td::SecureString combined(prefix.size() + data.size());
|
||||||
|
combined.as_mutable_slice().copy_from(prefix);
|
||||||
|
combined.as_mutable_slice().substr(prefix.size()).copy_from(data);
|
||||||
|
return MessageEncryption::encrypt_data_with_prefix(combined.as_slice(), secret);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace tde2e_core;
|
||||||
|
|
||||||
|
S_TEST(EncryptionTest, test_vectors) {
|
||||||
|
auto test_vectors = get_test_vectors();
|
||||||
|
for (const auto &vec : test_vectors) {
|
||||||
|
LOG(INFO) << "Testing vector: " << vec.name;
|
||||||
|
|
||||||
|
// Convert hex strings to binary
|
||||||
|
auto secret = td::hex_decode(vec.secret).move_as_ok();
|
||||||
|
auto data = td::hex_decode(vec.data).move_as_ok();
|
||||||
|
auto header = td::hex_decode(vec.header).move_as_ok();
|
||||||
|
auto expected_encrypted = td::hex_decode(vec.encrypted).move_as_ok();
|
||||||
|
auto expected_encrypted_header = td::hex_decode(vec.encrypted_header).move_as_ok();
|
||||||
|
|
||||||
|
// Test encrypt_data with deterministic padding
|
||||||
|
auto encrypted = EncryptionTest::encrypt_data_with_deterministic_padding(data, secret);
|
||||||
|
|
||||||
|
// Test encrypt_header
|
||||||
|
auto encrypted_header_result = MessageEncryption::encrypt_header(header, encrypted, secret);
|
||||||
|
ASSERT_TRUE(encrypted_header_result.is_ok());
|
||||||
|
|
||||||
|
// For simplicity during debugging, verify only decryption
|
||||||
|
auto decrypted_result = MessageEncryption::decrypt_data(expected_encrypted, secret);
|
||||||
|
ASSERT_TRUE(decrypted_result.is_ok());
|
||||||
|
ASSERT_EQ(td::hex_encode(decrypted_result.ok()), vec.data);
|
||||||
|
|
||||||
|
auto decrypted_header_result = MessageEncryption::decrypt_header(expected_encrypted_header, expected_encrypted, secret);
|
||||||
|
ASSERT_TRUE(decrypted_header_result.is_ok());
|
||||||
|
ASSERT_EQ(td::hex_encode(decrypted_header_result.ok()), vec.header);
|
||||||
|
}
|
||||||
|
return td::Status::OK();
|
||||||
|
}
|
50
third-party/td/td/tdutils/td/utils/crypto.cpp
vendored
50
third-party/td/td/tdutils/td/utils/crypto.cpp
vendored
@ -752,6 +752,21 @@ static void init_thread_local_evp_md_ctx(const EVP_MD_CTX *&evp_md_ctx, const ch
|
|||||||
evp_md_ctx = nullptr;
|
evp_md_ctx = nullptr;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
static void init_thread_local_evp_mac_ctx(EVP_MAC_CTX *&evp_mac_ctx, const char *digest) {
|
||||||
|
EVP_MAC *hmac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
|
||||||
|
LOG_IF(FATAL, hmac == nullptr);
|
||||||
|
evp_mac_ctx = EVP_MAC_CTX_new(hmac);
|
||||||
|
LOG_IF(FATAL, evp_mac_ctx == nullptr);
|
||||||
|
OSSL_PARAM params[2];
|
||||||
|
params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, const_cast<char *>(digest), 0);
|
||||||
|
params[1] = OSSL_PARAM_construct_end();
|
||||||
|
EVP_MAC_CTX_set_params(evp_mac_ctx, params);
|
||||||
|
EVP_MAC_free(hmac);
|
||||||
|
detail::add_thread_local_destructor(create_destructor([&evp_mac_ctx]() mutable {
|
||||||
|
EVP_MAC_CTX_free(const_cast<EVP_MAC_CTX *>(evp_mac_ctx));
|
||||||
|
evp_mac_ctx = nullptr;
|
||||||
|
}));
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void sha1(Slice data, unsigned char output[20]) {
|
void sha1(Slice data, unsigned char output[20]) {
|
||||||
@ -967,26 +982,27 @@ void pbkdf2_sha512(Slice password, Slice salt, int iteration_count, MutableSlice
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
|
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
|
||||||
static void hmac_impl(const char *digest, Slice key, Slice message, MutableSlice dest) {
|
static void hmac_impl_finish(EVP_MAC_CTX *ctx, Slice key, Slice message, MutableSlice dest) {
|
||||||
EVP_MAC *hmac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
|
int res = EVP_MAC_init(ctx, const_cast<unsigned char *>(key.ubegin()), key.size(), nullptr);
|
||||||
LOG_IF(FATAL, hmac == nullptr);
|
|
||||||
|
|
||||||
EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(hmac);
|
|
||||||
LOG_IF(FATAL, ctx == nullptr);
|
|
||||||
|
|
||||||
OSSL_PARAM params[2];
|
|
||||||
params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, const_cast<char *>(digest), 0);
|
|
||||||
params[1] = OSSL_PARAM_construct_end();
|
|
||||||
|
|
||||||
int res = EVP_MAC_init(ctx, const_cast<unsigned char *>(key.ubegin()), key.size(), params);
|
|
||||||
LOG_IF(FATAL, res != 1);
|
LOG_IF(FATAL, res != 1);
|
||||||
res = EVP_MAC_update(ctx, message.ubegin(), message.size());
|
res = EVP_MAC_update(ctx, message.ubegin(), message.size());
|
||||||
LOG_IF(FATAL, res != 1);
|
LOG_IF(FATAL, res != 1);
|
||||||
res = EVP_MAC_final(ctx, dest.ubegin(), nullptr, dest.size());
|
res = EVP_MAC_final(ctx, dest.ubegin(), nullptr, dest.size());
|
||||||
LOG_IF(FATAL, res != 1);
|
LOG_IF(FATAL, res != 1);
|
||||||
|
}
|
||||||
EVP_MAC_CTX_free(ctx);
|
static void hmac_impl_sha256(Slice key, Slice message, MutableSlice dest) {
|
||||||
EVP_MAC_free(hmac);
|
static TD_THREAD_LOCAL EVP_MAC_CTX *ctx = nullptr;
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
init_thread_local_evp_mac_ctx(ctx, "SHA256");
|
||||||
|
}
|
||||||
|
hmac_impl_finish(ctx, key, message, dest);
|
||||||
|
}
|
||||||
|
static void hmac_impl_sha512(Slice key, Slice message, MutableSlice dest) {
|
||||||
|
static TD_THREAD_LOCAL EVP_MAC_CTX *ctx = nullptr;
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
init_thread_local_evp_mac_ctx(ctx, "SHA512");
|
||||||
|
}
|
||||||
|
hmac_impl_finish(ctx, key, message, dest);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static void hmac_impl(const EVP_MD *evp_md, Slice key, Slice message, MutableSlice dest) {
|
static void hmac_impl(const EVP_MD *evp_md, Slice key, Slice message, MutableSlice dest) {
|
||||||
@ -1001,7 +1017,7 @@ static void hmac_impl(const EVP_MD *evp_md, Slice key, Slice message, MutableSli
|
|||||||
void hmac_sha256(Slice key, Slice message, MutableSlice dest) {
|
void hmac_sha256(Slice key, Slice message, MutableSlice dest) {
|
||||||
CHECK(dest.size() == 256 / 8);
|
CHECK(dest.size() == 256 / 8);
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
|
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
|
||||||
hmac_impl("SHA256", key, message, dest);
|
hmac_impl_sha256(key, message, dest);
|
||||||
#else
|
#else
|
||||||
hmac_impl(EVP_sha256(), key, message, dest);
|
hmac_impl(EVP_sha256(), key, message, dest);
|
||||||
#endif
|
#endif
|
||||||
@ -1010,7 +1026,7 @@ void hmac_sha256(Slice key, Slice message, MutableSlice dest) {
|
|||||||
void hmac_sha512(Slice key, Slice message, MutableSlice dest) {
|
void hmac_sha512(Slice key, Slice message, MutableSlice dest) {
|
||||||
CHECK(dest.size() == 512 / 8);
|
CHECK(dest.size() == 512 / 8);
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
|
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
|
||||||
hmac_impl("SHA512", key, message, dest);
|
hmac_impl_sha512(key, message, dest);
|
||||||
#else
|
#else
|
||||||
hmac_impl(EVP_sha512(), key, message, dest);
|
hmac_impl(EVP_sha512(), key, message, dest);
|
||||||
#endif
|
#endif
|
||||||
|
@ -95,7 +95,7 @@ class StatusTest : public Test {
|
|||||||
LOG(INFO) << "Test " << get_test_name() << " PASSED";
|
LOG(INFO) << "Test " << get_test_name() << " PASSED";
|
||||||
} else {
|
} else {
|
||||||
// Include debug context in error message if available
|
// Include debug context in error message if available
|
||||||
LOG(ERROR) << "Test " << get_test_name() << " FAILED: " << status.message()
|
LOG(FATAL) << "Test " << get_test_name() << " FAILED: " << status.message()
|
||||||
<< simple_test::DebugContext::instance();
|
<< simple_test::DebugContext::instance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user