diff --git a/.travis.yml b/.travis.yml index bc4cf2e04..490c9d6bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,9 +46,10 @@ install: }" - export PATH=$PWD/nim/$NIMVER/bin:$GOPATH/bin:$PATH - go version - - go get -v github.com/libp2p/go-libp2p-daemon - - cd $GOPATH/src/github.com/libp2p/go-libp2p-daemon - - make + - git clone https://github.com/libp2p/go-libp2p-daemon + - cd go-libp2p-daemon + - git checkout v0.0.1 + - go install ./... - cd $HOME/build/status-im/nim-libp2p script: diff --git a/libp2p/crypto/crypto.nim b/libp2p/crypto/crypto.nim index 39cc23f12..08bf191b1 100644 --- a/libp2p/crypto/crypto.nim +++ b/libp2p/crypto/crypto.nim @@ -8,11 +8,13 @@ ## those terms. ## This module implements Public Key and Private Key interface for libp2p. -import rsa, ecnist -import ed25519/ed25519 -import ../protobuf/minprotobuf +import rsa, ecnist, ed25519/ed25519 +import ../protobuf/minprotobuf, ../vbuffer import nimcrypto/[rijndael, blowfish, sha, sha2, hash, hmac, utils] +# This is workaround for Nim's `import` bug +export rijndael, blowfish, sha, sha2, hash, hmac, utils + type PKScheme* = enum RSA = 0, @@ -69,7 +71,11 @@ type macsize*: int data*: seq[byte] + Signature* = object + data*: seq[byte] + P2pKeyError* = object of Exception + P2pSigError* = object of Exception const SupportedSchemes* = {RSA, Ed25519, ECDSA} @@ -192,6 +198,14 @@ proc toBytes*(key: PublicKey, data: var openarray[byte]): int = if len(data) >= result: copyMem(addr data[0], addr msg.buffer[0], len(msg.buffer)) +proc toBytes*(sig: Signature, data: var openarray[byte]): int = + ## Serialize signature ``sig`` and store it to ``data``. + ## + ## Returns number of bytes (octets) needed to store signature ``sig``. + result = len(sig.data) + if len(data) >= result: + copyMem(addr data[0], unsafeAddr sig.data[0], len(sig.data)) + proc getBytes*(key: PrivateKey): seq[byte] = ## Return private key ``key`` in binary form (using libp2p's protobuf ## serialization). @@ -210,6 +224,10 @@ proc getBytes*(key: PublicKey): seq[byte] = msg.finish() result = msg.buffer +proc getBytes*(sig: Signature): seq[byte] = + ## Return signature ``sig`` in binary form. + result = sig.data + proc init*(key: var PrivateKey, data: openarray[byte]): bool = ## Initialize private key ``key`` from libp2p's protobuf serialized raw ## binary form. @@ -264,6 +282,14 @@ proc init*(key: var PublicKey, data: openarray[byte]): bool = key = nkey result = true +proc init*(sig: var Signature, data: openarray[byte]): bool = + ## Initialize signature ``sig`` from raw binary form. + ## + ## Returns ``true`` on success. + if len(data) > 0: + sig.data = @data + result = true + proc init*(key: var PrivateKey, data: string): bool = ## Initialize private key ``key`` from libp2p's protobuf serialized ## hexadecimal string representation. @@ -278,6 +304,13 @@ proc init*(key: var PublicKey, data: string): bool = ## Returns ``true`` on success. result = key.init(fromHex(data)) +proc init*(sig: var Signature, data: string): bool = + ## Initialize signature ``sig`` from serialized hexadecimal string + ## representation. + ## + ## Returns ``true`` on success. + result = sig.init(fromHex(data)) + proc init*(t: typedesc[PrivateKey], data: openarray[byte]): PrivateKey = ## Create new private key from libp2p's protobuf serialized binary form. if not result.init(data): @@ -288,6 +321,11 @@ proc init*(t: typedesc[PublicKey], data: openarray[byte]): PublicKey = if not result.init(data): raise newException(P2pKeyError, "Incorrect binary form") +proc init*(t: typedesc[Signature], data: openarray[byte]): Signature = + ## Create new public key from libp2p's protobuf serialized binary form. + if not result.init(data): + raise newException(P2pSigError, "Incorrect binary form") + proc init*(t: typedesc[PrivateKey], data: string): PrivateKey = ## Create new private key from libp2p's protobuf serialized hexadecimal string ## form. @@ -298,6 +336,10 @@ proc init*(t: typedesc[PublicKey], data: string): PublicKey = ## form. result = t.init(fromHex(data)) +proc init*(t: typedesc[Signature], data: string): Signature = + ## Create new signature from serialized hexadecimal string form. + result = t.init(fromHex(data)) + proc `==`*(key1, key2: PublicKey): bool = ## Return ``true`` if two public keys ``key1`` and ``key2`` of the same ## scheme and equal. @@ -346,34 +388,38 @@ proc `$`*(key: PublicKey): string = result.add($(key.eckey)) result.add(")") -proc sign*(key: PrivateKey, data: openarray[byte]): seq[byte] = +proc `$`*(sig: Signature): string = + ## Get string representation of signature ``sig``. + result = toHex(sig.data) + +proc sign*(key: PrivateKey, data: openarray[byte]): Signature = ## Sign message ``data`` using private key ``key`` and return generated ## signature in raw binary form. if key.scheme == RSA: var sig = key.rsakey.sign(data) - result = sig.getBytes() + result.data = sig.getBytes() elif key.scheme == Ed25519: var sig = key.edkey.sign(data) - result = sig.getBytes() + result.data = sig.getBytes() elif key.scheme == ECDSA: var sig = key.eckey.sign(data) - result = sig.getBytes() + result.data = sig.getBytes() -proc verify*(sig: openarray[byte], message: openarray[byte], +proc verify*(sig: Signature, message: openarray[byte], key: PublicKey): bool = ## Verify signature ``sig`` using message ``message`` and public key ``key``. ## Return ``true`` if message signature is valid. if key.scheme == RSA: var signature: RsaSignature - if signature.init(sig) == Asn1Status.Success: + if signature.init(sig.data) == Asn1Status.Success: result = signature.verify(message, key.rsakey) elif key.scheme == Ed25519: var signature: EdSignature - if signature.init(sig): + if signature.init(sig.data): result = signature.verify(message, key.edkey) elif key.scheme == ECDSA: var signature: EcSignature - if signature.init(sig) == Asn1Status.Success: + if signature.init(sig.data) == Asn1Status.Success: result = signature.verify(message, key.eckey) template makeSecret(buffer, hmactype, secret, seed) = @@ -485,3 +531,62 @@ proc makeSecret*(remoteEPublic: PublicKey, localEPrivate: PrivateKey, if remoteEPublic.scheme == ECDSA: if localEPrivate.scheme == remoteEPublic.scheme: result = toSecret(remoteEPublic.eckey, localEPrivate.eckey, data) + +## Serialization/Deserialization helpers + +proc write*(vb: var VBuffer, pubkey: PublicKey) {.inline.} = + ## Write PublicKey value ``pubkey`` to buffer ``vb``. + vb.writeSeq(pubkey.getBytes()) + +proc write*(vb: var VBuffer, seckey: PrivateKey) {.inline.} = + ## Write PrivateKey value ``seckey`` to buffer ``vb``. + vb.writeSeq(seckey.getBytes()) + +proc write*(vb: var VBuffer, sig: PrivateKey) {.inline.} = + ## Write Signature value ``sig`` to buffer ``vb``. + vb.writeSeq(sig.getBytes()) + +proc initProtoField*(index: int, pubkey: PublicKey): ProtoField = + ## Initialize ProtoField with PublicKey ``pubkey``. + result = initProtoField(index, pubkey.getBytes()) + +proc initProtoField*(index: int, seckey: PrivateKey): ProtoField = + ## Initialize ProtoField with PrivateKey ``seckey``. + result = initProtoField(index, seckey.getBytes()) + +proc initProtoField*(index: int, sig: Signature): ProtoField = + ## Initialize ProtoField with Signature ``sig``. + result = initProtoField(index, sig.getBytes()) + +proc getValue*(data: var ProtoBuffer, field: int, value: var PublicKey): int = + ## Read ``PublicKey`` from ProtoBuf's message and validate it. + var buf: seq[byte] + var key: PublicKey + result = getLengthValue(data, field, buf) + if result > 0: + if not key.init(buf): + result = -1 + else: + value = key + +proc getValue*(data: var ProtoBuffer, field: int, value: var PrivateKey): int = + ## Read ``PrivateKey`` from ProtoBuf's message and validate it. + var buf: seq[byte] + var key: PrivateKey + result = getLengthValue(data, field, buf) + if result > 0: + if not key.init(buf): + result = -1 + else: + value = key + +proc getValue*(data: var ProtoBuffer, field: int, value: var Signature): int = + ## Read ``Signature`` from ProtoBuf's message and validate it. + var buf: seq[byte] + var sig: Signature + result = getLengthValue(data, field, buf) + if result > 0: + if not sig.init(buf): + result = -1 + else: + value = sig diff --git a/libp2p/crypto/ed25519/ed25519.nim b/libp2p/crypto/ed25519/ed25519.nim index 7d842c6b7..280cf07ab 100644 --- a/libp2p/crypto/ed25519/ed25519.nim +++ b/libp2p/crypto/ed25519/ed25519.nim @@ -14,7 +14,33 @@ import constants import nimcrypto/[hash, sha2, sysrand, utils] # This workaround needed because of some bugs in Nim Static[T]. -export sha2 +export hash, sha2 + +const + EdPrivateKeySize* = 64 + ## Size in octets (bytes) of serialized ED25519 private key. + EdPublicKeySize* = 32 + ## Size in octets (bytes) of serialized ED25519 public key. + EdSignatureSize* = 64 + ## Size in octets (bytes) of serialized ED25519 signature. + +type + EdPrivateKey* = object + data*: array[EdPrivateKeySize, byte] + + EdPublicKey* = object + data*: array[EdPublicKeySize, byte] + + EdSignature* = object + data*: array[EdSignatureSize, byte] + + EdKeyPair* = object + seckey*: EdPrivateKey + pubkey*: EdPublicKey + + EdError* = object of Exception + EdRngError* = object of EdError + EdIncorrectError* = object of EdError proc `-`(x: uint32): uint32 {.inline.} = result = (0xFFFF_FFFF'u32 - x) + 1'u32 @@ -1612,32 +1638,6 @@ proc checkScalar*(scalar: openarray[byte]): uint32 = c = -1 result = NEQ(z, 0'u32) and LT0(c) -const - EdPrivateKeySize* = 64 - ## Size in octets (bytes) of serialized ED25519 private key. - EdPublicKeySize* = 32 - ## Size in octets (bytes) of serialized ED25519 public key. - EdSignatureSize* = 64 - ## Size in octets (bytes) of serialized ED25519 signature. - -type - EdPrivateKey* = object - data*: array[EdPrivateKeySize, byte] - - EdPublicKey* = object - data*: array[EdPublicKeySize, byte] - - EdSignature* = object - data*: array[EdSignatureSize, byte] - - EdKeyPair* = object - seckey*: EdPrivateKey - pubkey*: EdPublicKey - - EdError* = object of Exception - EdRngError* = object of EdError - EdIncorrectError* = object of EdError - proc random*(t: typedesc[EdPrivateKey]): EdPrivateKey = ## Generate new random ED25519 private key using OS specific CSPRNG. var @@ -1645,11 +1645,11 @@ proc random*(t: typedesc[EdPrivateKey]): EdPrivateKey = pk: array[EdPublicKeySize, byte] if randomBytes(result.data.toOpenArray(0, 31)) != 32: raise newException(EdRngError, "Could not generate random data") - var hash = sha512.digest(result.data.toOpenArray(0, 31)) - hash.data[0] = hash.data[0] and 0xF8'u8 - hash.data[31] = hash.data[31] and 0x3F'u8 - hash.data[31] = hash.data[31] or 0x40'u8 - geScalarMultBase(point, hash.data) + var hh = sha512.digest(result.data.toOpenArray(0, 31)) + hh.data[0] = hh.data[0] and 0xF8'u8 + hh.data[31] = hh.data[31] and 0x3F'u8 + hh.data[31] = hh.data[31] or 0x40'u8 + geScalarMultBase(point, hh.data) geP3ToBytes(pk, point) copyMem(addr result.data[32], addr pk[0], 32) @@ -1659,11 +1659,11 @@ proc random*(t: typedesc[EdKeyPair]): EdKeyPair = var point: GeP3 if randomBytes(result.seckey.data.toOpenArray(0, 31)) != 32: raise newException(EdRngError, "Could not generate random data") - var hash = sha512.digest(result.seckey.data.toOpenArray(0, 31)) - hash.data[0] = hash.data[0] and 0xF8'u8 - hash.data[31] = hash.data[31] and 0x3F'u8 - hash.data[31] = hash.data[31] or 0x40'u8 - geScalarMultBase(point, hash.data) + var hh = sha512.digest(result.seckey.data.toOpenArray(0, 31)) + hh.data[0] = hh.data[0] and 0xF8'u8 + hh.data[31] = hh.data[31] and 0x3F'u8 + hh.data[31] = hh.data[31] or 0x40'u8 + geScalarMultBase(point, hh.data) geP3ToBytes(result.pubkey.data, point) copyMem(addr result.seckey.data[32], addr result.pubkey.data[0], 32) diff --git a/libp2p/daemon/daemonapi.nim b/libp2p/daemon/daemonapi.nim index 9b97e109b..3e36dc8f8 100644 --- a/libp2p/daemon/daemonapi.nim +++ b/libp2p/daemon/daemonapi.nim @@ -12,8 +12,9 @@ import os, osproc, strutils, tables, streams, strtabs import chronos import ../varint, ../multiaddress, ../multicodec, ../base58, ../cid, ../peer import ../wire, ../multihash, ../protobuf/minprotobuf +import ../crypto/crypto -export peer, multiaddress, multicodec, multihash, cid +export peer, multiaddress, multicodec, multihash, cid, crypto when not defined(windows): import posix @@ -78,9 +79,7 @@ type VALUE = 1, END = 2 - # PeerID* = seq[byte] MultiProtocol* = string - LibP2PPublicKey* = seq[byte] DHTValue* = seq[byte] P2PStreamFlags* {.pure.} = enum @@ -137,8 +136,8 @@ type data*: seq[byte] seqno*: seq[byte] topics*: seq[string] - signature*: seq[byte] - key*: seq[byte] + signature*: Signature + key*: PublicKey P2PStreamCallback* = proc(api: DaemonAPI, stream: P2PStream): Future[void] {.gcsafe.} @@ -662,7 +661,7 @@ proc newDaemonApi*(flags: set[P2PDaemonFlags] = {}, raise newException(DaemonLocalError, "Could not find daemon executable!") # Starting daemon process - echo "Starting ", cmd, " ", args.join(" ") + # echo "Starting ", cmd, " ", args.join(" ") api.process = startProcess(cmd, "", args, env, {poStdErrToStdOut}) # Waiting until daemon will not be bound to control socket. while true: @@ -928,6 +927,10 @@ proc dhtGetSingleValue(pb: var ProtoBuffer): seq[byte] = if pb.getLengthValue(3, result) == -1: raise newException(DaemonLocalError, "Missing field `value`!") +proc dhtGetSinglePublicKey(pb: var ProtoBuffer): PublicKey = + if pb.getValue(3, result) == -1: + raise newException(DaemonLocalError, "Missing field `value`!") + proc dhtGetSinglePeerID(pb: var ProtoBuffer): PeerID = if pb.getValue(3, result) == -1: raise newException(DaemonLocalError, "Missing field `value`!") @@ -975,7 +978,7 @@ proc dhtFindPeer*(api: DaemonAPI, peer: PeerID, await api.closeConnection(transp) proc dhtGetPublicKey*(api: DaemonAPI, peer: PeerID, - timeout = 0): Future[LibP2PPublicKey] {.async.} = + timeout = 0): Future[PublicKey] {.async.} = ## Get peer's public key from peer with id ``peer``. ## ## You can specify timeout for DHT request with ``timeout`` value. ``0`` value @@ -985,7 +988,7 @@ proc dhtGetPublicKey*(api: DaemonAPI, peer: PeerID, var pb = await transp.transactMessage(requestDHTGetPublicKey(peer, timeout)) withMessage(pb) do: pb.enterDhtMessage(DHTResponseType.VALUE) - result = pb.dhtGetSingleValue() + result = pb.dhtGetSinglePublicKey() finally: await api.closeConnection(transp) @@ -1178,8 +1181,6 @@ proc pubsubPublish*(api: DaemonAPI, topic: string, proc getPubsubMessage*(pb: var ProtoBuffer): PubSubMessage = result.data = newSeq[byte]() result.seqno = newSeq[byte]() - result.signature = newSeq[byte]() - result.key = newSeq[byte]() discard pb.getValue(1, result.peer) discard pb.getBytes(2, result.data) discard pb.getBytes(3, result.seqno) @@ -1193,8 +1194,8 @@ proc getPubsubMessage*(pb: var ProtoBuffer): PubSubMessage = result.topics = newSeq[string]() result.topics.add(stritem) item.setLen(0) - discard pb.getBytes(5, result.signature) - discard pb.getBytes(6, result.key) + discard pb.getValue(5, result.signature) + discard pb.getValue(6, result.key) proc pubsubLoop(api: DaemonAPI, ticket: PubsubTicket) {.async.} = while true: diff --git a/libp2p/multihash.nim b/libp2p/multihash.nim index 0b6d7e08e..6b4311eb4 100644 --- a/libp2p/multihash.nim +++ b/libp2p/multihash.nim @@ -24,6 +24,9 @@ import tables import nimcrypto/[sha, sha2, keccak, blake2, hash, utils] import varint, vbuffer, base58, multicodec, multibase +# This is workaround for Nim `import` bug. +export sha, sha2, keccak, blake2, hash, utils + const MaxHashSize* = 128 diff --git a/tests/testcrypto.nim b/tests/testcrypto.nim index 65fc8dd2a..5f13a9007 100644 --- a/tests/testcrypto.nim +++ b/tests/testcrypto.nim @@ -342,9 +342,6 @@ const "FA5CB0689A1DFDBAE8618BC079D70E318377B0DA" ] - - - proc cmp(a, b: openarray[byte]): bool = result = (@a == @b) @@ -390,6 +387,70 @@ suite "Key interface test suite": toHex(checkseckey) == stripSpaces(PrivateKeys[i]) toHex(checkpubkey) == stripSpaces(PublicKeys[i]) + test "Generate/Sign/Serialize/Deserialize/Verify test": + var msg = "message to sign" + var bmsg = cast[seq[byte]](msg) + + for i in 0..<5: + var seckey = PrivateKey.random(ECDSA) + var pubkey = seckey.getKey() + var pair = KeyPair.random(ECDSA) + var sig1 = pair.seckey.sign(bmsg) + var sig2 = seckey.sign(bmsg) + var sersig1 = sig1.getBytes() + var sersig2 = sig2.getBytes() + var serpub1 = pair.pubkey.getBytes() + var serpub2 = pubkey.getBytes() + var recsig1 = Signature.init(sersig1) + var recsig2 = Signature.init(sersig2) + var recpub1 = PublicKey.init(serpub1) + var recpub2 = PublicKey.init(serpub2) + check: + sig1.verify(bmsg, pair.pubkey) == true + recsig1.verify(bmsg, recpub1) == true + sig2.verify(bmsg, pubkey) == true + recsig2.verify(bmsg, recpub2) == true + + for i in 0..<5: + var seckey = PrivateKey.random(Ed25519) + var pubkey = seckey.getKey() + var pair = KeyPair.random(Ed25519) + var sig1 = pair.seckey.sign(bmsg) + var sig2 = seckey.sign(bmsg) + var sersig1 = sig1.getBytes() + var sersig2 = sig2.getBytes() + var serpub1 = pair.pubkey.getBytes() + var serpub2 = pubkey.getBytes() + var recsig1 = Signature.init(sersig1) + var recsig2 = Signature.init(sersig2) + var recpub1 = PublicKey.init(serpub1) + var recpub2 = PublicKey.init(serpub2) + check: + sig1.verify(bmsg, pair.pubkey) == true + recsig1.verify(bmsg, recpub1) == true + sig2.verify(bmsg, pubkey) == true + recsig2.verify(bmsg, recpub2) == true + + for i in 0..<5: + var seckey = PrivateKey.random(RSA, 512) + var pubkey = seckey.getKey() + var pair = KeyPair.random(RSA, 512) + var sig1 = pair.seckey.sign(bmsg) + var sig2 = seckey.sign(bmsg) + var sersig1 = sig1.getBytes() + var sersig2 = sig2.getBytes() + var serpub1 = pair.pubkey.getBytes() + var serpub2 = pubkey.getBytes() + var recsig1 = Signature.init(sersig1) + var recsig2 = Signature.init(sersig2) + var recpub1 = PublicKey.init(serpub1) + var recpub2 = PublicKey.init(serpub2) + check: + sig1.verify(bmsg, pair.pubkey) == true + recsig1.verify(bmsg, recpub1) == true + sig2.verify(bmsg, pubkey) == true + recsig2.verify(bmsg, recpub2) == true + test "Go key stretch function AES128-SHA256 test vectors": check testStretcher(0, 4, Aes128, Sha256) == true test "Go key stretch function AES256-SHA512 test vectors":