def test_bbs_from_public_key(self): bls_key_pair = BlsKeyPair.generate_g2() bbs_key_pair = BlsKeyPair(bls_key_pair.public_key) self.assertIsNone(bbs_key_pair.secret_key) public_key = bbs_key_pair.get_bbs_key(1) self.assertIsNotNone(public_key) self.assertIsNotNone(bbs_key_pair.public_key) self.assertIsNone(bbs_key_pair.secret_key) self.assertEqual(196, len(public_key.public_key)) self.assertEqual(32, len(bls_key_pair.secret_key))
def test_generate_g2_key_with_seed(self): seed = 'just a seed' key_pair = BlsKeyPair.generate_g2(seed) self.assertIsNotNone(key_pair, "Key pair should not be None") self.assertIsNotNone(key_pair.public_key, "Key pair should have public key") self.assertIsNotNone(key_pair.secret_key, "Key pair should have secret key") self.assertTrue(key_pair.is_g2, "Key should be G2 key") self.assertFalse(key_pair.is_g1, "Key should NOT be G1 key") self.assertEqual(BlsKeyPair.secret_key_size(), len(key_pair.secret_key)) self.assertEqual(BlsKeyPair.public_g2_key_size(), len(key_pair.public_key))
def verify_signed_messages_bls12381g2(messages: List[bytes], signature: bytes, public_key: bytes) -> bool: """ Verify an ed25519 signed message according to a public verification key. Args: signed: The signed messages public_key: The public key to use in verification Returns: True if verified, else False """ key_pair = BlsKeyPair(public_key=public_key) messages = [message.decode("utf-8") for message in messages] verify_request = VerifyRequest(key_pair=key_pair, signature=signature, messages=messages) try: return bbs_verify(verify_request) except ( FfiException, NativeBbsException, ) as error: # would be nice to be able to distinct between false and error raise BbsException("Unable to verify BBS+ signature") from error
def test_verify_invalid_number_of_messages(self): verify_key = BlsKeyPair(self.public_bls_key_alice) request = VerifyRequest(verify_key, self.valid_signature, self.messages[:-1]) result = verify(request) self.assertFalse(result, "Verification should fail because number of verification messages differ from the " "messages that were signed.")
def setUp(self) -> None: self.public_bls_key_alice = b"\x86M\xc0cUPQ\xdb\xdblE\x87E\x832p8\xf5\xb9\xbeM\x05\xf1G\x9emHe\x99\xf0T\xbfn\x85\x18\xdb\x86'W\x1c\xe3\x8aG\x97S\x01\xda\xfe\x0e\x15)\x144I\xf9\xd0:\xcc\xdb\xc5\xc26\x10\xf9@\xaa\x18\xf5,6Es\xfd\xc7\xf1tcZ\x98\xfe\xd6\xcct\xbfk\xfb\x9f\xf1\xad)\x15\x88w\x80\xdd\xea" self.secret_bls_key_alice = b'\x06\xe7w\xf4\x90\x0e\xacK\xb7\x94l\x00/\xaaFD\x1c\xff\x9c\xad\xdcq\xed\xb6#%\x7fu\xc7\x8c\xfe\x9c' self.public_bls_key_bob = b'\x91\xc1\xe7\xf6\xf3h&\x97\xc8%\xe5\x18xy*W\xbcy\x0e{\x9f\xba\xde\xfe\x14\x14\xb9(K_6=\xe0\x80\xe92\xe4%\x13\x88\x86\xaf\x86\x00\xeb!\x95B\x04[\xaa\xeaU\x82\xbe\x9fts\x96\xe9\xaeG\xf1!K\xe1\xb8M+\x03\x18\xd6\xa2\xa1\xac\x1f\xa5}\x12z\x17QjU\x99\x12Q\xfb2\x03\x86\xc7O4\x9d7' self.secret_bls_key_bob = b'j\xf2\xf1abM\xd3\xa5Kumz6\xeaK\x1c\x03\xac\x97"(' \ b'kR\xdd\x84#]\xea\x82\xfb\x86\xf1 ' self.messages = [ "message 1", "message 2", "message 3", "message 4", "message 5", ] self.invalid_messages = [ "message 1", "message X", "message 3", "message X", "message 5", ] self.valid_signature = sign( SignRequest( BlsKeyPair( self.public_bls_key_alice, self.secret_bls_key_alice ), self.messages ) ) self.invalid_signature = sign( SignRequest( BlsKeyPair( self.public_bls_key_alice, self.secret_bls_key_bob ), self.messages ) )
def test_blind_commitment_single_message(self): key = BlsKeyPair.generate_g2() public_key = key.get_bbs_key(1) commitment = create_blinded_commitment( CreateBlindedCommitmentRequest( public_key=public_key, messages=[IndexedMessage('message_0', 0)], nonce=b'1234')) self.assertIsNotNone(commitment) self.assertIsNotNone(commitment.blinding_factor) self.assertIsNotNone(commitment.blind_sign_context) self.assertIsNotNone(commitment.commitment)
def test_sign_single_message(self): bls_key = BlsKeyPair.generate_g2() public_key = bls_key.get_bbs_key(2) messages: List[IndexedMessage] = [ IndexedMessage('message_0', 0), IndexedMessage('message_1', 1) ] nonce = b'12345' commitment = create_blinded_commitment( CreateBlindedCommitmentRequest(public_key, messages, nonce)) blind_signature = blind_sign( BlindSignRequest(bls_key, public_key, commitment.commitment, messages)) self.assertIsNotNone(blind_signature)
def create_bls12381g2_keypair(seed: bytes = None) -> Tuple[bytes, bytes]: """ Create a public and private bls12381g2 keypair from a seed value. Args: seed: Seed for keypair Returns: A tuple of (public key, secret key) """ if not seed: seed = random_seed() try: key_pair = BlsKeyPair.generate_g2(seed) return key_pair.public_key, key_pair.secret_key except (Exception) as error: raise BbsException("Unable to create keypair") from error
def sign_messages_bls12381g2(messages: List[bytes], secret: bytes): """Sign messages using a bls12381g2 private signing key. Args: messages (List[bytes]): The messages to sign secret (bytes): The private signing key Returns: bytes: The signature """ messages = [message.decode("utf-8") for message in messages] try: key_pair = BlsKeyPair.from_secret_key(secret) sign_request = SignRequest(key_pair=key_pair, messages=messages) except ( FfiException, NativeBbsException, ) as error: # would be nice to be able to distinct between false and error raise BbsException("Unable to sign messages") from error return bbs_sign(sign_request)
def test_get_secret_key_size(self): self.assertEqual(BlsKeyPair.secret_key_size(), 32, "Secret key should be of length 32")
def test_verify_invalid_public_key(self): verify_key = BlsKeyPair(self.public_bls_key_bob) request = VerifyRequest(verify_key, self.valid_signature, self.messages) result = verify(request) self.assertFalse(result, "Verification should fail due to invalid public key")
def test_verify_success(self): verify_key = BlsKeyPair(self.public_bls_key_alice) request = VerifyRequest(verify_key, self.valid_signature, self.messages) result = verify(request) self.assertTrue(result, "Verification should be successful")
async def derive_proof( self, *, proof: dict, document: dict, reveal_document: dict, document_loader: DocumentLoaderMethod, nonce: bytes = None, ): """Derive proof for document, return dict with derived document and proof.""" # Validate that the input proof document has a proof compatible with this suite if proof.get("type") not in self.supported_derive_proof_types: raise LinkedDataProofException( f"Proof document proof incompatible, expected proof types of" f" {self.supported_derive_proof_types}, received " + proof["type"]) # Extract the BBS signature from the input proof signature = b64_to_bytes(proof["proofValue"]) # Initialize the BBS signature suite # This is used for creating the input document verification data # NOTE: both suite._create_verify_xxx_data and self._create_verify_xxx_data # are used in this file. They have small changes in behavior suite = BbsBlsSignature2020(key_pair=self.key_pair) # Initialize the derived proof derived_proof = self.proof.copy() if self.proof else {} # Ensure proof type is set derived_proof["type"] = self.signature_type # Get the input document and proof statements document_statements = suite._create_verify_document_data( document=document, document_loader=document_loader) proof_statements = suite._create_verify_proof_data( proof=proof, document=document, document_loader=document_loader) # Transform any blank node identifiers for the input # document statements into actual node identifiers # e.g _:c14n0 => urn:bnid:_:c14n0 transformed_input_document_statements = ( self._transform_blank_node_ids_into_placeholder_node_ids( document_statements)) # Transform the resulting RDF statements back into JSON-LD compact_input_proof_document = jsonld.from_rdf( "\n".join(transformed_input_document_statements)) # Frame the result to create the reveal document result reveal_document_result = jsonld.frame( compact_input_proof_document, reveal_document, {"documentLoader": document_loader}, ) # Canonicalize the resulting reveal document reveal_document_statements = suite._create_verify_document_data( document=reveal_document_result, document_loader=document_loader) # Get the indices of the revealed statements from the transformed input document # offset by the number of proof statements number_of_proof_statements = len(proof_statements) # Always reveal all the statements associated to the original proof # these are always the first statements in the normalized form proof_reveal_indices = [ indice for indice in range(number_of_proof_statements) ] # Reveal the statements indicated from the reveal document document_reveal_indices = list( map( lambda reveal_statement: transformed_input_document_statements. index(reveal_statement) + number_of_proof_statements, reveal_document_statements, )) # Check there is not a mismatch if len(document_reveal_indices) != len(reveal_document_statements): raise LinkedDataProofException( "Some statements in the reveal document not found in original proof" ) # Combine all indices to get the resulting list of revealed indices reveal_indices = [*proof_reveal_indices, *document_reveal_indices] # Create a nonce if one is not supplied nonce = nonce or urandom(50) derived_proof["nonce"] = bytes_to_b64(nonce, urlsafe=False, pad=True, encoding="utf-8") # Combine all the input statements that were originally signed # NOTE: we use plain strings here as input for the bbs lib. # the MATTR lib uses bytes, but the wrapper expects strings # it also works if we pass bytes as input all_input_statements = [*proof_statements, *document_statements] # Fetch the verification method verification_method = self._get_verification_method( proof=proof, document_loader=document_loader) # Create key pair from public key in verification method key_pair = self.key_pair.from_verification_method(verification_method) # Get the proof messages (revealed or not) proof_messages = [] for input_statement_index in range(len(all_input_statements)): # if input statement index in revealed messages indexes use revealed type # otherwise use blinding proof_type = (ProofMessageType.Revealed if input_statement_index in reveal_indices else ProofMessageType.HiddenProofSpecificBlinding) proof_messages.append( ProofMessage( message=all_input_statements[input_statement_index], proof_type=proof_type, )) # get bbs key from bls key pair bbs_public_key = BlsKeyPair( public_key=key_pair.public_key).get_bbs_key( len(all_input_statements)) # Compute the proof proof_request = CreateProofRequest( public_key=bbs_public_key, messages=proof_messages, signature=signature, nonce=nonce, ) output_proof = bls_create_proof(proof_request) # Set the proof value on the derived proof derived_proof["proofValue"] = bytes_to_b64(output_proof, urlsafe=False, pad=True, encoding="utf-8") # Set the relevant proof elements on the derived proof from the input proof derived_proof["verificationMethod"] = proof["verificationMethod"] derived_proof["proofPurpose"] = proof["proofPurpose"] derived_proof["created"] = proof["created"] return DeriveProofResult(document={**reveal_document_result}, proof=derived_proof)
async def verify_proof( self, *, proof: dict, document: dict, purpose: ProofPurpose, document_loader: DocumentLoaderMethod, ) -> ProofResult: """Verify proof against document and proof purpose.""" try: proof["type"] = self.mapped_derived_proof_type # Get the proof and document statements proof_statements = self._create_verify_proof_data( proof=proof, document=document, document_loader=document_loader) document_statements = self._create_verify_document_data( document=document, document_loader=document_loader) # Transform the blank node identifier placeholders for the document statements # back into actual blank node identifiers transformed_document_statements = ( self._transform_placeholder_node_ids_into_blank_node_ids( document_statements)) # Combine all the statements to be verified # NOTE: we use plain strings here as input for the bbs lib. # the MATTR lib uses bytes, but the wrapper expects strings # it also works if we pass bytes as input statements_to_verify = [ *proof_statements, *transformed_document_statements ] # Fetch the verification method verification_method = self._get_verification_method( proof=proof, document_loader=document_loader) key_pair = self.key_pair.from_verification_method( verification_method) proof_bytes = b64_to_bytes(proof["proofValue"]) total_message_count = get_total_message_count(proof_bytes) # get bbs key from bls key pair bbs_public_key = BlsKeyPair(public_key=key_pair.public_key ).get_bbs_key(total_message_count) # verify dervied proof verify_request = VerifyProofRequest( public_key=bbs_public_key, proof=proof_bytes, messages=statements_to_verify, nonce=b64_to_bytes(proof["nonce"]), ) verified = bls_verify_proof(verify_request) if not verified: raise LinkedDataProofException( f"Invalid signature on document {document}") purpose_result = purpose.validate( proof=proof, document=document, suite=self, verification_method=verification_method, document_loader=document_loader, ) if not purpose_result.valid: return ProofResult( verified=False, purpose_result=purpose_result, error=purpose_result.error, ) return ProofResult(verified=True, purpose_result=purpose_result) except Exception as err: return ProofResult(verified=False, error=err)
def test_get_public_g1_key_size(self): self.assertEqual(BlsKeyPair.public_g1_key_size(), 48, "G1 key should be of length 48")
def test_bbs_from_secret_key(self): secret_key = BlsKeyPair.generate_g2() public_key = secret_key.get_bbs_key(1) self.assertIsNotNone(public_key) self.assertEqual(196, len(public_key.public_key))
def test_get_public_g2_key_size(self): self.assertEqual(BlsKeyPair.public_g2_key_size(), 96, "G2 key should be of length 96")